Delphi: Calling a function from a vc++ dll that exports a interface / class
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.Button开发者_开发技巧5Click(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;
}
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.
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...
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
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.
精彩评论