DLL内线程同步主线程研究(子线程代码放到主线程执行)
DLL内线程同步主线程研究(子线程代码放到主线程执行)
我们在实际项目中经常会用到多线程编程,比如Socket编程等,在创建的线程内同步主线程一般使用Synchronize方法实现子线程操作放到主线程执行,Synchronize使用非常方便,且在2009及以上版本都可以使用匿名方法,这样给我们多线程带来了很大的便利。但是实践证明Synchronize只在主程序内正常工作。如果在主程序加载的DLL程序内运行使用Synchronize方法要求的条件比较苛刻,它要求必须把DLL程序拷挂到主程序,同时DLL内有窗体状态需为Modal或者主程序内窗体无一显示。具体见下:
主程序:
unit MainFrm;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
type
TMainForm = class(TForm)
btn1: TButton;
procedure btn1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
MainForm: TMainForm;
implementation
{$R *.dfm}
procedure TMainForm.btn1Click(Sender: TObject);
var
FHandle:THandle;
OpenDLLWindow:procedure(AMainHandle:Integer);stdcall;
begin
FHandle:=LoadLibrary(PChar('DLLPrj.dll'));
if FHandle>0 then
begin
OpenDLLWindow:= GetProcAddress(FHandle,PChar('OpenDLLWindow'));
if Assigned(OpenDLLWindow) then
begin
OpenDLLWindow(Application.Handle);
end;
end
else
ShowMessage('加载DLL失败!');
end;
end.
DLL程序:
unit DLLFrm;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs;
type
TDLLForm = class(TForm)
procedure FormShow(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
type
TDLLThread = class(TThread)
protected
procedure Execute; override;
end;
var
DLLForm: TDLLForm;
implementation
{$R *.dfm}
procedure TDLLThread.Execute;
var
Count:Integer;
tp:TThreadProcedure;
begin
Count:=0;
while True do
begin
try
Synchronize(
procedure
begin
ShowMessage(IntToStr(Count));
Count:=Count+2000;
end
);
except
// ignore error
end;
Sleep(2000);// 暂停两秒
end;
end;
procedure OpenDLLWindow(AMainHandle:Integer);stdcall;
begin
if DLLForm=nil then
DLLForm:=TDLLForm.Create(Application);
// 拷挂到主程序
Application.Handle:=AMainHandle;
DLLForm.ShowModal;
//DLLForm.Show;
end;
exports OpenDLLWindow;
procedure TDLLForm.FormShow(Sender: TObject);
var
DLLThread :TDLLThread;
begin
DLLThread:=TDLLThread.Create(False);
end;
end.
以上代码使用了Synchronize方法进行同步主线程,可以正常运行。但如果我们把DLL项目内方法OpenDLLWindow内去掉Application.Handle:=AMainHandle;或者把代码DLLForm.ShowModal;改为DLLForm.Show;这样线程同步主线程时将被阻塞。
这样看来如果如果在DLL项目内如果被主程序加载后窗体显示方式不是Modal的话我们将无法使用便利的Synchronize方法。这样我们使用SendMessage往主线程发送消息成为必须,如我们把DLL工程内代码更改如下:
unit DLLFrm;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs;
type
TDLLForm = class(TForm)
procedure FormShow(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
procedure WMRefreshForm(var Msg: TMessage); message WM_USER+100;
end;
type
TDLLThread = class(TThread)
protected
procedure Execute; override;
end;
var
DLLForm: TDLLForm;
implementation
{$R *.dfm}
procedure TDLLThread.Execute;
var
Count:Integer;
tp:TThreadProcedure;
begin
Count:=0;
while True do
begin
try
SendMessage(DLLForm.Handle, WM_USER+100, Count, 0);
Count:=Count+2000;
except
// ignore error
end;
Sleep(2000);
end;
end;
procedure OpenDLLWindow(AMainHandle:Integer);stdcall;
begin
if DLLForm=nil then
DLLForm:=TDLLForm.Create(Application);
// 拷挂到主程序
//Application.Handle:=AMainHandle;
//DLLForm.ShowModal;
DLLForm.Show;
end;
exports OpenDLLWindow;
procedure TDLLForm.FormShow(Sender: TObject);
var
DLLThread :TDLLThread;
begin
DLLThread:=TDLLThread.Create(False);
end;
procedure TDLLForm.WMRefreshForm(var Msg: TMessage);
begin
if Msg.Msg=WM_USER+100 then
begin
ShowMessage(IntToStr(Msg.WParam));
end;
end;
end.
这样我们的DLL程序就工作正常了,这也是目前的常用方法。但是如果我们在线程内有频繁的同步操作,或者这些同步操作会用到比较多的线程内变量,这样SendMessage就显得麻烦又吃力。如果我们在线程执行方法内能自定义匿名方法就像使用Synchronize那样的话我们的代码量将大大减少,且编程过程将大大简化,这样我们就想到了把匿名方法的指针作为SendMessage的一个参数传到自定义的消息内,然后在自定义消息内执行这个匿名方法。令我们庆幸的是这样是行的通的。如我们把DLL工程内代码修改如下:
unit DLLFrm;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs;
type
TDLLForm = class(TForm)
procedure FormShow(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
procedure WMRefreshForm(var Msg: TMessage); message WM_USER+100;
end;
type
TDLLThread = class(TThread)
protected
procedure Execute; override;
end;
var
DLLForm: TDLLForm;
implementation
{$R *.dfm}
procedure TDLLThread.Execute;
var
Count:Integer;
tp:TThreadProcedure;
begin
Count:=0;
while True do
begin
try
SendMessage(DLLForm.Handle, WM_USER+100,Integer(
@procedure
begin
ShowMessage(IntToStr(Count));
Count:=Count+2000;
end),0
);
// tp:= procedure
// begin
// ShowMessage(IntToStr(Count));
// Count:=Count+2000;
// end;
// SendMessage(DLLForm.Handle, WM_USER+100,Integer(@tp),0);
except
// ignore error
end;
Sleep(2000);
end;
end;
procedure OpenDLLWindow(AMainHandle:Integer);stdcall;
begin
if DLLForm=nil then
DLLForm:=TDLLForm.Create(Application);
// 拷挂到主程序
//Application.Handle:=AMainHandle;
//DLLForm.ShowModal;
DLLForm.Show;
end;
exports OpenDLLWindow;
procedure TDLLForm.FormShow(Sender: TObject);
var
DLLThread :TDLLThread;
begin
DLLThread:=TDLLThread.Create(False);
end;
procedure TDLLForm.WMRefreshForm(var Msg: TMessage);
begin
if Msg.Msg=WM_USER+100 then
begin
TThreadProcedure(Pointer(Msg.WParam)).Invoke;
end;
end;
end.
如果方法TDLLThread.Execute改为
procedure TDLLThread.Execute;
var
Count:Integer;
tp:TThreadProcedure;
begin
Count:=0;
while True do
begin
try
tp:= procedure
begin
ShowMessage(IntToStr(Count));
Count:=Count+2000;
end;
SendMessage(DLLForm.Handle, WM_USER+100,Integer(@tp),0);
except
// ignore error
end;
Sleep(2000);
end;
end;
那么TDLLForm.WMRefreshForm方法需改为:
procedure TDLLForm.WMRefreshForm(var Msg: TMessage);
begin
if Msg.Msg=WM_USER+100 then
begin
TThreadProcedure(Pointer(Msg.WParam)^).Invoke;
end;
end;
这点需要注意。
这样一来我们在DLL程序内只需要稍加修改就可以实现Synchronize一模一样的效果,尽情使用匿名方法给我们带来的方便,以上代码在Delphi2010内通过调试,分享给大家,不足之处往指教。
作者:张皓
2010-7-30