我需要将一个WinForms表单(with BorderStyle = None
)嵌入到Inno安装向导中并出现问题.
这是一个Inno安装脚本:
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;
这是一个C#代码:
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)); } }
它工作但不如预期.设置加载后,它会自动调用EmbedConfiguratorForm
从configurator.dll
和窗体显示但不进入设置向导页面.它显示在后面(见截图).那么我做错了什么?
解决了.
解决方案是从DLL返回新窗口(窗体)的句柄,并使用user32.SetParent
WinAPI函数强制将窗体嵌入到向导中.这是一段代码.
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安装脚本:
[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 'SetParent@user32.dll 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;
关于C#DLL的注意事项:
它使用UnmanagedExports
NuGet包(包含DLLExportAttribute
).
关于Inno Setup脚本的注意事项:
在InitializeWizard
函数中我们只需创建新页面,但我们需要实现DLL调用CurPageChanged
以确保我们的页面现在打开.
经过一些研究工作,我创建了一个小样本项目,解释了.Net和InnoSetup的双向整合
https://github.com/sharpcoder7/innoGlue.net