Delphi TControls are hwnds, too


Borland devcon starts Tuesday, so with that in mind I figured what can be more fun than putting WPF inside a Delphi application?  Turns out this is both really easy and really difficult.  The easy part is that hwnds are hwnds are hwnds, so Delphi hwnds get along just fine with WPF HwndSource.  There’s more than one way to do this in Delphi, but the quickest and easiest was grabbed the Delphi form’s handle and pass it to HwndSource:

  dialog_global := TForm1.Create(nil);
Result := dialog_global.Handle;

(An alternate and more elegant approach would have been to create a subclass of TControl that overrides CreateWnd, and create the HwndSource from there.  But if you don’t need to manipulate the HwndSource as a TControl, what I did works fine)


The hard part was, Delphi doesn’t yet support .Net 2.0, which WPF requires.  I’m sure Borland will fix that in a future version, although for now it definitely makes life harder!  But I wasn’t to be denied, so I decided to use the unmanaged version of Delphi (“Delphi for Win32” as opposed to “Delphi for the .Net Framework”), together with a separate assembly written in a language that supports .Net 2.0.  The easiest way to make this happen was using a Delphi DLL together with a managed .exe, because I couldn’t figure out a way to write a DLL that unmanaged Delphi would be willing to call.  (I tried writing a dll in managed C++ with unmanaged entry points, but Delphi identified the dll as managed code and refused to touch it because of the CLR version)  My Delphi dll exported two functions — one that returns the form’s hwnd, and a second that displayed the form.  All the managed code .exe has to do is call the first export, pass that hwnd to HwndSource, and then call the second export.


The Delphi part looked like this:

var
dialog_global: TForm1;

function Setup(): HWND; stdcall;
begin
dialog_global := TForm1.Create(nil);
Result := dialog_global.Handle;
end;

function ShowIt(child:HWND): Integer; stdcall;
begin
dialog_global.ShowModal();
Result := 0;
end;

exports
Setup, ShowIt;


And the managed code? 

HWND dialog = Setup();
HWND clock = ManagedCode::GetHwnd(dialog, 263, 72, 186, 183);
ShowIt(clock);

Where GetHwnd creates an HwndSource and instantiates the WPF content of your choice.  Not bad for a Sunday afternoon!


Comments (4)

  1. PatriotB says:

    I’m not familiar with Delphi, but could you have written an unmanaged Delphi EXE and talked to WPF via COM Interop?

  2. Dan says:

    I’m an experienced Delphi developer who understood all your other articles about HWND Interop.

    However, from this article I don’t understand many things:

    – Which API functions do I need to call from Delphi?

    – How they are declared? How are the related data structures declared?

    – In which DLL are they?

    – How can I handle from Delphi an event that happens in the Avalon interface (ie. I click a WPF button)?

  3. Dan says:

    I have read the article several times with great care. I find it to be a strange and backward way of doing things.

    If I have a complex Delphi app, I certainly don’t want to compile this as a DLL. I want it to be an "unmanaged" native code .EXE which calls .NET or whatever the interop makes available to display the WPF content.

    Thank you.

  4. Yeah, that was my first choice about how to structure the EXE vs DLL, Delphi support for .Net 2.0 will give quite a bit more flexibility here.