views:

533

answers:

4

Hello,
i have some trouble accessing a dll written in vc++ that exports an interface. First i tried to use classes, but after some google-search i came to the solution, that this i not possible. I just want to make sure, that the plugin interface can accessed, by using other languages like c++.

Delphi Interface

IPlugIn = interface
  function GetName: WideString; stdcall;
end;

Delphi Plugin call

procedure TForm1.Button5Click(Sender: TObject);
var
  hLib: Cardinal; 
  MLoadPlugIn: TLoadPlugIn;
  PlugIn: IPlugIn;
begin
  hLib := LoadLibrary('PluginB.dll');
  try
    if not(hLib = 0) then
    begin
      @MLoadPlugIn := GetProcAddress(hLib, 'LoadPlugIn');
      if not(@MLoadPlugIn = nil) then
      begin
        if MLoadPlugIn(PlugIn) then
          try
            ShowMessage(PlugIn.GetName); // here i get the access-violation using the vc++ plugin
          finally                        // i get the return value but the instance is not created
            PlugIn := nil;
          end;
      end
      else
        raise Exception.Create('');
    end;
  finally
    FreeLibrary(hLib);
  end;
end;

Delphi plugin dll

  TMyPlugin = class(TInterfacedObject, IPlugIn)
  public
    function GetName: WideString; stdcall;
  end;

function TMyPlugin.GetName;
begin
  result := 'TMyPlugin';
end;

function LoadPlugIn(var PlugIn: IPlugIn): Boolean; stdcall;
begin
  try
    PlugIn := TMyPlugin.Create;
    result := True;
  except
    result := False;
  end;
end;

exports
  LoadPlugIn;

vc++ plugin dll

// IPlugIn

__interface //__declspec(uuid("E44BB34F-D13F-42D7-9479-4C79AF5C0D1B"))
IPlugIn : public IUnknown
{
 void _stdcall GetName(BSTR* result);
};

// TMyPlugIn header

class TMyPlugIn : public IPlugIn
{
public:
 // Constructor
 TMyPlugIn() : m_cRef(1) {}
 // Destructor
 ~TMyPlugIn() {}

 // Needed to implement IUnknown used by COM to acces your component
 HRESULT _stdcall QueryInterface(const IID& iid, void** ppv);
    ULONG _stdcall AddRef();
 ULONG _stdcall Release();

 void _stdcall GetName(BSTR* result);
private:
 long m_cRef ;
};

// TMyPlugIn cpp

HRESULT _stdcall TMyPlugIn::QueryInterface(const IID& iid, void** ppv)
{    
 if (iid == IID_IUnknown)
 {
  *ppv = static_cast<IPlugIn*>(this) ; 
 }
 else if (iid == IID_IPlugIn)
 {
  *ppv = static_cast<IPlugIn*>(this) ;
 }
 else
 {
  *ppv = NULL ;
  return E_NOINTERFACE ;
 }
 reinterpret_cast<IUnknown*>(*ppv)->AddRef() ;
 return S_OK ;
}

ULONG _stdcall TMyPlugIn::AddRef()
{
 return InterlockedIncrement(&m_cRef) ;
}

ULONG _stdcall TMyPlugIn::Release() 
{
 if (InterlockedDecrement(&m_cRef) == 0)
 {
  delete this ;
  return 0 ;
 }
 return m_cRef ;
}

void _stdcall TMyPlugIn::GetName(BSTR* result)
{
 string s1 = "PluginName";
 *result = A2WBSTR(s1.c_str());
}

// the export function from the cpp plugin

extern "C" bool __declspec(dllexport) __stdcall LoadPlugIn(IPlugIn* PlugIn);
bool __declspec(dllexport) __stdcall LoadPlugIn(IPlugIn* PlugIn)
{
 PlugIn = new TMyPlugIn;
 return TRUE; 
}
A: 

All Delphi classes parent - TObject class from VCL. If you use Borland C++ Builder(VCL library) - you can write plugin to Delphi with this way.

