为什么我不需要在COM线程中创build的线程中调用CoInitialize?

为了学习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.OpenTADOQuery.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相同的上下文中,不会引发错误?

以下任何一个或两个可能适用:

  1. 在未用COM初始化的线程上,所有现有的接口指针都会继续工作,直到您进行COM API调用或者需要COM编组,然后检测到未初始化的线程时失败。 也就是说,你的“没有带来什么麻烦”现在可能还言之过早。

  2. 如果进程中的任何线程使用COINIT_MULTITHREADED标志调用CoInitialize [Ex],那么不仅会将当前线程初始化为多线程单元的成员,而且还会说“任何从未调用过CoInitialize [Ex]也是多线程公寓的一部分。“ – 所谓的MTP的不足之处

用于连接数据库的TADOConnection当前正在COM线程上下文(TThread.Create构造函数)中创建。 虽然TADOConnection.Open和TADOQuery.Create / .Open都在TThread.Execute中执行。

这是行不通的,原因有二:

  1. TWorker.Create()TWorker.Execute()将在不同的线程上下文中运行。 Create()将在调用TserverConn2.CheckException()的线程的上下文中运行CoInitialize/Ex()它预先已经在本身调用了CoInitialize/Ex() ),但是Execute()会在TThread线程的上下文中运行。 ADO是单元线程,这意味着它的COM接口不能跨越线程/单元边界使用,除非通过IGlobalInterfaceTable接口或CoMarshalInterThreadInterfaceInStream()CoGetInterfaceAndReleaseStream()函数对CoGetInterfaceAndReleaseStream()

  2. 即使你编组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为您调用CoInitializeCoUnInitialize – 它在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;