为了学习multithreading,我在COM线程( TRemoteDataModule
)中创build了一个线程。
这是我的组件工厂:
TComponentFactory.Create(ComServer, TServerConn2, Class_ServerConn2, ciMultiInstance, tmApartment);
在线程内部,我不需要调用CoInitialize来使用TADOQuery.Create
, .Open
… .Exec
我读过, 我需要在线程上初始化COM库,然后再调用除CoGetMalloc之外的任何库函数,以获得指向标准分配器的指针和内存分配函数。
但在这种情况下,CoInitialize的缺席并没有带来任何麻烦。
这与线程模型有关吗? 我在哪里可以find这个问题的解释?
更新:
当我说INSIDE时,它意味着在COM方法上下文中:
interface type TWorker = class(TThread); TServerConn2 = class(TRemoteDataModule, IServerConn2) public procedure Method(); safecall; end; implementation procedure TServerConn2.Method(); var W: TWorker; begin W := TWorkerTread.Create(Self); end;
更新2:
用于连接数据库的TADOConnection
当前正在COM线程上下文( TThread.Create constructor
)中创build。 虽然TADOConnection.Open
和TADOQuery.Create/.Open
都在TThread.Execute
中执行。
更新3 – Simulacrum
接口:
type TServerConn2 = class; TWorker = class(TThread) private FDB: TADOConnection; FOwner: TServerConn2; protected procedure Execute; override; public constructor Create(Owner: TServerConn2); destructor Destroy; override; end; TServerConn2 = class(TRemoteDataModule, IServerConn2) ADOConnection1: TADOConnection; procedure RemoteDataModuleCreate(Sender: TObject); private { Private declarations } protected class procedure UpdateRegistry(Register: Boolean; const ClassID, ProgID: string); override; procedure CheckException; safecall; public User, Pswd, Str: String; Ok: Boolean; end;
执行:
class procedure TServerConn2.UpdateRegistry(Register: Boolean; const ClassID, ProgID: string); begin if Register then begin inherited UpdateRegistry(Register, ClassID, ProgID); EnableSocketTransport(ClassID); EnableWebTransport(ClassID); end else begin DisableSocketTransport(ClassID); DisableWebTransport(ClassID); inherited UpdateRegistry(Register, ClassID, ProgID); end; end; { TWorker } constructor TWorker.Create(Owner: TServerConn2); begin inherited Create(False); FreeOnTerminate := True; FDB := TADOConnection.Create(nil); FOwner := Owner; end; destructor TWorker.Destroy; begin FDB.Free; FOwner.Ok := True; inherited; end; procedure TWorker.Execute; var Qry: TADOQuery; begin FDB.LoginPrompt := False; FDB.ConnectionString := FOwner.Str; FDB.Open(FOwner.User, FOwner.Pswd); Qry := TADOQuery.Create(nil); try Qry.Connection := FDB; Qry.LockType := ltReadOnly; Qry.SQL.Text := 'SELECT TOP 1 * FROM MyTable'; Qry.Open; finally Qry.Free; end; end; procedure TServerConn2.CheckException; var W: TWorker; begin W := TWorker.Create(Self); while not Ok do Sleep(100); end; procedure TServerConn2.RemoteDataModuleCreate(Sender: TObject); begin User := 'user'; Pswd := 'pass'; Str := ADOConnection1.ConnectionString; end; initialization TComponentFactory.Create(ComServer, TServerConn2, Class_ServerConn2, ciMultiInstance, tmApartment); end.
更新4
错误应该在这里发生:
function CreateADOObject(const ClassID: TGUID): IUnknown; var Status: HResult; FPUControlWord: Word; begin asm FNSTCW FPUControlWord end; Status := CoCreateInstance(ClassID, nil, CLSCTX_INPROC_SERVER or CLSCTX_LOCAL_SERVER, IUnknown, Result); asm FNCLEX FLDCW FPUControlWord end; if (Status = REGDB_E_CLASSNOTREG) then raise Exception.CreateRes(@SADOCreateError) else OleCheck(Status); end;
通过某种方式(因为TWorker
也许?) CoCreateInstance
标识TWorker
是在与TServerConn2
相同的上下文中,不会引发错误?
以下任何一个或两个可能适用:
在未用COM初始化的线程上,所有现有的接口指针都会继续工作,直到您进行COM API调用或者需要COM编组,然后检测到未初始化的线程时失败。 也就是说,你的“没有带来什么麻烦”现在可能还言之过早。
如果进程中的任何线程使用COINIT_MULTITHREADED标志调用CoInitialize [Ex],那么不仅会将当前线程初始化为多线程单元的成员,而且还会说“任何从未调用过CoInitialize [Ex]也是多线程公寓的一部分。“ – 所谓的MTP的不足之处
用于连接数据库的TADOConnection当前正在COM线程上下文(TThread.Create构造函数)中创建。 虽然TADOConnection.Open和TADOQuery.Create / .Open都在TThread.Execute中执行。
这是行不通的,原因有二:
TWorker.Create()
和TWorker.Execute()
将在不同的线程上下文中运行。 Create()
将在调用TserverConn2.CheckException()
的线程的上下文中运行CoInitialize/Ex()
它预先已经在本身调用了CoInitialize/Ex()
),但是Execute()
会在TThread
线程的上下文中运行。 ADO是单元线程,这意味着它的COM接口不能跨越线程/单元边界使用,除非通过IGlobalInterfaceTable
接口或CoMarshalInterThreadInterfaceInStream()
和CoGetInterfaceAndReleaseStream()
函数对CoGetInterfaceAndReleaseStream()
。
即使你编组ADO接口, TWorker.Execute()
必须调用CoInitialize/Ex()
。 每个单独的线程必须初始化COM,然后在访问任何COM接口之前建立它的线程模型。 线程模型决定了COM如何访问接口(直接或通过代理),是否使用消息队列等。
所以对你的问题的简单解决方案是不要跨线程边界创建和使用ADO组件。 将您的TADOConnection
移到Execute()
:
constructor TWorker.Create(Owner: TserverConn2); begin inherited Create(False); FreeOnTerminate := True; FOwner := Owner; end; destructor TWorker.Destroy; begin FOwner.Ok := True; inherited; end; procedure TWorker.Execute; var DB: TADOConnection; Qry: TADOQuery; begin CoInitialize; try DB := TADOConnection.Create(nil); try DB.LoginPrompt := False; DB.ConnectionString := FOwner.Str; DB.Open(FOwner.User, FOwner.Pswd); Qry := TADOQuery.Create(nil); try Qry.Connection := DB; Qry.LockType := ltReadOnly; Qry.SQL.Text := 'SELECT TOP 1 * FROM MyTable'; Qry.Open; finally Qry.Free; end; finally DB.Free; end; finally CoUninitialize; end; end;
当您使用CoUnInitialize
创建一个单元线程时,它CoUnInitialize
为您调用CoInitialize
和CoUnInitialize
– 它在VCL源代码( System.Win.VCLCom.pas
)中是正确的:
procedure TApartmentThread.Execute; var msg: TMsg; Unk: IUnknown; begin try CoInitialize(nil); // *** HERE try FCreateResult := FFactory.CreateInstanceLic(FUnkOuter, nil, FIID, '', Unk); FUnkOuter := nil; FFactory := nil; if FCreateResult = S_OK then CoMarshalInterThreadInterfaceInStream(FIID, Unk, IStream(FStream)); ReleaseSemaphore(FSemaphore, 1, nil); if FCreateResult = S_OK then while GetMessage(msg, 0, 0, 0) do begin DispatchMessage(msg); Unk._AddRef; if Unk._Release = 1 then break; end; finally Unk := nil; CoUninitialize; // ** AND HERE end; except { No exceptions should go unhandled } end; end;