在上一节中,我们看到这个示例实在是太简单了,实际在作业处理过程中,稍微复杂一点的作业,我们都需要传递参数给作业的处理函数。那么怎么传递参数给作业呢?
在 Post 函数的声明中,我们看到有一个 AData 参数,是一个无类型的指针,我们实际上就是通过它来作业传递参数。我们知道,无类型指针实际上是一个整数来记录地址的值,在32位程序中,它是32位的,在64位程序中,它是64位的,那么,我们实际上,对于小于等于32的内容,是可以直接传值的,这样做的好处是不需要额外的释放步骤,而64位的内容则需要视我们的具体程序环境而定,具体可参考下表:
当然,这些可以直接传值的类型,我们也需要做一些简单的技巧性处理,如传递一个字符时,Delphi 中就可能需要用到 Ord 函数传它的内码,而传递一个整数类型的值时,我们就需要强制转换一个,Pointer(值)即可,而传递浮点值时,则需要更复杂一点的转换,您可以用 absolute 语法,也可以使用取地址转换的办法来完成。如下所示:
//整数类型 Pointer(100); //浮点类型-absolute var f:Single; i:Integer absolute f; begin ... f:=1.2; ... Pointer(i); ... end; //浮点类型-强转 Pointer(PInteger(@f)^);
当然,到了作业中相应的需要将Pointer反转换来就可以了,具体就不再缀述,省得大家认为我在骗字数。
上面的简单类型传递已经搞定了,那么复杂的类型传递我们怎么传呢?大家可能注意到了 Post 函数还有一个参数叫 AFreeType,它指定了 AData 指针关联的对象如何去释放。QWorker 默认提供了下面几种释放方式:
- jdfFreeByUser :用户自己负责,QWorker 可以当甩手掌握的
- jdfFreeAsObject:传递的是一个对象指针,QWorker 在作业生命周期结束时,调用对象的析构函数释放
- jdfFreeAsSimpleRecord:传递的是一个简单记录类型,QWorker 在作业生命周期结束时,直接调用Dispose(Pointer) 来释放,注意不要传递包含字符串类型/接口/动态数组等类型的成员,否则会造成内存泄露
- jdfFreeAsInterface:传递的是一个接口,QWorker 会自动调用 AddRef 和 Release 来维护其生命周期
- jdfFreeAsC1 ~ jdfFreeAsC6:传递的是一个用户自定义的内存释放方式,由用户自行指定 Workers.OnCustomFreeData 事件的响应函数来处理
看起来挺多的,但实际的业务需要可能更复杂,那么我们能不能进一步简化呢?万能的神龙,变变变~~~TQJobExtData登场了,AData 参数传递的是TQJobExtData这个类型的一个实例,然后由它提供了一层封装。当然了,因为 TQJobExtData 是一个类,所以 AFreeType 必需手动指定为 jdfFreeAsObject。我们看这个类的声明:
TQJobExtData = class private function GetAsBoolean: Boolean; function GetAsDouble: Double; function GetAsInteger: Integer; function GetAsString: QStringW; procedure SetAsBoolean(const Value: Boolean); procedure SetAsDouble(const Value: Double); procedure SetAsInteger(const Value: Integer); procedure SetAsString(const Value: QStringW); function GetAsDateTime: TDateTime; procedure SetAsDateTime(const Value: TDateTime); function GetAsInt64: Int64; procedure SetAsInt64(const Value: Int64); protected FOrigin: Pointer; FOnFree: TQExtFreeEvent; {$IFDEF UNICODE} FOnFreeA: TQExtFreeEventA; {$ENDIF} procedure DoFreeAsString(AData: Pointer); procedure DoSimpleTypeFree(AData: Pointer); {$IFNDEF NEXTGEN} function GetAsAnsiString: AnsiString; procedure SetAsAnsiString(const Value: AnsiString); procedure DoFreeAsAnsiString(AData: Pointer); {$ENDIF} public constructor Create(AData: Pointer; AOnFree: TQExtFreeEvent); overload; constructor Create(AOnInit: TQExtInitEvent; AOnFree: TQExtFreeEvent); overload; {$IFDEF UNICODE} constructor Create(AData: Pointer; AOnFree: TQExtFreeEventA); overload; constructor Create(AOnInit: TQExtInitEventA; AOnFree: TQExtFreeEventA); overload; {$ENDIF} constructor Create(const Value: Int64); overload; constructor Create(const Value: Integer); overload; constructor Create(const Value: Boolean); overload; constructor Create(const Value: Double); overload; constructor CreateAsDateTime(const Value: TDateTime); overload; constructor Create(const S: QStringW); overload; {$IFNDEF NEXTGEN} constructor Create(const S: AnsiString); overload; {$ENDIF} destructor Destroy; override; property Origin: Pointer read FOrigin; property AsString: QStringW read GetAsString write SetAsString; {$IFNDEF NEXTGEN} property AsAnsiString: AnsiString read GetAsAnsiString write SetAsAnsiString; {$ENDIF} property AsInteger: Integer read GetAsInteger write SetAsInteger; property AsInt64: Int64 read GetAsInt64 write SetAsInt64; property AsFloat: Double read GetAsDouble write SetAsDouble; property AsBoolean: Boolean read GetAsBoolean write SetAsBoolean; property AsDateTime: TDateTime read GetAsDateTime write SetAsDateTime; end;
它在这里提供了常用的类型的构造函数封装来简化操作,比如,我们要在上一节的例子中,传递一个要显示的字符串给 DoHelloworld 这个作业函数,我们就直接用下面的代码即可:
【Delphi】
Workers.Post(DoHelloworld,TQJobExtData.Create('字符串内容'),True,jdfFreeAsObject);
【C++ Builder】
Workers->Post(DoHelloworld,new TQJobExtData('字符串内容'),true,jdfFreeAsObject);
那么,我们在作业中,如何访问参数的值呢?大家可能已经注意到了对应的 AsXXX 属性,根据对应的类型,使用它即可直接访问了。当然,如果是你自定义的结构体或对象,那么只好老老实实的访问 Origin 指针来得到原始的内容了。
接下来的问题是:如果我们要传复杂的结构体给作业时怎么办?一个程序用到的结构体可能远不止6种,jdfFreeAsC1~jdfFreeAsC6 显然是无法满足需要的。在刚才的 TQJobExtData 声明中,大家可以看到,其包含了一个带有自定义释放函数的形式,实际上只要传递释放内存的代码给它就搞定了。
下面是一个简单的例子:
【Delphi】
type TMyComplexRecord=record Id:Integer; Name:String; Actions:array of TAction; end; PMyComplexRecord=^TMyComplexRecord; ... procedure TForm1.DoJob(AJob:PQJob); var R:PMyComplexRecord; begin R:=AJob.ExtData.Origin; OutputDebugString(PChar(R.Name)); end; procedure TForm1.DoFreeMyComplexRecord(AData:Pointer); begin Dispose(PMyComplexRecord(AData)); end; procedure TForm1.Button1Click(Sender:TObject); var AData:PMyComplexRecord; begin New(AData); AData.Name:='Hello'; SetLength(AData.Actions,1); Workers.Post(DoJob,TQJobExtData.Create(AData,DoFreeMyComplexRecord),false,jdfFreeAsObject); end;
【C++ Builder】
struct TMyComplexRecord { int Id; String Name; TAction Actions[1]; }; typedef TMyComplexRecord * PMyComplexRecord; ... void __fastcall TForm1::DoJob(PQJob AJob) { OutputDebugString(((PMyComplexRecord)AJob.ExtData.Origin)->Name.c_str()); } void __fastcall TForm1::DoFreeMyComplexRecord(void *AData) { delete (PMyComplexRecord)AData; } void __fastcall TForm1::Button1Click(System::TObject *Sender) { PMyComplexRecord AData=new TMyComplexRecord; AData->Name=L"Hello"; Workers->Post(DoJob,new TQJobExtData(AData,DoFreeMyComplexRecord),false,jdfFreeAsObject); }
好了,上面的一切看起来比较完美了。在结束之前,有的朋友可能说,你自动管理内容的释放虽然好,但是,我如果一个参数要在多个作业间顺序传递,你这个作业完成时就给释放了,害我不得不重建一个拷贝,这个不太好吧?
这是个问题,不过好在我们已经提供了比较完美的解决方案,那就是使用基于接口的智能指针。这本来是另一篇文章的内容,我给内容拷贝过来,比大家参考(原文链接 ):
用 QString 单元为您提供的 TQPtr 做智能指针封装,然后作业时使用 jdfFreeAsInterface 让 QWorker 采用接口的方式来管理附加的数据。
我们先看一下声明:
IQPtr = interface(IInterface) function Get: Pointer; end; TQPtrFreeEvent=procedure (AData:Pointer) of object; TQPtrFreeEventG=procedure (AData:Pointer); {$IFDEF UNICODE} TQPtrFreeEventA=reference to procedure (AData:Pointer); {$ENDIF} TQPtr = class(TInterfacedObject, IQPtr) private FObject: Pointer; FOnFree:TQPtrFreeEvent; {$IFDEF UNICODE} FOnFreeA:TQPtrFreeEventA; {$ENDIF} public constructor Create(AObject: Pointer); overload; destructor Destroy; override; class function Bind(AObject: TObject): IQPtr;overload; class function Bind(AData:Pointer;AOnFree:TQPtrFreeEvent):IQPtr;overload; class function Bind(AData:Pointer;AOnFree:TQPtrFreeEventG):IQPtr;overload; {$IFDEF UNICODE} class function Bind(AData:Pointer;AOnFree:TQPtrFreeEventA):IQPtr;overload; {$ENDIF} function Get: Pointer; end;我们会用到其中的 TQPtr.Bind 函数和 IQPtr 的 Get 接口:
(1)Bind 函数用于将对象或其它类型的指针通过接口的方式进行管理,它有四种重载,如果是对象,不需要指定释放方法,如果是其它的如记录类型,就需要自己指定内存释放函数,后面三个分别对应类成员、全局函数和匿名函数版本,不进行赘述。
(2)Get 函数用于获取到咱们管理的原始的指针。
下面我们提供一个具体的例子来看一下它的用法:
unit Unit1; interface uses Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs,QString,QWorker, Vcl.StdCtrls; type TMyObject=class public Text:String; destructor Destroy;override; end; TForm1 = class(TForm) Button1: TButton; procedure Button1Click(Sender: TObject); private { Private declarations } procedure DoStep1(AJob:PQJob); procedure DoStep2(AJob:PQJob); public { Public declarations } end; var Form1: TForm1; implementation {$R *.dfm} procedure TForm1.Button1Click(Sender: TObject); begin Workers.Post(DoStep1,Pointer(TQPtr.Bind(TMyObject.Create)),False,jdfFreeAsInterface); end; procedure TForm1.DoStep1(AJob: PQJob); var AObj:TMyObject; begin AObj:=IQPtr(AJob.Data).Get; AObj.Text:='Hello,world from DoStep1'; Workers.Post(DoStep2,AJob.Data,True,jdfFreeAsInterface); end; procedure TForm1.DoStep2(AJob: PQJob); var AObj:TMyObject; begin AObj:=IQPtr(AJob.Data).Get; ShowMessage(AObj.Text); end; { TMyObject } destructor TMyObject.Destroy; begin OutputDebugString('Object Free'); inherited; end; end.上面的这段代码我们投寄了一个两步作业,第一步作业设置了 TMyObject 的一个实例的 Text 值,而第二步作业则将其用 ShowMessage 显示出来,我们没有自己释放 TMyObject 实例,但我们执行上面的代码,我们可以看到结果,截个图看下:
可以看到,在后续步骤中,参数被安全的访问,而TMyObject对象的实例被自动释放了。
好了,看起来一切都很完美。传参数的问题解决了,也不需要做太多额外的工作。但我还是想补充一点点,那就是实际上,如果需要,你可以通过 TQMsgPack 或者 TList 一类的对象来传递参数,反正只要指定了 jdfFreeAsObject,在作业生命周期结束时,QWorker 都会帮你自动释放它。所以,本章的内容,只是给的参考,并不代表必需这么做,条条大路通罗马,何必单恋一枝花~~~,词有点串了:),本节内容结束。