VCL/LCL – DLL 中的窗体 – 没有应用程序任务栏 window, 无法最小化主窗体
VCL/LCL – a form in DLL – no Application taskbar window, cannot minimize the main form
我有一个问题,我试图搜索解决方案但无法实现我想要的。抱歉,如果这真的很简单,请指出正确的操作方法。
所以!我有一个 C 程序,它是一个加载程序。它必须调用我用 Delphi 或 Lazarus (Free Pascal) 编写的 DLL。 DLL 实际上是一个独立的 GUI 应用程序:在调试期间,我有条件地将它编译为 EXE 并且它可以工作。
我的构建脚本将其编译为具有一个入口点的 DLL,该入口点必须像独立运行一样执行它。我期待完全相同的行为,但如果需要,我可以做一些不同的事情(尤其是设置应用程序图标)。
Loader 是一个 console-style 程序,但在没有控制台的情况下编译 – 没有 windows,没有任何东西。它只是加载 DLL 并调用一个函数。
问题是,当我使用一种形式构建一个空的默认项目作为 EXE – 它实际上会在任务栏中有 "master" Application (.Handle <> 0) window。所以我可以独立于主窗体标题设置它的标题。
但是当同样的东西在DLL里面时——没有Applicationwindow(.Handle=0),标题会是form caption,但是最重要的bug:一个form不能被最小化!
在 Delphi 7 中,它在其他 windows 下进入后台(但任务栏的东西仍然存在!);在 Lazarus 中,它只是最小化到无处(隐藏,无法再恢复);两者都没有任何最小化动画。
除此之外,我的应用程序似乎运行正常。这是我遇到的唯一问题。
好的,我知道图书馆中的表格是一件坏事,但是:
我很好地实例化 "another" VCL 完全独立于主机的实例,甚至可能在不同的线程中。
我的特定主机应用程序中没有 VCL!对我来说,它必须完全像在 EXE 中一样工作......
我在 DLL 中搜索了一些关于 Application.Handle 的内容,现在明白我需要将句柄传递给主机的应用程序 object,这样 DLL 将与其他主机形式结合,但我有 none!它甚至不是 Delphi...(和 Application:=TApplication.Create(nil);也没有帮助)
以下任何内容都可能对我有所帮助:
A) 如何指示 VCL 为我创建一个普通的应用程序 object?它在 EXE 中是如何工作的,也许我可以复制该代码?
B) 如何从 C(适当的样式等)创建一个合适的母版 window 以将其句柄传递给 DLL?另外,我相信在 Free Pascal 中不能直接访问 TApplication 句柄值,所以我可能无法分配它。
C) 如何在没有任务栏的情况下生活 window,但让我的表单(好消息:我的程序只有一种表单!)正确地最小化(或只是以某种方式......)?
我现在你们都喜欢看一些代码,所以这里是:
// default empty project code, produces valid working EXE:
program Project1;
uses Forms, Unit1 in 'Unit1.pas' {Form1};
{$R *.res}
begin
Application.Initialize;
Application.CreateForm(TForm1, Form1);
Application.Run;
end.
+
// that's how I tried to put it in a DLL:
library Project1;
uses Forms, Unit1 in 'Unit1.pas' {Form1};
{$R *.res}
function entry(a, b, c, d: Integer): Integer; stdcall;
begin
Application.Initialize;
Application.CreateForm(TForm1, Form1);
Application.Run;
Result := 0;
end;
exports
entry;
begin
end.
我特制的 entry() 函数可以用 运行dll32 调用,仅用于测试。
此外,我尝试将 body 直接放入“begin end.
”初始化部分——同样的错误行为。
// To call a DLL, this can be used:
program Project1;
function entry(a, b, c, d: Integer): Integer; stdcall; external 'Project1.dll';
begin
entry(0, 0, 0, 0);
end.
此外,CMD-command“rundll32 project1.dll entry
”会立即运行。 (是的,那样我可能会得到 Rundll 给我的句柄,但这不是我想要的。)
最后说明:(a) DLL 必须在 Lazarus 中编译;实际上,我首先认为这是 LCL 中的一个错误,但现在在 Delphi7 中进行测试时,我看到了同样的情况;由于 Delphi 案例更简单、更健壮,我决定把它放在这里; (b) 我的 C 加载程序不调用 LoadLibrary,它使用 TFakeDLL hack(该 OBJ 文件被调整为在没有 Delphi 包装器的情况下工作)并从内存加载我的 DLL(所以我没有 DLL 本身的句柄), 但除此之外它们的行为是相同的。
好的,感谢@Sertac Akyuz,我尝试使用 .ShowModal
:
// working Delphi solution:
library Project1;
uses Forms, Dialogs, SysUtils, Unit1 in 'Unit1.pas' {Form1};
{$R *.res}
function entry(a, b, c, d: Integer): Integer; stdcall;
begin
Result := 0;
Application.Initialize;
Form1 := TForm1.Create(nil);
try
Form1.ShowModal;
except
on e: Exception do
ShowMessage(e.message);
end;
Form1.Free;
end;
exports
entry;
begin
end.
仍然没有应用程序window(任务栏标题等于表单标题),但现在我的表单可以成功最小化(有系统动画)。请注意,对于 EXE 编译,我必须使用应用程序的默认方式,因为当我尝试创建这样的表单时 - 它开始最小化到桌面(图标化)而不是任务栏。
它在空的默认 Lazarus 项目中也能完美运行。但是当我试图将它实现到我的生产程序时,它在 .ShowModal!
处给了我 "Disk Full" 异常
早些时候那件事让我很沮丧(这就是为什么我完全摆脱模态,不再尝试),但现在我有足够的决心弄清楚这个问题。
我发现了问题!我的构建脚本没有传递“-WG
”("Specify graphic type application")编译器选项。看起来 LCL 中的某些东西正在使用 console-alike 模式,并且由于某种原因模态循环失败。
然后,我想分享另一个非常令人困惑的问题。我的窗体的 OnCreate 相当大且复杂(甚至启动其他线程),并且当我尝试使用窗体上的控件之一执行某些操作时,某些内部函数会给我访问冲突。看起来控件还没有构建,或者表单本身…
原来实际的调用Form1:=TForm1.Create(nil);
显然会在FormCreate事件中让全局变量"Form1"未赋值。解决方法很简单:在 TForm1.FormCreate(Sender: TObject);
的开头添加 Form1:=Self;
现在一切正常,没有任何问题。如果我首先将它们添加到我的 entry() 函数中,我什至可以使用具有正常 Form2.Show();
的其他形式,例如 Form2:=TForm2.Create(Form1);
(编辑:小提示,如果你想使用 Lazarus 并尝试从任何不同的线程而不是加载 DLL 库本身的线程 运行 entry() 函数——那么你应该把 MainThreadID:=GetCurrentThreadId();
放在以上 Application.Initialize;)
耶,这个问题解决了!
我有一个问题,我试图搜索解决方案但无法实现我想要的。抱歉,如果这真的很简单,请指出正确的操作方法。
所以!我有一个 C 程序,它是一个加载程序。它必须调用我用 Delphi 或 Lazarus (Free Pascal) 编写的 DLL。 DLL 实际上是一个独立的 GUI 应用程序:在调试期间,我有条件地将它编译为 EXE 并且它可以工作。
我的构建脚本将其编译为具有一个入口点的 DLL,该入口点必须像独立运行一样执行它。我期待完全相同的行为,但如果需要,我可以做一些不同的事情(尤其是设置应用程序图标)。
Loader 是一个 console-style 程序,但在没有控制台的情况下编译 – 没有 windows,没有任何东西。它只是加载 DLL 并调用一个函数。
问题是,当我使用一种形式构建一个空的默认项目作为 EXE – 它实际上会在任务栏中有 "master" Application (.Handle <> 0) window。所以我可以独立于主窗体标题设置它的标题。
但是当同样的东西在DLL里面时——没有Applicationwindow(.Handle=0),标题会是form caption,但是最重要的bug:一个form不能被最小化!
在 Delphi 7 中,它在其他 windows 下进入后台(但任务栏的东西仍然存在!);在 Lazarus 中,它只是最小化到无处(隐藏,无法再恢复);两者都没有任何最小化动画。
除此之外,我的应用程序似乎运行正常。这是我遇到的唯一问题。
好的,我知道图书馆中的表格是一件坏事,但是:
我很好地实例化 "another" VCL 完全独立于主机的实例,甚至可能在不同的线程中。
我的特定主机应用程序中没有 VCL!对我来说,它必须完全像在 EXE 中一样工作......
我在 DLL 中搜索了一些关于 Application.Handle 的内容,现在明白我需要将句柄传递给主机的应用程序 object,这样 DLL 将与其他主机形式结合,但我有 none!它甚至不是 Delphi...(和 Application:=TApplication.Create(nil);也没有帮助)
以下任何内容都可能对我有所帮助:
A) 如何指示 VCL 为我创建一个普通的应用程序 object?它在 EXE 中是如何工作的,也许我可以复制该代码?
B) 如何从 C(适当的样式等)创建一个合适的母版 window 以将其句柄传递给 DLL?另外,我相信在 Free Pascal 中不能直接访问 TApplication 句柄值,所以我可能无法分配它。
C) 如何在没有任务栏的情况下生活 window,但让我的表单(好消息:我的程序只有一种表单!)正确地最小化(或只是以某种方式......)?
我现在你们都喜欢看一些代码,所以这里是:
// default empty project code, produces valid working EXE:
program Project1;
uses Forms, Unit1 in 'Unit1.pas' {Form1};
{$R *.res}
begin
Application.Initialize;
Application.CreateForm(TForm1, Form1);
Application.Run;
end.
+
// that's how I tried to put it in a DLL:
library Project1;
uses Forms, Unit1 in 'Unit1.pas' {Form1};
{$R *.res}
function entry(a, b, c, d: Integer): Integer; stdcall;
begin
Application.Initialize;
Application.CreateForm(TForm1, Form1);
Application.Run;
Result := 0;
end;
exports
entry;
begin
end.
我特制的 entry() 函数可以用 运行dll32 调用,仅用于测试。
此外,我尝试将 body 直接放入“begin end.
”初始化部分——同样的错误行为。
// To call a DLL, this can be used:
program Project1;
function entry(a, b, c, d: Integer): Integer; stdcall; external 'Project1.dll';
begin
entry(0, 0, 0, 0);
end.
此外,CMD-command“rundll32 project1.dll entry
”会立即运行。 (是的,那样我可能会得到 Rundll 给我的句柄,但这不是我想要的。)
最后说明:(a) DLL 必须在 Lazarus 中编译;实际上,我首先认为这是 LCL 中的一个错误,但现在在 Delphi7 中进行测试时,我看到了同样的情况;由于 Delphi 案例更简单、更健壮,我决定把它放在这里; (b) 我的 C 加载程序不调用 LoadLibrary,它使用 TFakeDLL hack(该 OBJ 文件被调整为在没有 Delphi 包装器的情况下工作)并从内存加载我的 DLL(所以我没有 DLL 本身的句柄), 但除此之外它们的行为是相同的。
好的,感谢@Sertac Akyuz,我尝试使用 .ShowModal
:
// working Delphi solution:
library Project1;
uses Forms, Dialogs, SysUtils, Unit1 in 'Unit1.pas' {Form1};
{$R *.res}
function entry(a, b, c, d: Integer): Integer; stdcall;
begin
Result := 0;
Application.Initialize;
Form1 := TForm1.Create(nil);
try
Form1.ShowModal;
except
on e: Exception do
ShowMessage(e.message);
end;
Form1.Free;
end;
exports
entry;
begin
end.
仍然没有应用程序window(任务栏标题等于表单标题),但现在我的表单可以成功最小化(有系统动画)。请注意,对于 EXE 编译,我必须使用应用程序的默认方式,因为当我尝试创建这样的表单时 - 它开始最小化到桌面(图标化)而不是任务栏。
它在空的默认 Lazarus 项目中也能完美运行。但是当我试图将它实现到我的生产程序时,它在 .ShowModal!
处给了我 "Disk Full" 异常早些时候那件事让我很沮丧(这就是为什么我完全摆脱模态,不再尝试),但现在我有足够的决心弄清楚这个问题。
我发现了问题!我的构建脚本没有传递“-WG
”("Specify graphic type application")编译器选项。看起来 LCL 中的某些东西正在使用 console-alike 模式,并且由于某种原因模态循环失败。
然后,我想分享另一个非常令人困惑的问题。我的窗体的 OnCreate 相当大且复杂(甚至启动其他线程),并且当我尝试使用窗体上的控件之一执行某些操作时,某些内部函数会给我访问冲突。看起来控件还没有构建,或者表单本身…
原来实际的调用Form1:=TForm1.Create(nil);
显然会在FormCreate事件中让全局变量"Form1"未赋值。解决方法很简单:在 TForm1.FormCreate(Sender: TObject);
Form1:=Self;
现在一切正常,没有任何问题。如果我首先将它们添加到我的 entry() 函数中,我什至可以使用具有正常 Form2.Show();
的其他形式,例如 Form2:=TForm2.Create(Form1);
(编辑:小提示,如果你想使用 Lazarus 并尝试从任何不同的线程而不是加载 DLL 库本身的线程 运行 entry() 函数——那么你应该把 MainThreadID:=GetCurrentThreadId();
放在以上 Application.Initialize;)
耶,这个问题解决了!