Freigeben über


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

  • Anonymous
    November 07, 2005
    I'm not familiar with Delphi, but could you have written an unmanaged Delphi EXE and talked to WPF via COM Interop?
  • Anonymous
    March 21, 2006
    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)?
  • Anonymous
    March 21, 2006
    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.
  • Anonymous
    March 24, 2006
    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.