Title: Calling a C++ DLL which exports a class?
Question: As I stated in an earlier article, it's possible to get an
object-reference out from a DLL. This technique is known under
the name DLL+. But how about the DLL is written in c++?
What's about Name Mangling?
Answer:
First of all, you have to translate the header-file (should be delivered with the DLL), which is like an interface-section in ObjectPascal. Headers in c usually contain all sorts of definitions which are relevant outside the
module. In our c++ example it looks like:
/*FILE: income.h */
class CIncome
{
public:
virtual double __stdcall GetIncome( double aNetto ) = 0 ;
virtual void __stdcall SetRate( int aPercent, int aYear ) = 0 ;
virtual void __stdcall FreeObject() = 0 ;
} ;
the export definition works like this
extern "C" __declspec(dllexport) CIncome *CreateIncome();
CIncome *CreateIncome()
{
return new CIncomeImp ;
}
There are two ways to compile a function f1( ) in a DLL as exportable and then export it.
Flag the function f1( ) with the _export keyword.
Flag the function f1( ) with the __declspec(dllexport) keyword.
Then you translate it to an Abstract Class in a unit of her own:
//FILE: income.pas
interface
type
IIncome = class
public
function GetIncome(const aNetto: double): double;
virtual; stdcall; abstract;
procedure SetRate(const aPercent:Integer; aYear:integer);
virtual; stdcall; abstract;
procedure FreeObject; virtual; stdcall; abstract;
end;
In the c++ dll, there is a procedure FreeObject this is necessary
because of differences in memory management between C++ and ObjectPascal:
void __stdcall FreeObject()
{
delete this ;
}
When you call the DLL written in C or C++, you have to use
the stdcall or cdecl convention. Otherwise, you
will end up in violation troubles and from time to time the
application may crash. By the way the DLL, you are calling, should be
on the search path;).
So these conventions pass parameters from right to left. With
this convention, the caller (that's Delphi)has to remove the parameters
from the stack when the call returns.
At least the DLL-call in Delphi is simple, except read name mangling below:
incomeRef: IIncome; //member of the reference
function CreateIncome: IIncome;
stdcall; external('income_c.dll');
procedure TfrmIncome.FormCreate(Sender: TObject);
begin
incomeRef:=createIncome;
end;
procedure TfrmIncome.btncplusClick(Sender: TObject);
var
cIncome: Double;
begin
// this is the c++ dll+ call ;)
incomeRef.SetRate(strToInt(edtZins.text),
strToInt(edtJahre.text));
cIncome:= incomeRef.GetIncome(StrToFloat(edtBetrag.Text));
edtBetrag.text:= Format('%f',[cIncome]);
end;
Name Mangling Problems
---------------------------------------------------------
The cause of diff. problems is the not standardised "name mangling" of different compilers, which decorates the signature of an method to guarantee overloading. So the vc++ compiler (linker) puts some information about types and parameters on the entry point which the caller doesnt know.
in fact, it has been traditional, due to the changing nature of the defintion of C++, for major compiler releases to generate code incompatible with earlier releases from the same vendor.
The vendor would therefore change the name mangling to prevent linking incompatible code.o.
Decorated names were originally created to allow C++ to work with legacy linkers (that might not understand uppercase/lowercase, namespaces, class names, and overloading). In practice these "decorated names" are still around for reasons of compatibility with older versions of the same compiler. Note that there isn't any standard for how names get decorated, which is why it's nearly impossible to mix C++ object code from two different compilers.
So, what can you do?
1. you can hack the map file of the linker
http://groups.google.ch/groups?q=object+dll+c%2B%2B+mangling&hl=de&lr=&selm=1995Apr2.155609.28919%40emba.uvm.edu&rnum=9
So you could conceivably get around the problem by explicitly renaming Delphi export functions according to BC++'s name-mangling conventions, but this seems rather heavy handed and insecure.
2. You can work with an index instead of a name in a .def file(depends on your signature)
C++:
LIBRARY mxlump_dll
EXPORTS
FunctionName1 @1
FunctionName1 @2
ProcedureName1 @3
example:
c++:
EXPORTS
WEP @1
@Triple$qi @2 ; Triple(int)
Delphi:
EXPORTS
WEP @2
TRIPLE @1
3. Set an alias in the delphi external declaration:
function CreateIncome2: CIncome; stdcall; external 'income.dll'
name '_CreateIncome';
4. an easy way to test the dll is on the dos shell:
: rundll32 income.dll _SayHello2