четверг, 3 декабря 2009 г.

Pathological Interfaces II: Маршалинг

WTF?Для маршаллинга COM-интерфейсов между потоками есть такой функционал системы COM, как CoMarshalInterThreadInterfaceInStream / CoGetInterfaceAndReleaseStream.

Функции выполняют именно то, что указанно в названии. А именно работу по облегчению "обмена" информацией об интерфейсе между потоками: в одном потоке сохранили данные о интерфейсе в IStream, а в другом восстановили их из этого же IStream’а.
Вроде ничего проще быть не может, все ясно, все понятно и очень удобно. Но разработчики подсунули свои грабли…

Дело в том, что, несмотря на то, что на выходе CoMarshalInterThreadInterfaceInStream получаем IStream, использовать этот стрим кроме как для функции, которой он предназначен нельзя. Совсем нельзя. Даже менять счетчик ссылок.

Оказывается, что CoGetInterfaceAndReleaseStream делает именно то, что написано в названии, а именно уничтожает стрим-объект, полностью игнорируя счетчик ссылок.

И не сразу понятно, что этот стрим предназначен исключительно для использования в качестве параметра CoGetInterfaceAndReleaseStream — новички могут наступить на расставленные грабли. Тем более что в мануалах о таком поведении не написано (ну может где и есть, но точно не там где должно быть).

Да что там новички! Ошибки делают и профессионалы из солидных контор. Например, во всех известных мне версиях Delphi функции объявлены как
function CoMarshalInterThreadInterfaceInStream(const iid: TIID; 
unk: IUnknown; out stm: IStream): HResult; stdcall;

function CoGetInterfaceAndReleaseStream(stm: IStream; const 
iid: TIID; out pv): HResult; stdcall;

Дело в том, что delphi по части интерфейсов ведет себя как managed-язык: автоматически наращивает/убавляет счетчик ссылок. Но поскольку CoGetInterfaceAndReleaseStream этот счетчик игнорирует и сразу уничтожает объект, то получается ситуация, что делфи пытается вызвать Release для уже уничтоженного объекта.

Обычно в таких случаях делают всяческие извращения, типа этого:
var
pStream : pointer; // Чтоб дельфа не вела подсчет ссылок вместо IStream будет обычный указатель

// В потоке 1: маршаллинг
CoMarshalInterThreadInterfaceInStream (IDispatch,  pObject1 as IUnknown, IStream (pStream));

// В потоке 2: демаршаллинг
CoGetInterfaceAndReleaseStream(IStream(pStream), IDispatch, pObject1);

А теперь вопрос: зачем сюда было тулить IStream? Чем обычный Cookie в виде DWORD не прокатил?

Отдельное "спасибо" и Borland-у: ведь достаточно было правильно оформить интерес к этим функциям, и не пришлось бы выдумывать вышеописанные ужасы маршаллинга
// Интерфейс стирма заменили на куку
function CoMarshalInterThreadInterfaceInStream(const iid: TIID; 
unk: IUnknown; out cookie: DWORD): HResult; stdcall;

function CoGetInterfaceAndReleaseStream(cookie: DWORD; const 
iid: TIID; out pv): HResult; stdcall;


Документация по функциям-виновникам сего поста:
1. CoMarshalInterThreadInterfaceInStream Function
2. CoGetInterfaceAndReleaseStream Function

1 комментарий:

Анонимный комментирует...

Странно, искал совсем не это, гугл выдал Ваш сайт, и судя по всему не зря, есть что почитать! Goodwork!