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

posted on 2010-07-29 00:52  张皓  阅读(2252)  评论(0编辑  收藏  举报

导航