COM marshalling: создание proxy/stub на коленке

Хочу поделиться рецептом победы над коварным IShellLinkDataList (см. предыдущие посты COM marshalling. и Shortcuts, shell and COM apartments.)

Итак, исходная задача: вызвать метод локального интерфейса (интерфейса, помеченного атрибутом [local]) удалённо.

В принципе, единственное отличие локального интерфейса от нелокального – это наличие marshalling кода, иными словами – зарегистрированного proxy/stub объекта для этого интерфейса. В большинстве случаев это означает, что достаточно написать (или взять готовое) описание интерфейса в IDL, пропустить его через MIDL и скомпоновать полученные исходные файлы в .dll. После регистрации этой библиотеки на клиенте и сервере появиться возможность вызывать методы нужного интерфейса по сети (или через границу apartment на одной и той же машине).

В случае IShellLinkDataList этот метод не работает из-за двух проблемных методов:

HRESULT AddDataBlock([in] void* pDataBlock);
HRESULT CopyDataBlock([in] DWORD dwSig, [out] void** ppDataBlock);

AddDataBlock принимает указатель на структуру переменного размера. Размер и тип структуры задается в её заголовке:

DWORD cbSize;             // Size of this extra data block
DWORD dwSignature;        // signature of this extra data block

CopyDataBlock возвращает указатель на ту же структуру. Тип структуры определяется переданным значением dwSig. Самое неприятное, что память под структуру выделяется сервером с помощью функции LocalAlloc, а освобождается – клиентом функцией LocalFree. Именно использование LocalAlloc и LocalFree, как я понимаю, и является причиной того, что интерфейс в целом помечен как локальный.

К счастью RPC обладает достаточной гибкостью и позволяет программисту брать на себя контроль над передачей нетривиальных данных. Для этой цели служит IDL атрибут [wire_marshal]. В случае с AddDataBlock и CopyDataBlock проблема решается следующим описанием:

typedef [unique] wireDATABLOCK* wirePDATABLOCK;
typedef [wire_marshal(wirePDATABLOCK)] wirePDATABLOCK PDATABLOCK;

Здесь wirePDATABLOCK это имя типа передаваемой структуры. Первая строка просто определяет указатель на структуру. Основное здесь вторая строка. Она определяет тип PDATABLOCK как тип, marshalling которого реализуется вручную. Определение интерфейса при этом выглядит так:

[…]
interface IShellLinkDataList: IUnknown {
    HRESULT AddDataBlock([in] PDATABLOCK pDataBlock);
    HRESULT CopyDataBlock([in] DWORD dwSig, [out] PDATABLOCK* ppDataBlock);
};

Код, сгенерированный по такому описанию ожидает, что приложение реализует 4 функции, ответственные за marshalling PDATABLOCK:

Реализация этих функций тривиальна, за исключением нескольких деталей.

Ссылка на исходный код примера.

comments powered by Disqus