3

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!

petej
  • 31
  • 3
  • 2
    That class will be extremely difficult to convert to Delphi. First, it's not a real COM-style interface. Second, its method uses Microsoft's default calling convention for methods, which is thiscall; Delphi doesn't support that calling convention, so no Delphi code can call it. If you have any influence on the C++ code, get it changed to something compatible with the rest of the Windows API. – Rob Kennedy Oct 05 '11 at 04:30

3 Answers3

3

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.

Seva Alekseyev
  • 59,826
  • 25
  • 160
  • 281
2

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.

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • 1
    Though TObject class in Delphi has virtual methods, they all have negative offsets in VMT - TObject class uses compiler magic. I don't see why Delphi class cannot reproduce VMT of C++ class. – kludg Oct 05 '11 at 02:46
  • 1
    It is not the VMT alone. The methods must probably be called using MS-C++'s *__thiscall* convention. Delphi can't easily produce functions or methods that mimic it. It requires assembler, and makes the functions unusable from the Delphi side. – Rudy Velthuis Oct 05 '11 at 08:01
  • @Rudy - Yes, default `thiscall` is probably the reason for AV. Consuming `thiscall` on Delphi side is tricky - see http://stackoverflow.com/questions/2667925/pass-a-delphi-class-to-a-c-function-method-that-expects-a-class-with-thiscall . Now I see that the author of old question had problems with virtual destructor (which has negative offset in Delphi VMT, and should be 'thunked'), but there is no virtual destructor now. – kludg Oct 05 '11 at 09:00
  • @Serg: `__thiscall` is always a problem, as it is in fact `__cdecl` combined with Microsoft's version of `__fastcall`. None of the Delphi calling conventions matches that. Writing or consuming is both a problem. – Rudy Velthuis Oct 06 '11 at 06:28
1

__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;
// ...
Roman R.
  • 68,205
  • 6
  • 94
  • 158
  • How do you explain the AV? Code in Q looks fine according to Hallvard's article that you linked to? – David Heffernan Oct 04 '11 at 23:31
  • The C++ and Delphi interfaces in original post just cannot match, no chance at all. Let's look at class/interface virtual method table: in C++ class there is only one virtual method SomeBoolMethod (we can omit its convention for now). If Delphi's interface was declared as `interface` as you suggested, its 1st virtual method would be IUnknown::QueryInterface, and SomeBoolMethod would be 4th, which in C++ VMT just does not exist. Since OP declared the class as `class` in Delphi, the C++ and Delphi `class`es are incompatible by declaration too. So Access Violation is just guaranteed. – Roman R. Oct 05 '11 at 05:07
  • The way to make stuff compatible is to derive C++ class from `IUnknown`, on Delphi side to declare it as `interface`, then check/match convention and arguments. – Roman R. Oct 05 '11 at 05:10
  • @David: This is in fact an abstract C++ class, and depending on which compiler was used, the calling convention is none of the ones Delphi knows. It is e.g. MS-C++'s __thiscall, which requires a little assembler to make it callable from Delphi. The author of the DLL didn't think properly, which is not new. – Rudy Velthuis Oct 05 '11 at 06:47
  • @Roman: the most important part is that the functions must be declared `__stdcall`, `WINAPI` or some such, i.e. that they are not MS's own `__thiscall`. The VMT can easily be mimicked. – Rudy Velthuis Oct 05 '11 at 08:05
  • Many thanks for the replies. I've updated the question to include more info on what I'm trying to achieve and what I've found so far. – petej Oct 05 '11 at 15:31