Конференция "Corba" » Взаимодействие с COM через IRpcChannelBuffer [Delphi, Windows]
 
  • SPeller © (04.05.09 05:36) [0]
    Не получается сделать даже локальный вызов. Код такой:

     ITestIntf = interface(IDispatch)
       ['{A95C745C-DD9C-4B79-9402-2E5891D9BB51}']
       procedure TestMethod; safecall;
     end;

     TTestImpl = class(TAutoObject, ITestIntf)
       procedure TestMethod; safecall;
     end;

     TMyChannel = class(TInterfacedObject, IRpcChannelBuffer)
     private
       FStub: IRpcStubBuffer;
     public
       constructor Create; virtual;

       property Stub: IRpcStubBuffer read FStub write FStub;

       function GetBuffer(var message: TRpcOleMessage; iid: TIID): HResult; stdcall;
       function SendReceive(var message: TRpcOleMessage; var status: Longint): HResult; stdcall;
       function FreeBuffer(var message: TRpcOleMessage): HResult; stdcall;
       function GetDestCtx(out dwDestContext: Longint; out pvDestContext): HResult; stdcall;
       function IsConnected: HResult; stdcall;
     end;

    procedure TTestImpl.TestMethod;
    begin
     ShowMessage('TestMethod');
    end;

    begin
     FObj := TTestImpl.Create;
     FObj._AddRef;

     Lib := LoadTypeLibrary(ShortToLongFileName(GetModuleFileName));
     Lib.GetTypeInfoOfGuid(IID_ITestIntf, TypInfo);

     OleCheck(CreateStubFromTypeInfo(TypInfo, IID_ITestIntf, nil, Stub));
     OleCheck(CreateProxyFromTypeInfo(TypInfo, nil, IID_ITestIntf, Proxy, ppv));

     Chan := TMyChannel.Create;
     Proxy.Connect(Chan);
     Chan.Stub := Stub;
     Stub.Connect(FObj);

     ITestIntf(ppv).TestMethod;
    end;



    На ITestIntf(ppv).TestMethod вылетает ошибка OLE error C0000005. При этом при выполнении этой строки вызываются методы моего IRpcChannelBuffer в таком порядке:

    TMyChannel.GetDestCtx
    TMyChannel.GetBuffer
    TMyChannel.FreeBuffer
    Эксепшин

    В чем может быть проблема?

    Реализация IRpcChannelBuffer такая:

    constructor TMyChannel.Create;
    begin
     inherited;
    end;

    function TMyChannel.FreeBuffer(var message: TRpcOleMessage): HResult;
    begin
     FreeMem(message.Buffer);
     Result := S_OK;
    end;

    function TMyChannel.GetBuffer(var message: TRpcOleMessage; iid: TIID): HResult;
    begin
     try
       //message.reserved1 := nil;
       //FillChar(message.reserved2, SizeOf(message.reserved2), 0);
       message.cbBuffer := 2500;
       GetMem(message.Buffer, message.cbBuffer);
       FillChar(message.Buffer^, message.cbBuffer, 0);
       Result := S_OK;
     except
       Result := E_OUTOFMEMORY;
     end;
    end;

    function TMyChannel.GetDestCtx(out dwDestContext: Integer;
     out pvDestContext): HResult;
    begin
     dwDestContext := MSHCTX_LOCAL;
     Result := S_OK;
    end;

    function TMyChannel.IsConnected: HResult;
    begin
     if (FStub <> nil) then
       Result := S_OK
     else
       Result := S_FALSE;
    end;

    function TMyChannel.SendReceive(var message: TRpcOleMessage;
     var status: Integer): HResult;
    begin
     if (Stub <> nil) then
       Result := Stub.Invoke(message, Self)
     else
       Result := S_FALSE;
     if Succeeded(Result) and (status <> 0) then
       status := 0;
    end;



    Функции создания стаба и прокси объявлены так:

    function CreateStubFromTypeInfo(
     pTypeInfo: ITypeInfo; const riid: TGUID; pUnkServer: IInterface;
     out ppStub: IRpcStubBuffer): HRESULT; stdcall;
    function CreateProxyFromTypeInfo(
     pTypeInfo: ITypeInfo; pUnkOuter: IInterface; const riid: TGUID;
     out ppProxy: IRpcProxyBuffer; out ppv: Pointer): HRESULT; stdcall;
    function CreateProxyFromTypeInfo; external RPCDLL name 'CreateProxyFromTypeInfo';
    function CreateStubFromTypeInfo; external RPCDLL name 'CreateStubFromTypeInfo';


  • SPeller © (04.05.09 07:30) [1]
    Хм. Завел свое описание IRpcStubBuffer, в котором поправил метод GetBuffer на function GetBuffer(var message: TRpcOleMessage; const iid: TIID): HResult; stdcall; . Теперь доходит до TMyChannel.SendReceive, но при вызове Stub.Invoke - та же самая ошибка.
  • SPeller © (05.05.09 04:45) [2]
    Вот же блин. Из-под отладчика - ошибка. Запускал под идой - происходит AV: The memory could not be read. Без отладчика всё работает... Не хорошо так )
  • SPeller © (05.05.09 05:28) [3]
    Не, без отладчика работает только если фаза луны совпадает :) И при завершении приложения процесс остается висеть, загружая процессор на 100%
  • SPeller © (05.05.09 10:40) [4]
    Висло при выгрузке из-за того, что сразу после создания прокси/стаба делал
     TypInfo._Release;
     Lib._Release;


    Убрал - завершается нормально. Но проблема осталась.
  • SPeller © (05.05.09 10:45) [5]
    Надо было просто nil присваивать
  • SPeller © (05.05.09 12:03) [6]
    Что интересно:

     Msg: TRpcOleMessage;
     I: Integer;

     FillChar(Msg, SizeOf(Msg), 0);
     Msg.dataRepresentation := 16;
     Msg.Buffer := @I;
     Msg.cbBuffer := 4;
     Msg.iMethod := 7;
     I := 101;
     Stub.Invoke(Msg, Chan as IRpcChannelBuffer);



    Работает на ура. Отчего Stub.Invoke не пашет из TMyChannel.SendReceive?
  • Nfberegnyh (05.05.09 17:37) [7]
    Вы напрасно закомментировали
      //message.reserved1 := nil;
      //FillChar(message.reserved2, SizeOf(message.reserved2), 0);


    У меня в старом коде написано такое
     FillChar(message.reserved2, SizeOf(message.reserved2), 0);
    //  message.reserved2[3]:= pointer($100);   !!!!!!!!!


    Я уж не помню, по какому поводу поставил тут серию восклицательных знаков, но-что-то за этим явно скрывалось. Скорее всего после этого менялся адрес в AV :)

    Кроме того, переобъявить следует не только IRpcStubBuffer, но и IRpcChannelBuffer, и IRpcProxyBuffer. Во всех есть ошибки того-же плана. Вот, например,
    GetBuffer(var message: TRpcOleMessage; iid: TIID)


    Подчеркнутое должно быть с const.

    Кроме того, в объявлениях всех функций параметры интерфейсного типа следует делать с модификаторами. Например
    function CreateProxyFromTypeInfo(
        const pTypeInfo: ITypeInfo;
        const pUnkOuter: IUnknown;
        const iid: TGUID;
        out   pProxy: IRpcProxyBuffer;
        out   ppv): HRESULT; stdcall;

    function CreateStubFromTypeInfo(
        const pTypeInfo: ITypeInfo;
        const iid: TGUID;
        const pUnkServer: IUnknown;
        out   ppStub: IRpcStubBuffer): HRESULT; stdcall;


    Эти объявления точно правильные, потому как неоднократно проверены практически. Так-же следует делать и в методах интерфейсов.

    Кроме того, Прокси - объект аггрегируемый, аггрегатором ему служит Proxy Manager. Он ожидает, что ему будет передан валидный интерфейс контроллера. Как он поведёт себе, если, как у Вас, передать ему nil, я лично предсказать не берусь. Может в этом и есть основная причина ошибки.

    Это что заметил на первый взгляд. Может и ещеё что есть, но сначала устраните перечисленное.
  • SPeller © (06.05.09 02:12) [8]

    > Вы напрасно закомментировали

    Так и есть, ура! Заработало :)

    На счет модификаторов в параметрах интерфейсов. Проверял этот момент, просматривая скомпиленный код при вызове процедур с одним интерфейсным параметром, но в одном случае параметр описан как const, а в другом просто по значению. Так вот, вызов абсолютно идентичен! Разница лишь в коде вызываемой процедуры. В случае const не происходит ничего, в пустой процедуре ret и всё. А вот если по значению, то там компилятор заносит что-то в стек, а потом вытаскивает из него. Не вникал что именно там происходит. Для меня было важно что на код вызова наличие или отсутствие const в интерфейсном параметре не оказывает никакого влияния.
  • SPeller © (13.05.09 04:31) [9]
    Тут появилась другая проблема. Создал и зарегистрировал свою фабрику прокси/стаб. При работе внутри процесса теперь интерфейсы маршалируются и работают корректно. Но когда разнес клиента и сервера по разным приложениям, то вызов Stub.Invoke возвращается с ошибкой "Заглушке переданы неправильные данные". Суть кода прежняя, только возвращаемое значение контекста изменено на MSHCTX_CROSSCTX. Если передать MSHCTX_DIFFERENTMACHINE, то буфер, передаваемый в rpcolemessage, содержит сетевые данные, вызов Stub.Invoke вешается пока не завершится процесс клиента (клиент послал сетевой пакет серверу и ждет ответа), а сразу по завершении возвращается с результатом "Интерфейс не поддерживается". Подскажите, как бы мне так передать интерфейс на другой конец, чтобы там он был понят как надо?

    Делаю всё для того чтобы через свой канал маршалить ком-объекты без регистрации оных в реестре, но при наличии библиотеки типов, и чтобы работа с такими объектами не отличалась от обычной.
  • SPeller © (15.05.09 01:25) [10]
    Короче, разобрался с маршалингом и научил винду делать то что надо мне, а не ей :)
  • Slym © (15.05.09 06:13) [11]
    SPeller ©   (15.05.09 1:25) [10]
    если проблема решена, то тут приветствуется опубликовать правильное решение
  • SPeller © (15.05.09 06:57) [12]
    Код не приведу, поскольку он для работы. Но могу подсказать что нужно перехватывать вызовы API CoMarshalInterface и CoUnmarshalInterface чтобы писать в RPC пакет свои данные, а не вывешивать интерфейс RPC-сервером, как это делает стандартный маршалер. В результате нам для общения двух ком-интерфейсов не нужны никакие настройки безопасности, никакой информации об интерфейсах на этапе компиляции, мы не засоряем реестр никакими регистрациями. Нужна только доступная библиотека типов. Работа программиста по реализации нисколько не отличется. Естественно, что функции создания удаленных объектов - свои.
  • Nfberegnyh (16.05.09 12:16) [13]
    Жаль. Я надеялся. что Вы найдёте менее грязный способ :(

    > Нужна только доступная библиотека типов.

    Она тоже не обязательна.
  • speller © (16.05.09 12:33) [14]

    > Я надеялся. что Вы найдёте менее грязный способ :(

    Подскажите тогда более чистый
  • Nfberegnyh (16.05.09 14:03) [15]

    > Подскажите тогда более чистый

    Я его не знаю. Я же сказал - я надеялся, что Вы его найдёте :)
  • SPeller © (18.05.09 02:28) [16]
    Разобравшись в принципах маршалинга я пришел к выводу, что нужно либо ковырять руками стек удаленного вызова в соответствии с форматной строкой, которую можно вытащить из typelib-прокси/стаба, либо перехватывать системные вызовы маршалинга интерфейса и не заморачиваться форматом стека. Оба варианта в своей реализации не чище друг друга, только первый гораздо сложнее. Второй предпочтительней, поскольку не надо дублировать код, который уже есть в системе для удаленной передачи вызовов. Нужно было заменить один кирпичик вместо перекладки всей стены :)
  • SPeller © (18.05.09 02:38) [17]

    > Она тоже не обязательна

    Почему? Если ее нет, то кто и по каким правилам будет формировать пакет из стека удаленного вызова на клиенте, и восстанавливать этот стек на сервере?
  • SPeller © (18.05.09 04:21) [18]
    Еще добавлю про чистоту. Винда просто не предоставляет инструментов для ручного маршалинга интерфейсов без реализации оными IMarshal. Функции CreateХХХFromTypeInfo и те до сих пор недокументированы. Поэтому мощная и полезная штука для удаленного взаимодействия насильно завязана мелкософтом на свой RPC, в результате чего становится абсолютно непригодна на практике.

    Пэтому и "чистых" способов решения задачи, имхо, не существует.
 
Конференция "Corba" » Взаимодействие с COM через IRpcChannelBuffer [Delphi, Windows]
Есть новые Нет новых   [118640   +43][b:0][p:0.003]