How to Host WPF Controls in a Delphi Application and Delphi Controls in a WPF Application

In this post, I will be showing how you can host a WPF UserControl in a Delphi application and a Delphi VCL TFrame in a WPF application. To achieve this, we will use a Type Library. A Type Library is going to allow our C# code to talk to our Delphi code, and vice-versa. Let’s start with hosting a Delphi VCL TFrame in WPF.

Part I: Hosting Delphi in WPF

Firstly, create a new project in Delphi, under File > New > Other… expand Delphi and select Windows. Choose ActiveX Library. It will provide you with a *.ridl editor right away. Modify this IDL right away by defining your class as follows:

  • Give your top-level object in the tree view a unique name such as DelphiCOMService.
  • Right click DelphiCOMService and select New > Interface. This will allow us to define our COM interface which we will be able to use from C#.
  • Give your interface a unique name such as IDelphiCOMService. Make it have a Parent Interface of IUnknown. There are different parent interfaces you can choose; IUnknown is the most light-weight for our purposes.
  • Right click the interface and add a couple of new methods. One will be called GetDelphiFrame, the other WindowResized.
  • Set up their parameters by modifying their Parameters tab.
  • GetDelphiFrame will return a void* as its Return Type. It will take two parameters, a width and a height parameter with each of them having an [in] modifier, both of which will be a double. It will be called to instantiate our VCL TFrame and return back a pointer to it for consumption in WPF.
  • WindowResized will be the same except its Return Type will be an HRESULT. It will be called when the WPF window is resized and we need to resize our VCL TFrame.
  • Right click DelphiCOMService and select New > CoClass. Name it DelphiCOMServiceImplementation. Give it a unique GUID such as {D448873F-EAF7-4F40-8BC7-EF9853E64A0F}. Under the Implements tab, add the IDelphiCOMService interface. This tells Delphi what class we are going to eventually create inside of a new Delphi unit that will house our interface. Hence, this is exactly what a CoClass is, as per the name.

Create the following Delphi unit named DelphiCOMServiceUnit.pas:

unit DelphiCOMServiceUnit;

interface

uses SysUtils, ComObj, ComServ, DelphiCOMService_TLB, Winapi.ActiveX, StdVcl, Frame, Vcl.Forms, Winapi.Windows, Form;

type
  DelphiCOMServiceImplementation = class(TComObject, IDelphiCOMService)
  private
    MainFrame: TMainFrame;
    MainHandle: Cardinal;
    MainPointer: Pointer;
    MainForm: TMainForm;
  protected
    function GetDelphiFrame(width: Double; height: Double): Pointer; stdcall;
    procedure WindowResized(width: Double; height: Double); safecall;
  end;

implementation

function DelphiCOMServiceImplementation.GetDelphiFrame(width: Double; height: Double): Pointer; stdcall;
begin
  MainForm := TMainForm.Create(Application);
  MainForm.ClientWidth := Trunc(width);
  MainForm.ClientHeight := Trunc(height);
  MainHandle := MainForm.Frame.Handle;
  MainPointer := System.Pointer(MainHandle);
  Result := MainPointer;
end;

procedure DelphiCOMServiceImplementation.WindowResized(width: Double; height: Double); safecall;
begin
  MainForm.ClientWidth := Trunc(width);
  MainForm.ClientHeight := Trunc(height);
end;

initialization

TComObjectFactory.Create(ComServer, DelphiCOMServiceImplementation, StringToGUID('{D448873F-EAF7-4F40-8BC7-EF9853E64A0F}'), 'DelphiCOMServiceImplementation', '', ciMultiInstance, tmApartment);

end.

Add the following VCL Form to the project:

unit Form;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Frame;

type
  TMainForm = class(TForm)
    MainFrame: TMainFrame;
  private
    { Private declarations }
  public
    property Frame: TMainFrame read MainFrame;
  end;

var
  MainForm: TMainForm;

implementation

{$R *.dfm}

end.

Add the following VCL Frame to the project:

unit Frame;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes,
  Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;

