Конвертировать __declspec в заголовке C в Delphi - PullRequest
3 голосов
/ 05 октября 2011

У меня проблемы с преобразованием класса из заголовка C для использования в Delphi.

Фрагмент объявления в заголовочном файле C выглядит следующим образом:

class __declspec(uuid("aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"))
ISomeInterface
{    
public:
  virtual
  BOOL
  SomeBoolMethod(
    VOID
  ) const = 0;
}

Я пишу DLL, которая экспортирует метод, который принимает параметр ISomeInterface, например,

function MyExportFunc (pSomeInterface: ISomeInterface): Cardinal; export; stdcall;
var
  aBool: BOOL;
begin
  aBool := pSomeInterface.SomeBoolMethod;
end;

Я объявил ISomeInterface в Delphi следующим образом:

type ISomeInterface = class
  function SomeBoolMethod: BOOL; cdecl; virtual; abstract;
end;

Вызов pSomeInterface.SomeBoolMethod приводит к нарушению доступа.

Я делаю что-то в корне неправильно?

Фактический заголовок C - httpserv.h, и я пытаюсь реализовать собственный модуль IIS7 в Delphi.

Некоторый код на С ++, который работает, выглядит следующим образом:

HRESULT
__stdcall
RegisterModule(
    DWORD                           dwServerVersion,
    IHttpModuleRegistrationInfo *   pModuleInfo,
    IHttpServer *                   pHttpServer
)
{
  // etc
}

При отладке я вижу, что параметр pModuleInfo содержит член __vfptr, который содержит 6 членов (с именами от [0] до [5] и имеют адреса в качестве значений), которые, как я вывел, являются указателями на виртуальные методы в классе IHttpModuleRegistrationInfo.

Экспорт в Delphi RegisterModule теперь выглядит следующим образом:

function RegisterModule (dwServerVersion: DWORD; var pModuleInfo: Pointer; var pHttpServer: Pointer): HRESULT; export; stdcall;
begin
  // etc
end;

pModuleInfo содержит эквивалентный адрес члену __vfptr в примере cpp, и, предполагая, что порядок в __vfptr такой же, как объявление класса в заголовочном файле, я извлекаю адреса методов:

function RegisterModule (dwServerVersion: DWORD; var pModuleInfo: Pointer; var pHttpServer: Pointer): HRESULT; export; stdcall;
var
  vfptr: Pointer;
  ptrGetName: Pointer;
  ptrGetId: Pointer;
begin
  vfptr := pModuleInfo;
  ptrGetName := Pointer (Pointer (Cardinal(vfptr))^);
  ptrGetId := Pointer (Pointer (Cardinal(vfptr) + 4)^);
end;

Теперь у меня есть адрес метода для вызова, так что теперь мне просто нужно как-то его вызвать. Я, наверное, все об этом пошла не так!

Ответы [ 3 ]

3 голосов
/ 05 октября 2011

Не объявлять как cdecl. Соглашение о вызовах для 32-битных COM-методов - это соглашение, которое C обозначает как stdcall, псевдонимы CALLBACK и WINAPI. Посмотрите, есть ли у Delphi эквивалентный. Если Delphi поддерживает COM, он есть.

Кроме того, убедитесь, что интерфейс C не является производным от IUnknown - практически все интерфейсы COM делают. если это так, выведите свой интерфейс из эквивалента Delphi.

Кроме того, я не уверен, что компоновка объекта в Delphi совпадает с компоновкой COM (таблица указателей виртуальных функций в качестве первого элемента данных). Снова найдите способ, которым Delphi реализует COM.

2 голосов
/ 05 октября 2011

В Delphi все классы происходят от TObject, независимо от того, указали вы это или нет.Это означает следующее объявление Delphi:

type
  ISomeInterface = class
    function SomeBoolMethod: BOOL; cdecl; virtual; abstract;
  end;

То же самое, что и это:

type
  ISomeInterface = class(TObject)
    function SomeBoolMethod: BOOL; cdecl; virtual; abstract;
  end;

Это делает VMT класса ISomeInterface в Delphi отличным от VMT *Класс 1009 * в C ++, который может привести к AV.

Аналогично, как уже упоминалось, объявление типа Delphi ISomeInterface как interface вместо class будет неявно выводить его из IUnknown, чего также не делает класс C ++.

Короче говоря, Delphi просто не может воспроизвести тип простых классов ванили, который может использовать C ++.Самое близкое, что вы можете получить в Delphi (без изменения кода C ++ для использования чего-либо, более совместимого с Delphi), - это объявить тип record вместо типа class или типа interface.Но затем вам нужно вручную воспроизвести VMT C ++ в Delphi, поскольку используется виртуальный метод, и вы должны объявить все методы, чтобы иметь явный параметр для указателя this (Self в Delphi).

В противном случае переключите код C ++ и код Delphi на использование интерфейсов COM вместо этого, что является двоичным стандартом, чтобы оба языка были совместимы друг с другом.

1 голос
/ 05 октября 2011

__declspec(uuid прикрепил UUID к определению класса, чтобы компилятор мог применить его где-то еще в коде, когда он запрашивается __uuidof оператором .

То есть, если вы портируете на Delphi, вы можете пропустить эту спецификацию в классе. Что касается интерфейсов, когда вы объявляете интерфейс с Delphi, у вас определенно есть шанс предоставить IID для этого определения.

Нарушение прав доступа в вашем коде, однако, не совсем связано с __declspec. Ваш C ++ ISomeInterface не совсем интерфейс, так как он не унаследован от IUnknown. IIRC, с Delphi вы просто не можете объявить интерфейс такого рода, все, что вы объявляете, должно быть получено как минимум из IUnknown. Таким образом, нет способа безопасно и легко преобразовать интерфейс (точнее, класс - так как он не является допустимым определением интерфейса COM).

Вот совпадение / преобразование, выполненное правильно:

MIDL_INTERFACE("56a86897-0ad4-11ce-b03a-0020af0ba770")
IReferenceClock : public IUnknown
{
public:
    virtual HRESULT STDMETHODCALLTYPE GetTime( 
        /* [out] */ REFERENCE_TIME *pTime) = 0;
// ...

И его близнец Дельфи из http://code.google.com/p/dspack/source/browse/trunk/src/DirectX9/DirectSound.pas#456

type
  IReferenceClock = interface(IUnknown)
    ['{56a86897-0ad4-11ce-b03a-0020af0ba770}']
    // IReferenceClock methods
    function GetTime(out pTime: TReferenceTime): HResult; stdcall;
// ...
...