4
votes

I need to embed a WinForms form (with BorderStyle = None) into the Inno Setup Wizard and have an issue.

Here is an Inno Setup script:

procedure EmbedConfiguratorForm(parentWnd: HWND);
  external 'EmbedConfiguratorForm@files:configurator.dll stdcall';

procedure InitializeWizard();
var
  cfgPageHandle: HWND;
begin
  cfgPageHandle := CreateCustomPage(wpSelectDir, 
    'Configuration', 
    ExpandConstant(description)).Surface.Handle;
  EmbedConfiguratorForm(cfgPageHandle);
end;

Here is a C# code:

class WizardWindow : IWin32Window
{
    public WizardWindow(IntPtr handle)
    {
        Handle = handle;
    }

    public WizardWindow(int handle) : this(new IntPtr(handle))
    {
    }

    public IntPtr Handle { get; private set; }
}

public static class MainClass
{
    [DllExport("EmbedConfiguratorForm", CallingConvention.StdCall)]
    public static void EmbedConfiguratorForm(int parentWnd)
    {
        // System.Diagnostics.Debugger.Launch();
        ConfiguratorForm form = new ConfiguratorForm();
        form.Show(new WizardWindow(parentWnd));
    }
}

It works but not as expected. After setup loads, it automatically call EmbedConfiguratorForm from configurator.dll and the form shows but not into setup wizard page. It shows behind (see screenshot). So what am I doing wrong?

enter image description here

1
Your solution is the same as in the duplicate question, except that they pass a parent handle to the DLL, while you pass a child handle from the DLL.Martin Prikryl
Sure. Remark is winforms form has no Parent as handle (pointer) but only Form or Control. So the easiest way is to use SetParent winapi function. I think this will be then same where You will call SetParent from InnoSetup or from DLLAlexey Kulikov
You can use the NativeWindow to wrap handle, see stackoverflow.com/a/213751/850848Martin Prikryl
Have you try to use this (NativeWindow) by youself? Sorry but It not works in my issueAlexey Kulikov

1 Answers

5
votes

Solved.

The solution is to return a handle of new window (form) from DLL and use user32.SetParent WinAPI function to force embed the form into the wizard. Here a piece of code.

C#:

namespace configurator
{
    class WizardWindow : IWin32Window
    {
        public WizardWindow(IntPtr handle)
        {
            Handle = handle;
        }

        public WizardWindow(int handle) : this(new IntPtr(handle))
        {
        }

        public IntPtr Handle { get; private set; }
    }

    public static class MainClass
    {
        private static ConfiguratorForm _configuratorForm;

        [DllExport("EmbedConfiguratorForm", CallingConvention.StdCall)]
        public static IntPtr EmbedConfiguratorForm(int parentWnd)
        {
            _configuratorForm = new ConfiguratorForm();
            _configuratorForm.Show(new WizardWindow(parentWnd));
            return _configuratorForm.Handle;

        }

        [DllExport("CloseConfiguratorForm", CallingConvention.StdCall)]
        public static void CloseConfiguratorForm()
        {
            if (_configuratorForm != null)
            {
                _configuratorForm.Close();
                _configuratorForm.Dispose();
                _configuratorForm = null;
            }
        }
    }
}

Inno Setup script:

[Code]
const
  description = 'my page description';

var
  configFile: string;
  configuratorPage: TWizardPage;

function EmbedConfiguratorForm(parentWnd: HWND): HWND;
external 'EmbedConfiguratorForm@files:configurator.dll stdcall';

procedure CloseConfiguratorForm();
external 'CloseConfiguratorForm@files:configurator.dll stdcall';

function SetParent(hWndChild, hWndNewParent: HWND): HWND;
external '[email protected] stdcall';

procedure InitializeWizard();
begin
  configuratorPage := CreateCustomPage(wpSelectDir, 
    'Title', 'Description');
end;

procedure ShowConfigurationStep();
var
  cfgPageHandle: HWND;
  cfgWinHandle: HWND;
begin
  cfgPageHandle := configuratorPage.Surface.Handle;
  cfgWinHandle := EmbedConfiguratorForm(cfgPageHandle);
  SetParent(cfgWinHandle, cfgPageHandle);
end;

procedure CurPageChanged(CurPageId: Integer);
begin
  if (CurPageId = configuratorPage.ID) then
  begin
    ShowConfigurationStep();
  end else
  begin
    CloseConfiguratorForm(); // here we can make some optimization like checking previos page
  end;
end;

procedure DeinitializeSetup();
begin
  CloseConfiguratorForm();
end;

Note about C# DLL:
It uses UnmanagedExports NuGet packet (contains DLLExportAttribute).

Note about Inno Setup script:
In InitializeWizard function we need just create new page, but DLL call we need to implement into CurPageChanged to ensure our page is opened now.


After some research works I have created a little sample project explain Two-Way integration of .Net and InnoSetup

https://github.com/sharpcoder7/innoGlue.net