For another cases.. you should read about COM.

Adelf
+1  A: 

In general, you should not export interfaces (and for that matter: objects should really not be exported) across DLL boundaries because you do not know which memory manager, run-time library and object model will be on either side.

See also this thread about exceptions in DLL's (exceptions are objects).

Since the Delphi interface model is binary compatible with the COM interface model, and Visual C++ can export COM objects, you should go the COM way (was Adelf also suggested).

--jeroen

Jeroen Pluimers
Why would you not be able to export interfaces from DLLs? You write yourself that Delphi interfaces are compatible to COM interfaces, and the C++ plugin in the question implements `IUnknown`, so what's the problem?
mghie
That's why I wrote "in general". If the default IUnknown implementation in VC++ is COM compatible, then you might be able to export it and use it in Delphi. But then you still have two memory managers, and need to be very careful they don't fight, and make sure the AddRef/Release count matches.
Jeroen Pluimers
`IUnknown` *is* the foundation of COM, I don't think there realistically can be an incompatible one. Ref-counted interfaces are intended to tie the allocation and deallocation of a given chunk of memory to the same memory manager, no worries there either. Using a C++ interface-based plugin in a Delphi application should work really well, because the reference counting is handled automagically by the Delphi RTL.
mghie
The problem is the 'intend' and 'should'. Hence my 'in general'. I've seen people do this in practice, and fail horribly because they didn't understand what is working under the hood. For the simple case in this particular question it will work, but it takes a lot of care to keep it working in a more complex environment (threading, interfaces referencing other interfaces, etc). I've adapted my answer from 'cannot' to 'should not'.
Jeroen Pluimers
I think using COM is like using a sledge-hammer to crack a nut. I just want to build an easy plugin system, which don't depends on Delphi library's.
geskill
@geskill: Either way is a sledge hammer approach. You need to choose between two bad things here: mixing two run-times and languages and work around any oddities, or depend on the Delphi libraries. Sorry :-)
Jeroen Pluimers
+2  A: 

You get the access violation because this code

extern "C" bool __declspec(dllexport) __stdcall LoadPlugIn(IPlugIn* PlugIn);
bool __declspec(dllexport) __stdcall LoadPlugIn(IPlugIn* PlugIn)
{
 PlugIn = new TMyPlugIn;
 return TRUE; 
}

creates an instance of your plugin class and writes the address to the stack, where it quickly will be forgotten. Back in the Delphi program the original plugin interface variable is still nil, so calling a method on it crashes. You need to mimic what QueryInterface() does, like so:

extern "C" bool __declspec(dllexport) __stdcall LoadPlugIn(IPlugIn** PlugIn);
bool __declspec(dllexport) __stdcall LoadPlugIn(IPlugIn** PlugIn)
{
  *PlugIn = new TMyPlugIn;
  return TRUE; 
}

This passes the address of the interface variable, and the address of the plugin instance will be written to the variable.

mghie
It works! Thank you very very much!!I've heard about one "*" to get the address - but as you already said the solution was in the QueryInterface ;)
geskill
+1  A: 

In addition to what mghie has said, you also have a problem with mismatched definitions between Delphi and C++

Your C++ signature for GetName is:

 void _stdcall GetName(BSTR* result);

Your Delphi signature is:

  function GetName: WideString; stdcall;

There are (at least) 2 possible ways to fix this.

1) If you want the Delphi code to work as a function, then make it safecall and adjust the C++ to match:

Delphi:

  function GetName: WideString; safecall;

C++:

  HRESULT _stdcall GetName(BSTR* result);

or

2) fix the Delphi to match the existing C++ defn:

  procedure GetName( var name: WideString );

I (personally) would probably go the safecall route, as I think it is much cleaner on the Delphi side...

Alistair Ward
Your are right, also a small mistake - Thanksusing the safecall calling convention looks even nicer :)
geskill