Delphi Clinic | C++Builder Gate | Training & Consultancy | Delphi Notes Weblog | Dr.Bob's Webshop |
|
Give me your number and I will call back
Remember the pension calculator of lost+found 9?
One of the features in this system are callbacks from a C++ backend into a Delphi frontend without using GetProcAddress or having to export entry points from the frontend executable.
The world is flat
Under Win32, memory, or I should say, address space is flat.
What that means is that a process including all the DLLs it loads are living together in one single linear address space.
Under Windows NT that address space runs from address 0x00000000 to address 0x7fffffff.
Beyond that, user processes fall off the world so to say.
More seriously, the addresses that run from 0x8000000 to 0xffffffff are kernel space addresses and are reserved for the operating system.
Proof
Oooooh.
You want proof.
Ok.
Just have a look at the event list when starting an executable.
One of the things you will see there is that the operating system is loading DLLs and other stuff for you.
What you also see is that some DLLs collide with others in terms of loading address.
They are subsequently loaded at the another address.
C++Builder DLL
The C++ side is actually not very difficult.
The only problem you might have is remembering the exact syntax for defining function pointers.
What we do is define a function type and a setter function that can be called from the outside:
//------------------------------------------------------------------- // header extern "C" { // Function pointer type typedef long (__stdcall *CallBackFuncType)(const char* message); // Setter function __declspec(dllexport) void SetCallBack(CallBackFuncType fun); } long CallBack(const char* message); //------------------------------------------------------------------- // code static CallBackFuncType cbfun = 0; void SetCallBack(CallBackFuncType fun) { CallBackFuncType oldfun = cbfun; cbfun = fun; return oldfun; }So far so good, nothing special. However, make sure you are doing the declarations in extern "C" {}, otherwise you will get mangled names and they do not work so good in Delphi.
Delphi EXE
One of the first things we need to do is define a function type that is exactly the same as our typedef above.
We use a function type for that:
type CallBackFuncType = function (mesg: PChar): Integer; stdcall;The next thing is writing the function that will be called back, declaring the SetCallBack function and binding it:
var procedure SetCallBack(cbfunc: CallBackFuncType); stdcall; function ReceiveCallBack(mesg: PChar): Integer; stdcall; begin ShowMessage(String(mesg)); end; function Initialize: Boolean; var succeeded: Boolean; begin @SetCallBack := GetProcAddress(DLLHandle, 'SetCallBack'); succeeded := Assigned(SetCallBack); if succeeded then begin SetCallBack(ReceiveCallBack); end; end;
Call them back!
Calling back is actually trivial, but we need to check for a nil function pointer and handle that.
Therefore we write a wrapper:
//------------------------------------------------------------------- // callback wrapper long CallBack(const char* message) { long result = 0; if (cbfun != 0) { result = cbfun(message); } else { cout << message << endl; } return result; }
Why do they pick up?
Once compiled there is no difference between the code that Delphi generates and the code that C++Builder generates.
They both generate i386 assembly.
The difference could lie in the fact that Delphi per default handles the stack in a different way than C++Builder.
However, this difference in calling convention is circumvented by specifying that both ends use the stdcall calling convention.
Furthermore, a function pointer is nothing more than the address in memory where a function starts. And since the world is flat, we can rely on the fact that that address is reachable from anywhere within the same process.