type
  TMainFrame = class(TFrame)
    FrameLabel: TLabel;
  private
    { Private declarations }
  public
    { Public declarations }
  end;

implementation

{$R *.dfm}

end.
  • Add the VCL Frame to your VCL Form and name it MainFrame. Make sure it resizes with the size of the form. Add a TLabel to your VCL Frame named FrameLabel. Throw some text in it, make sure its centered inside of the frame, and that it resizes with the size of the frame.
  • Go back to your IDL. Hit Refresh Implementation, Register Type Library, and Save As Type Library File. Run the application in order to register the Delphi COM service.

What did we just do?

  • We created and registered a new COM service, which we can call from C#.
  • DelphiCOMServiceUnit houses the service.
  • DelphiCOMServiceUnit creates a new Delphi VCL Form and returns back a pointer to the VCL Frame it houses when GetDelphiFrame is called.
  • DelphiCOMServiceUnit resizes the Delphi VCL Form and thus the VCL Frame as appropriate when the WindowResized function is called.
  • DelphiCOMServiceUnit implements IUnknown, which is why it subclasses TComObject. It also uses TComObjectFactory.Create in order to tell Delphi to register this unit as our CoClass.
  • If DelphiCOMServiceUnit were to implement IDispatch, it should subclass TAutoObject and use TAutoObjectFactory.Create instead.

How do we use this new COM object?

  • Create a new WPF project.
  • Add a reference under the COM tab to this DelphiCOMService.
  • In MainWindow.xaml, add the following border:
<Border BorderThickness="0" Padding="0" Margin="0" x:Name="DelphiHostElement" SizeChanged="MainCanvas_OnSizeChanged"/>
  • Handle the Activated event in MainWindow.
  • Modify MainWindow.xaml.cs as follows:
using System;
using System.Runtime.InteropServices;
using System.Windows;
using DelphiCOMService;

namespace WPFApplication
{
    public partial class MainWindow : Window
    {

        [DllImport("msvcr110.dll", CallingConvention = CallingConvention.Cdecl)]
        public static extern int _fpreset();

        private DelphiCOMServiceImplementation service;

        public MainWindow()
        {
            _fpreset();
            InitializeComponent();
        }

        private void MainWindow_OnActivated(object sender, EventArgs e)
        {
            if (service != null)
                return;
            service = new DelphiCOMServiceImplementation();
            var control = service.GetDelphiFrame(DelphiHostElement.ActualWidth, DelphiHostElement.ActualHeight);
            DelphiHostElement.Child = new DelphiControlHost(DelphiHostElement.ActualWidth, DelphiHostElement.ActualHeight, control);
        }

        private void MainCanvas_OnSizeChanged(object sender, SizeChangedEventArgs e)
        {
            service?.WindowResized(DelphiHostElement.ActualWidth, DelphiHostElement.ActualHeight);
        }
    }
}

  • Add the following class in as DelphiControlHost.cs:
using System;
using System.Windows.Interop;
using System.Runtime.InteropServices;

namespace WPFApplication
{
    public class DelphiControlHost : HwndHost
    {

        [DllImport("user32.dll")]
        static extern IntPtr CreateWindowEx(int dwExStyle, string lpszClassName, string lpszWindowName, int style, int x, int y, int width, int height, IntPtr hWndParent, IntPtr hMenu, IntPtr hInstance, IntPtr lpParam);

        [DllImport("user32.dll")]
        static extern IntPtr SetParent(IntPtr hWndChild, IntPtr hWndNewParent);

        [DllImport("user32.dll")]
        static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);

        [DllImport("user32.dll")]
        static extern bool DestroyWindow(IntPtr hWnd);

        int height;
        int width;
        IntPtr child;

        public DelphiControlHost(double initialWidth, double initialHeight, IntPtr hostedControl)
        {
            width = (int)initialWidth;
            height = (int)initialHeight;
            child = hostedControl;
        }

        protected override HandleRef BuildWindowCore(HandleRef hwndParent)
        {
            var host = CreateWindowEx(0, "static", null, 0x40000000 | 0x10000000, 0, 0, height, width, hwndParent.Handle, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero);
            SetParent(child, host);
            ShowWindow(child, 5);
            return new HandleRef(this, host);
        }

