Search code examples
c++delphivisual-c++dlldelphi-2009

Writing a Delphi DLL linked in from C++ Application: Access to C++ Interface Member Functions creates Access Violation


I need to write a DLL (in Delphi 2009) that is to be linked into a third party application written in MS VC++. The concept is very much that of a plugin system, which means that the application runs perfectly well without the DLL, and loads it when it is present.

Upon certain events, the application calls functions that the DLL exports. Documentation has a list of defined functions, and the so called SDK provides some sample code, of course also in C++. I do not have access to the source code of the application itself.

Following is a somewhat lengthy introduction together with some code examples. The question (that will be asked again at the bottom of this post) is: How do I have to implement the applications C++ classes, passed as pointers to interfaces, in the Delphi DLL? I've already read a couple of threads on stackoverflow and other sources, but most of them deal with modifying both ends (C++ applicaton and Delphi DLL), which is not an option. So, what I'm looking for is someone who can assist in translating the C++ DLL code to Delphi DLL code.

The called functions typically receive some parameters (mostly TCHAR*, int and some ENUMs) and have a return value of type HRESULT. Some of them do also have a parameter that is described as a pointer to a "COM-like interface", intended to make it possible to call member functions defined inside the application. During translation, I have simply prepended the types with a 'T' and declared the correspondind types in a seperate unit (TCHAR* becomes PTCHAR and is defined as PTCHAR = PAnsiChar. That makes it easy to replace the types, should it prove necessary).

Currently, the DLL functions are already called by the application and the code in the DLL has full access to the "standard" parameters like Strings or Integers. Return values are passed back to the application, so reckon that the implementation of the export functions is correct. On of the shorter examples (that works in the Delphi implementation):

// C++ function defined in the SDK
DLLAPI int FPHOOK_OnStartFlowInstance(const TCHAR* strSvcAppName, 
           const TCHAR* strAppName,
           const FLOW_SECTION_TYPE eSectionType,
           IIFlowContext* pContext)
{
 return 0;
}


// Delphi translation of the same function
function FPHOOK_OnStartFlowInstance( const strSvcAppName : PTCHAR;
                                     const strAppName : PTCHAR;
                                     const eSectionType : TFLOW_SECTION_TYPE;
                                     pContext : PIIFlowContext) : Int; stdcall;
begin
  dbg('ENTER FPHOOK_OnStartFlowInstance: strSvcAppName = ''%s'', strAppName = ''%s''',[String(strSvcAppName),String(strAppName)]);
  result := 0;
end;

Now, the problem is that I need to call one of the member functions. Here is the definition of the class (C++) resp. the interface (Delphi). I've left out most of the functions just to save space, but will gladly provide more source code if helpful.

// (shortened) class definition from C++
class IIFlowContext : virtual public CIUnknown
{
   // Operation
   public:
   virtual HRESULT getContextID(/*[out]*/unsigned short* pContextId) = 0;
   virtual HRESULT cleanExecutionState() = 0;
   /* skipped some other 'virtual HRESULT ...' */
};

// (shortened) interface declaration from Delphi
type IIFlowContext = Interface(IUnknown)
       function getContextID(pContextId : Punsigned_short) : HRESULT; stdcall;
       function cleanExecutionState : HRESULT; stdcall;
       // skipped some other 'function ...'
end;

If I now try to access one of the member functions:

function FPHOOK_OnStartFlowInstance( ...,pContext : PIIFlowContext) : Int; stdcall; 
var fphookResult : HRESULT;
begin
  try
    fphookResult := pContext.cleanExecutionState;
  except On E: Exception do
    dbg('FPHOOK_OnStartFlowInstance, pContext.cleanExecutionState: %s::%s',[E.ClassName,E.Message]);
  end;
  result := 0;
end;

an EAccessViolation error is caught by the except block and written to the debug log. I've already tried different conventions (not sure if 'convention' is the correct term here) like cdecl or safecall instead of stdcall, all with the same result.

This is where I currently have no clue at all where to look at... I've never been a C++ (or even C) programmer, so my translation to Delphi might well be wrong. Maybe there's some other point I'm missing.

Anyway, I'd be glad if someone with a little (or much) more experience would give me some hints.

Thanks in advance

Patrick

// 2010-11-05: What I extracted from the comments, the answers and the comments to the answers

Remko's suggestion to define the parameter as

var pContext : IIFlowContext;

gives almost the same outcome as my initial attempt as

pContext : PIIFlowContext;

