3

マルチスレッドを学習するために、COM スレッド ( ) 内にスレッドを作成しました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識別しますか?TWorkerTServerConn2

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 内で実行されています。

次の 2 つの理由から、これは機能しません。

  1. TWorker.Create()TWorker.Execute()異なるスレッド コンテキストで実行されます。Create()呼び出しているスレッドのコンテキストで実行されます(事前にそれ自体TServerConn2.CheckException()が呼び出されています) が、代わりにスレッドのコンテキストで実行されます。ADO はアパートメント スレッドです。つまり、その COM インターフェイスは、インターフェイスまたは and関数を介してマーシャリングしない限り、スレッド/アパートメントの境界を越えて使用できません。CoInitialize/Ex()Execute()TThreadIGlobalInterfaceTableCoMarshalInterThreadInterfaceInStream()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;
于 2013-08-09T16:34:37.243 に答える
1

それを使用してアパートメント スレッドを作成すると、VCL ソース ( )に含まれTComponentFactoryます。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 に答える