        protected override void DestroyWindowCore(HandleRef hwnd)
        {
            DestroyWindow(hwnd.Handle);
        }
    }
}

What is happening here?

  • The border we added in is actually our Delphi VCL Frame host.
  • When our window activates, it gets a handle to the Delphi frame by calling our COM object.
  • We use the DelphiControlHost like a child canvas for which we create a native window.
  • The DelphiControlHost takes the Delphi frame handle and make it a child of its native window.
  • We then call ShowWindow to show our Delphi VCL Frame in this border.
  • The OnSizeChanged event handles any VCL Frame resizing we might need from Delphi.

Part II: Hosting WPF in Delphi

  • Create a new .NET Framework Class Library named WPFCOMService.
  • Right click the library and select Properties.
  • Under the Build tab, select Register for COM interop.
  • Add the following class named WPFCOMService.cs to this class library:
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Interop;
using System.Windows.Threading;
using WPFApplication;

namespace WPFCOMService
{
    [Guid("264A4D5A-C680-486A-B1DD-C265EFA1C201")]
    [ClassInterface(ClassInterfaceType.None)]
    [ComVisible(true)]
    public sealed class WPFCOMService : IWPFCOMService
    {

        [DllImport("user32.dll")]
        static extern void ShowWindow(IntPtr hWnd, int nCmdShow);

        static EmbeddedUserControl UserControl;
        static IntPtr UserControlPointer;
        static Dispatcher UserInterfaceThread;

        public void EmbedWPFWindow(IntPtr pointer, int width, int height)
        {
            Debugger.Launch();
            UserControlPointer = pointer;
            UserControl = new EmbeddedUserControl
            {
                Width = width, 
                Height = height
            };
            UserControl.InitializeComponent();
            var param = new HwndSourceParameters
            {
                WindowStyle = 0x40000000 | 0x04000000 | 0x02000000, 
                ParentWindow = UserControlPointer
            };
            var hWndSource = new HwndSource(param)
            {
                SizeToContent = SizeToContent.WidthAndHeight, 
                RootVisual = UserControl
            };
            ShowWindow(hWndSource.Handle, 5);
            UserInterfaceThread = Dispatcher.CurrentDispatcher;
        }

        public void WindowResized(int width, int height)
        {
            UserInterfaceThread?.Invoke(() =>
            {
                UserControl.Width = width;
                UserControl.Height = height;
            });
        }
    }

    [ComVisible(true)]
    [Guid("06AD88DA-7BC3-4D3B-98E4-B383C7C55E38")]
    public interface IWPFCOMService
    {
        void EmbedWPFWindow(IntPtr pointer, int width, int height);
        void WindowResized(int width, int height);
    }
}

  • When this project builds, it will register a COM object named WPFCOMService with the IWPFCOMService interface. The WPFCOMService.cs file houses our CoClass.
  • I will add a reference in this class library project to the WPF project I created before, but you can use any WPF project.
  • I added the EmbeddedUserControl, a simple user control to my referenced WPF project.
  • I use this control in my two C# COM object functions, in which I initialize and keep track of a WPF UserControl and its settings.
  • The EmbedWPFWindow function takes a pointer to a Delphi window and embeds the EmbeddedUserControl into this Delphi window by using HwndSourceParameters to set the Delphi window as its ParentWindow along with the appropriate child WindowStyle.
  • The WindowResized function takes a width and a height from Delphi and resizes the control as appropriate.

How do we use this COM object in Delphi?

  • Create a new VCL Form project in Delphi.
  • Go to the Component tab and select Import Component…
  • Select Import a Type Library.
  • Choose WPFCOMService (you should see it if you build the class library we created above).
  • Go to the Pascal code in your main form, Main.pas, and update it as follows to use our C# class library:
unit Main;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, WPFCOMService_TLB, Vcl.ExtCtrls;