The exception ist thrown in both cases, but the content of the variable is different. More information is given below where I've listed the different test cases.

Barry mentioned that Interfaces in Delphi (opposite to C++) already are pointers. While C++ therefore needs to pass a pointer to the class (aka pass as reference), Delphi already expects a reference to the class. The parameter should thus be declared as

pContext : IIFlowContext;

That is, not as a pointer to the interface, nor with a var modifier.

I ran the following three test cases, all of which had a debug break point at the very first instruction on the function exported by the dll:

1) declare the parameter as a pointer to the interface

pContext : PIIFlowContext;

Outcome: According to the debugger, pContext contains a pointer to memory address $EF83B8. Calling one of the interfaces methods leads to a jump to memory address $560004C2t and throws an EAccessViolation exception.

2) declare the parameter as a reference to the Interface

var pContext : IIFlowContext;

Outcome: The debugger shows the content of pContext as "Pointer($4592DC) as IIFlowContext". Calling the interfaces method leads to a jump to the same memory address $560004C2, which then throws the same execption.

3) declare the parameter as the Interface itself (without modifier)

pContext : IIFlowContext;

Outcome: The exported dll function does not even get called. An EAccessViolation is thrown (and caught by the debugger) before the jump into the dll function occurs.

From the above I conclude that it shouldn't be to much of a difference wether the parameter is declared as var pContext : IIFlowContext or pContext : PIIFlowContext, but it is a noticable difference if it's declared as pContext : IIFlowContext.

As requested, here's the output of the debuggers dissasembly view. In the comments I've noted the values of the registers after the execution of the operation to their left:

SystemHook.pas.180: fcnRslt := pContext.cleanExecutionState;
028A3065 8B4514           mov eax,[ebp+$14]        // EAX now = $00EF83D0
028A3068 8B00             mov eax,[eax]            // EAX now = $004592DC
028A306A 50               push eax
028A306B 8B00             mov eax,[eax]            // EAX now = $0041DE86
028A306D FF5010           call dword ptr [eax+$10]     // <-- Throws Exception, EAX+$10 contains $560004C2
028A3070 59               pop ecx
028A3071 8BD8             mov ebx,eax

The disassembly is exactly the same, no matter wether the parameter is a pointer to the interface or a var reference.

Is there anything else I should provide?

One additional question that came to my mind...

In the original header file from the SDK, the class is defined as

class IIFlowContext : virtual public CIUnknown

CIUnknown, in turn, is defined in another header file (win_unknown.h) as

class CIUnknown
{
//  Operation
public:
    virtual HRESULT QueryInterface(REFIID iid, void ** ppvObject) = 0;
    virtual unsigned long AddRef(void) = 0;
    virtual unsigned long Release(void) = 0;
    static bool IsEqualIID(REFIID iid1, REFIID iid2)
    {
        if (memcmp(&iid1, &iid2, sizeof(IID)) == 0)
            return true;

        return false;
    }
};

Is it OK to use IUnknown as base for the Delphi interfaces? I guess not, because as far as I know, IUnknown does not implement IsEqualIID and so there would be a shift in the VMT. But, how would I implement this in Delphi? Is C++ static the same as Delphi class function?

// 2010-11-18: Some Updates

Unfortunately, I've not yet found a way to get it working. One thing that indeed changed the behaviour was passing the interface reference as

const pContext : IIFlowContext;

As Barry stated, this inhibits delphi from "automagically" calling _AddRef() on the interface. This way, I was able to initiate and debug a call to member functions of the Interface. Now I can follow the execution quite some time and can even see some calls into the windows API (e.g. CriticalSections), but at some time it still throws an EAccessViolation Error.

Currently I have no further ideas. I think I will try to get hands on a MSVC++ compiler so that I can build the DLL like recommended by the SDK. If that works then maybe using C++ to create a wrapper around by Delphi code will be the solution.

Anyway, thanks a lot for your help so far! Any additional input will be very much appreciated, though.


Solution

  • Based on your most recent comment, I think I know what's going on; and I should have spotted it sooner.

    IIFlowContext on the C++ side is a class; IIFlowContext* pContext is passing a pointer to the class, which is how COM-style interfaces are represented in C++.

    But Delphi interfaces are already pointers; the indirection is assumed, as Delphi classes are never passed around by value, like C++ classes are. You should use IIFlowContext directly, no var or const modifier, in the Delphi entrypoint.

    There may still be a problem with the interface method declaration; it'll be clearer with more info: see my latest comment to your question.