3

为了学习多线程,我在 COM Thread ( TRemoteDataModule) 中创建了一个线程。

这是我的组件工厂:

TComponentFactory.Create(ComServer, TServerConn2, Class_ServerConn2, ciMultiInstance, tmApartment);

在线程内部,我不需要调用 CoInitialize 来使用TADOQuery.Create.Open....Exec

我读到我需要在调用除 CoGetMalloc 之外的任何库函数之前在线程上初始化 COM 库,以获取指向标准分配器和内存分配函数的指针。

但是在这种情况下,没有 CoInitialize 并没有给我带来任何麻烦。
这与线程模型有关吗?我在哪里可以找到这个主题的解释?

更新:

当我说 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) 中创建。虽然,TADOConnection.OpenTADOQuery.Create/.Open都在里面进行TThread.Execute

更新 3 - 拟像

界面:

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;

以某种方式(因为TComponentFactory可能?)CoCreateInstance识别出TWorker它在相同的上下文中TServerConn2而不是引发错误?

4

3 回答 3

5

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

  1. 在未使用 COM 初始化的线程上,所有现有接口指针都会继续工作,直到您进行 COM API 调用或以其他方式需要 COM 编组,然后无法检测到未初始化的线程。也就是说,您的“没有给我带来任何麻烦”实际上可能言之过早。

  2. 如果进程中的任何线程使用 COINIT_MULTITHREADED 标志调用 CoInitialize[Ex],那么这不仅会将当前线程初始化为多线程单元的成员,而且还会说:“任何从未调用过 CoInitialize[Ex] 的线程也是多线程单元的一部分。” - 所谓的隐式 MTA 事物

于 2013-08-09T14:13:41.013 回答
4

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

这行不通,原因有两个:

  1. TWorker.Create()并将TWorker.Execute()在不同的线程上下文中运行。Create()将在正在调用的线程的上下文中运行TServerConn2.CheckException()(它已经CoInitialize/Ex()事先调用了自身),但Execute()将在TThread线程的上下文中运行。ADO 是单元线程的,这意味着它的 COM 接口不能跨线程/单元边界使用,除非您通过IGlobalInterfaceTable接口或 CoMarshalInterThreadInterfaceInStream()andCoGetInterfaceAndReleaseStream()函数对它们进行编组。

  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;
于 2013-08-09T16:34:37.243 回答
1

当您使用它创建一个单元线程时,它会为您TComponentFactory调用- 它就在 VCL 源代码 ( ) 中:CoInitializeCoUnInitializeSystem.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;
于 2013-08-09T13:44:02.020 回答