type
  TMainForm = class(TForm)
    procedure FormActivate(Sender: TObject);
  private
    service: IWPFCOMService;
    thread: TThread;
    isMoving: Boolean;
    procedure WMEnterSizeMove(var Message: TMessage) ; message WM_ENTERSIZEMOVE;
    procedure WMMove(var Message: TMessage) ; message WM_MOVE;
    procedure WMExitSizeMove(var Message: TMessage) ; message WM_EXITSIZEMOVE;
    procedure PerformResize;
  public
    { Public declarations }
  end;

var
  MainForm: TMainForm;

implementation

{$R *.dfm}

procedure TMainForm.FormActivate(Sender: TObject);
begin
  if (service = nil) then
    service := CoWPFCOMService_.Create();
  service.EmbedWPFWindow(Handle, ClientWidth, ClientHeight);
end;

procedure TMainForm.WMEnterSizeMove(var Message: TMessage) ;
begin
  thread := TThread.CreateAnonymousThread(
    procedure
    begin
      while (isMoving) do
      begin
        PerformResize;
        Sleep(10);
      end;
      PerformResize;
    end
  );
  isMoving := True;
  thread.Start;
end;

procedure TMainForm.WMMove(var Message: TMessage) ;
begin
  PerformResize;
end;

procedure TMainForm.WMExitSizeMove(var Message: TMessage) ;
begin
  isMoving := False;
end;

procedure TMainForm.PerformResize;
begin
  if (service = nil) then
    exit;
  service.WindowResized(ClientWidth, ClientHeight);
end;

end.
  • Make sure to bind the Event named OnActivate of the main VCL Form to the FormActivate procedure.

Sample Projects

Finally, you can download the complete source code of my implementation. Note that, you use this code at your own risk and are entirely liable for any problems that may arise out of it, whatsoever. I do ask that if you use my code, you give me a little credit. Just throw my name in a comment somewhere. Here is the download link: Projects.zip.

Alexandru

"To avoid criticism, say nothing, do nothing, be nothing." - Aristotle

"It is wise to direct your anger towards problems - not people; to focus your energies on answers - not excuses." - William Arthur Ward

"Science does not know its debt to imagination." - Ralph Waldo Emerson

"Money was never a big motivation for me, except as a way to keep score. The real excitement is playing the game." - Donald Trump

"All our dreams can come true, if we have the courage to pursue them." - Walt Disney

"Mitch flashes back to a basketball game held in the Brandeis University gymnasium in 1979. The team is doing well and chants, 'We're number one!' Morrie stands and shouts, 'What's wrong with being number two?' The students fall silent." - Tuesdays with Morrie

I'm not entirely sure what makes me successful in general programming or development, but to any newcomers to this blood-sport, my best guess would be that success in programming comes from some strange combination of interest, persistence, patience, instincts (for example, someone might tell you that something can't be done, or that it can't be done a certain way, but you just know that can't be true, or you look at a piece of code and know something doesn't seem right with it at first glance, but you can't quite put your finger on it until you think it through some more), fearlessness of tinkering, and an ability to take advice because you should be humble. Its okay to be wrong or to have a bad approach, realize it, and try to find a better one, and even better to be wrong and find a better approach to solve something than to have had a bad approach to begin with. I hope that whatever fragments of information I sprinkle across here help those who hit the same roadblocks.

2 thoughts to “How to Host WPF Controls in a Delphi Application and Delphi Controls in a WPF Application”

  1. Thank you for the example you provided. You explained it well. I wanted to run wpf in delphi, I implemented your example.
    I get the error “external exception e0434352”. I guess it has to do with sizing the control. What could be the problem?
    Also, can we run wpf window in delphi?
    Thank you.

  2. I have followed your article and created a Delphi control with ActiveX Library and hosted it in WPF app using COM type library. I can see the Label and Delphi side background template colors loaded successfully in WPF but it’s not showing controls such as button, Datepicker, Checkbox. Only label text and background are rendered properly. Can anyone help if I am missing anything?

    Please check below StackOverflow post for detail

    https://stackoverflow.com/questions/73788100/hosting-of-delphi-controls-in-a-wpf-application-using-com-component-not-working

Leave a Reply

Your email address will not be published. Required fields are marked *