Delphi Clinic | C++Builder Gate | Training & Consultancy | Delphi Notes Weblog | Dr.Bob's Webshop |
|
Living Apart Together
It seems to me that since I am working with C++Builder 5 and Delphi 5 I regularly have te reiterate old stuff I used to write.
I also notice while working with both development environments that they become more and more integrated.
It does not look that way, but it is certainly the case.
Introduction
I left you in lost+found 10 with a way to call back from a C++ DLL into a Delphi executable.
I also told you in lost+found 7 how to debug such a DLL in C++Builder using the setting for the host application.
Well, things have changed a bit.
Debugging DLLs
In fact, I was debugging a little test application created with Delphi 5 that called an entrypoint in a DLL built with C++Builder.
What actually happenen was that I only pressed the 'Jump Into' key one time too many.
I expected to see the CPU window, but instead was shown the C++ sourcefile instead.
This aroused my suspicion a bit.
I subsequently tried inspecting a few variables.
What happened was that I got a C++Builder Inspector while working in Delphi!
Once back in Delphi, the Inspector was the regular Delphi one again.
This can only mean one thing.
Either I have a very special setup in which a couple of lucky accidents happened, or this functionality was built into Delphi from the beginning.
Either way, it is a sign that the debugging symbol format of both Delphi 5 and C++Builder 5 are so similar that debugging C++ in Delphi is a possibility and I suspect that the reverse is also true.
[Since C++Builder can already compile Pascal code, I'd say that's indeed the case - Bob]
Method Callbacks
The other thing I discovered is that it is possible to call a method in a class for an object.
Does the term 'closure' mean anything to you?
The term surfaces when you read up on creating your own events and event handlers.
It actually is a pair of pointers.
The first one points to the address of the method in the class, the second one points to the object for which the method must be executed.
It should now be clear to you why closures are used in event handling.
What happens is that if you create a handler for some type of event, the event is associated with the closure that points to the OnXXX method in the particular object that handles that event.
I was inventing a callback scheme again.
The problem was to pass a possibly large number of strings.
The classic approach is to pass a buffer to a DLL and let the DLL fill that in.
The drawback is that you must enlarge the buffer if it is not large enough, and the resulting code on the calling side is quite complicated.
What if I could pass a callback to such a function in the backend that is called from there once for every string to be passed to the Delphi part?
Have a look at the C++ part:
typedef void __stdcall (*StringElementCallbackProcType)(const char* str); __declspec(dllexport) long __stdcall SendNamesToProc(StringElementCallbackProcType cb) { long result = BACKEND_ERROR; try { // Iterate over the vector of strings and call back // for each string in the vector vectorAnd now the Delphi part:sl = GetNameStrings(); for (vector ::const_iterator i = sl.begin(); i != sl.end(); ++i) { cb((*i).c_str()); // !!Callback to procedure!! } result = BACKEND_OK; } catch (exception& ex) { SendExceptionNotification(ex); } return result; }
type StringElementCallbackProcType = procedure(astr: PChar); stdcall; function SendNamesToProc(cb: StringElementCallbackProcType): Integer; stdcall; external thedll name 'SendNamesToProc';Pretty much boilerplate. But then I wanted to fill a listbox in a form class. Then I had to write a regular procedure that would be called back, and that procedure in turn would have to pass the strings to the listbox I wanted to fill. Too much trouble.
However, in C++ it is possible to call methods for a certain object, and why wouldn't that be the same for Delphi? Actually, this can be accomplished. It took some intense reading in the help file, but once I was on the track of closures, the solution was near. The Delphi way of declaring a method pointer is as follows:
type StringElementCallbackObjectType = procedure(astr: PChar) of object; stdcall;Notice the addition of 'of object'. Once I had that down, I had a little brainwave. Let's compile this piece of Delphi in the C++Builder IDE and have a good look at the header that is generated! It turned out to be that a method pointer declaration is translated into:
typedef void __stdcall (__closure *StringElementCallbackObjectType)(const char* str);Notice the addition of '__closure'! This is a Borland extension to accommodate VCL style classes in C++. Now the proverbial light began to shine! Basically, the backend function that does the callback is the same except for the different type of the cb-argument:
__declspec(dllexport) long __stdcall SendNamesToObject(StringElementCallbackObjectType cb) { long result = BACKEND_ERROR; try { // Iterate over the vector of strings and call back // for each string in the vector vectorIt's use in Delphi is as follows:sl = GetNameStrings(); for (vector ::const_iterator i = sl.begin(); i != sl.end(); ++i) { cb((*i).c_str());// !!Callback to object method!! } result = BACKEND_OK; } catch (exception& ex) { SendExceptionNotification(ex); } return result; }
type TMainForm = class(TForm) ListBoxNames: TListBox; procedure FormShow(Sender: TObject); procedure AddName(name: PChar); stdcall; end; function SendNamesToObject(cb: StringElementCallbackObjectType): Integer; stdcall; external thedll name 'SendNamesToObject'; ... procedure TMainForm.FormShow(Sender: TObject); begin SendNamesToObject(MainForm.AddName); end; procedure TMainForm.AddName(name: PChar); stdcall; begin ListBoxNames.Items.Add(name); end;
Conclusion
So what? Nice trick, but what is its use?
The first thing is that it spares you the trouble of having to write an intermediate function to add the strings to the listbox.
The next thing I realized was that this technique can be used to create some sort of Model-View-Controller pattern between a Delphi front-end and a C++Builder backend!
If two GUI-elements in the front-end refer to the same variable in the backend, keeping a list of closures of notification methods in the fronted is enough to be able to notify both GUI elements of a change.
Just wrap the variable in a class that also keeps a list of closures.
Let the GUI elements pass a closure of the notification method to that class.
If you change the variable, just use each closure to call the notification method for each object!
The Delphi frontend is the View, the variable wrapper the Controller, and the variable itself the Model.