Convert __declspec in C header to Delphi

1.7k views Asked by At

I'm having trouble converting a class from a C header to use in Delphi.

A snippet of the declaration in the C header file looks like this:

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

I'm writing a DLL which exports a method that accepts a ISomeInterface parameter, e.g.

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

I've declared ISomeInterface in Delphi like this:

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

Calling the pSomeInterface.SomeBoolMethod results in an access violation.

Am I doing something fundamentally wrong?

The actual C header is httpserv.h and I'm trying to implement an IIS7 native module in Delphi.

Some c++ code which works looks like this:

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

When debugging I see that the pModuleInfo parameter contains a __vfptr member which has 6 members under it (named [0] to [5] and have addresses as the values) which I deduce are pointers the virtual methods in the IHttpModuleRegistrationInfo class.

The Delphi RegisterModule export now looks like this:

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

pModuleInfo contains the equivalent address to the __vfptr member in the cpp example, and assuming the order in __vfptr is the same as the class declaration in the header file I extract the method addresses:

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;

I now have the method address to call, so now I just need to invoke it somehow. I am probably going about this all the wrong way though!

3

There are 3 answers

6
Roman Ryltsov On

__declspec(uuid attached an UUID to class definition so that compiler could apply it somewhere else in code when it is requested by __uuidof operator.

That is, if you are porting to Delphi, you might be able basically omit this specification from the class. As for interfaces, when you declare an interface with Delphi you definitely have a chance to provide an IID to this definition.

Access Violation in your code is however not quite related to __declspec. Your C++ ISomeInterface is not exactly an interface as it is not inherited from IUnknown. IIRC, with Delphi you just cannot declare an interface of this kind, whatever you declare has to be derived from at least IUnknown. So there is no way to safely and easily convert the interface (to be exact, the class - as it is not a valid COM interface definition).

Here is the match/conversion done correctly:

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

And its Delphi twin from 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;
// ...
6
Seva Alekseyev On

Don't declare as cdecl. The calling convention of 32-bit COM methods is the one that C refers to as stdcall, aliases CALLBACK and WINAPI. See if Delphi has an equivalent one. If Delphi supports COM, it has one.

Also, make sure that the C interface does not derive from IUnknown - practically all COM interfaces do. if it is, derive your interface from the Delphi equivalent.

Also, I'm not sure if the object layout in Delphi matches that of COM (a virtual function pointer table as the first data item). Again, find the way Delphi implements COM.

4
Remy Lebeau On

In Delphi, all classes derive from TObject whether you specify it or not. That means the following Delphi declaration:

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

Is the same as this:

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

That makes the VMT of the ISomeInterface class in Delphi different than the VMT of the ISomeInterface class in C++, which can lead to AVs.

Likewise, as others have mentioned, declaring the Delphi ISomeInterface type as an interface instead of a class will implicitally derive it from IUnknown, which the C++ class is not doing, either.

In short, Delphi simply cannot reproduce the type of plain vanilla class types that C++ can. The closest you can get in Delphi (without changing the C++ code to use something that is more compatible with Delphi) is to declare a record type instead of a class type or an interface type. But then you have to manually reproduce the C++ VMT in Delphi since a virtual method is being used, and you have to declare all methods to have an explicit parameter for the this (Self in Delphi) pointer.

Otherwise, switch both the C++ code and the Delphi code to use COM interfaces instead, which is a binary standard so both languages will be compatible with each other.