4

途中でCOMオブジェクトを作成するUIアプリケーションを開発しています。問題は、このCOMオブジェクトを完全に別のスレッドに「移動」したいということです。

私がしていることはこれです:

  • オブジェクトを移動したい新しいスレッドを作成します(CreateThread APIを使用)
  • このスレッドに入った後、PeekMessageを呼び出してメッセージキューを設定します
  • CoInitialize、CoCreateInstanceを呼び出してCOMオブジェクトを作成し、QueryInterfaceを呼び出して必要なインターフェイスを取得します
  • 最後に、GetCurrentThreadId()によって返された値を使用してMessageBoxを表示するインターフェイスのメソッドを呼び出します(オブジェクトが存在するCOMライブラリのVB6コードにアクセスできます)。

問題は、このメッセージボックスに示されているように、オブジェクトメソッドは、作成してこれらすべての手順を実行したスレッドではなく、元のUIスレッドで実行されることです。もう1つ言及すべきことは、インターフェイスメソッドを呼び出した後、クラシックメッセージループも設定していることです。

この動作を変更して、目的を達成するにはどうすればよいですか?(つまり、新しく作成したスレッドから発信されたCOMオブジェクト呼び出しを、元のアプリケーションスレッドではなく、IT上で実行する必要があります)

これをさらに明確にするためのいくつかの擬似コードを次に示します。

void myMainUIMethod(){
  MessageBox(GetCurrentThreadId()); // displays 1
  CreateThread(&myCOMObjectThreadProc);
}
void myCOMObjectThreadProc(){
  MessageBox(GetCurrentThreadId()); // displays 2
  CoInitialize(NULL);
  myObject = CoCreateInstance(myObjectsCLSID);
  myObjectInterface = myObject->QueryInterface(myObjectInterfaceCLSID);
  myObjectInterface->showThreadIDMessageBox(); // this would be the COM object method call
}

And, in the VB6 code of the object, here's the pseudo-definition of showThreadIDMessageBox.
Public Sub showThreadIDMessageBox()
  Call MessageBox(GetCurrentThreadId()) //displays 1, I want it to display 2
End Sub

新しいスレッドを作成する前に、メインスレッドでCoUninitalizingすることで、私が望んでいたことを達成しました。しかし、なぜこれが起こるのでしょうか?新しいスレッドを作成する前にCOMがメインスレッドで初期化された場合、おそらく何らかの理由でそれが必要でした。新しいスレッドを作成する前にCoUninitializeを呼び出さなければならなかったので、後でアプリケーションをクラッシュさせたくありません。これは、CoInitializeを最初に呼び出すスレッドが、STAオブジェクトによって選択されるスレッドになることを示すいくつかの擬似コードです。

void myMainUIMethod(){
  MessageBox(GetCurrentThreadId()); // displays 1
  CoUninitialize(); // uninitialize COM on the main thread
  CreateThread(&myCOMObjectThreadProc);
  ***i: MessageBox("When you want to initialize COM on main thread, confirm this");
  CoInitialize();
}
void myCOMObjectThreadProc(){
  MessageBox(GetCurrentThreadId()); // displays 2
  ***ii: MessageBox("When you want to initialize COM on the new thread, confirm this");
  CoInitialize(NULL);
  myObject = CoCreateInstance(myObjectsCLSID);
  myObjectInterface = myObject->QueryInterface(myObjectInterfaceCLSID);
  myObjectInterface->showThreadIDMessageBox(); // this shows 2 IF ***ii is confirmed before ***i, 1 otherwise
}

よろしくお願いします、Corneliu

4

5 に答える 5

6

問題は、COMコンポーネントのスレッドモデルがレジストリキーInprocServer32で指定されていないことのようです。これは、オブジェクトがSTA(シングルスレッドアパートメント)と見なされますが、オブジェクトを作成したSTAではなく、メイン(またはホスト)STAにロードされることを意味します。これは、を呼び出した最初のスレッドです。呼び出されたのと同じSTAで作成するには、レジストリ値を作成してに設定する必要があります。CoInitializeCoCreateInstanceHKEY_LOCAL_MACHINE\SOFTWARE\Classes\CLSID\{Your CLSID}\InprocServer32@ThreadingModelApartment

MSDNからの引用(InprocServer32レジストリキーのドキュメント):

ThreadingModelが存在しないか、値に設定されていない場合、サーバーはプロセスで初期化された最初のアパートメントにロードされます。このアパートメントは、メインシングルスレッドアパートメント(STA)と呼ばれることもあります。プロセスの最初のSTAが、CoInitializeまたはCoInitializeExの明示的な呼び出しではなく、COMによって初期化される場合、それはホストSTAと呼ばれます。たとえば、ロードするインプロセスサーバーにSTAが必要であるが、現在プロセスにSTAがない場合、COMはホストSTAを作成します。

于 2012-09-19T10:26:17.573 に答える
1

やっと自分のやりたいことが達成できました!新しいスレッドを作成する前に、メインUIスレッドにCoUninitialize呼び出しを追加すると、それが解決されます。これは、STACOMオブジェクトが最初にCoInitializeを呼び出すスレッドで処理されるために発生します。これで、オブジェクトメソッドへのすべての呼び出しが、作成したスレッドで実行されたと報告され、オブジェクトのメインウィンドウ(COMコンポーネントにはフォームがあります)もそれに属していると報告されます。(WinSpy ++を使用してテストしました)。

しかし、まだ質問(および問題)があります..なぜそれがこのように動作するのですか?インターネットで検索すると、STA COMコンポーネントが作成されたスレッドで完全に実行されるという回答が表示されます(CoInitializeまたはCoInitializeEx with COINIT_APARTMENTTHREADEDが以前に呼び出されていた場合)。以前に別のスレッドでCoInitializeを呼び出した場合、それが重要なのはなぜですか。Microsoftがそうすることは私の意見ではまったく愚かです:)さらに、前に述べたように、アプリケーションの将来の動作を損なう可能性があります。

編集:正解はFrostによって投稿されたものです。ありがとうございました。

于 2012-09-19T10:09:50.107 に答える
0

スレッドは並行して実行されており、それが目的です。1つのオブジェクトが他のスレッドでの操作が完了するのを待つようにする場合は、2つのスレッド間で同期する必要があります。イベントオブジェクトはあなたの目的のために役立ちます。

于 2012-09-19T01:19:36.910 に答える
0

COMクラスを作成するときは、COMクラスのスレッドモデルとしてフリースレッドを選択する必要があります。C ++ ATLでは、これは、[新規]-> [COMクラス] (またはそのようなもの)を選択した場合のウィザードのオプションです。.NET言語では、これはクラスの属性として指定されていると思います。

ところで、CoCreateInstanceの後にQueryInterfaceを呼び出す必要はありません(複数のインターフェイスポインターが必要な場合を除く)。必要なインターフェイスのGUIDを4番目のパラメーターとしてCoCreateInstanceに渡すだけです。

于 2012-09-19T06:19:26.100 に答える
0

ああ、私は今問題を知っているかもしれないと思います。あなたが作成しているVB6 COMオブジェクトは、アパートメントスレッドではなく、シングルスレッドとして登録されたようです。これは、アプリがCoInitialize()を最初に呼び出すスレッドでオブジェクトが作成されることを意味します。

これはあなたが見ている振る舞いを説明しています:最初にメインスレッドをCoInitialize()にすると、COMに関する限り、それは「メインスレッド」になるので、CoCreateは、CoCreatedであるにもかかわらず、オブジェクトを作成することになります。別のスレッド。(これは、シングルスレッドオブジェクトの場合のみです。)

ただし、他のスレッドを最初にCoInitialize()にすると、それはCOMの「メインスレッド」になるため、オブジェクトは必要な場所に作成されます。

VBオブジェクトのスレッドモデルをシングルではなくアパートメントに変更できますか?これにより、CoCreate()を呼び出すスレッドで作成できるようになります。

問題は、VB6コンポーネントのスレッドモデルを変更できないことです。これは、他のアプリケーションですでに使用されており、動作に損傷を与える可能性があるためです。

...それはあなたのために機能しないように見えます。現在のスレッドモデルが何であるかを確認でき、それが単一であることを確認できれば、なぜそれがそのように動作するのかについての説明があり、それはあなたがそれを扱うのに役立つかもしれません。

-

では、なぜCOMはそのように動作するのでしょうか。-A:レガシー互換性の問題。シングルスレッドモデルは、すべてのプロセスにスレッドが1つしかない場合に、ウィンドウにスレッドが最初に存在する前からの継承であり、コードはプロセス内のオブジェクト間の同期について何も想定する必要がありませんでした。この錯覚を維持し、シングルスレッドCOMを想定して作成されたオブジェクトをマルチスレッド環境で使用できるようにするために、COMは「レガシーSTA」とも呼ばれる「シングル」モデルを導入しました。このページの詳細、下にスクロールするか、「LegacySTA」を検索して詳細を確認してください。COMは基本的に、これらすべての「単一」オブジェクトを同じ[STA]スレッドに配置し、CoInitializeを最初に呼び出したスレッドを使用します。別のスレッドでCoUninitとCoInitを再度実行すると、基本的にCOMを再起動します。これで、新しい「CoInitを呼び出す最初のスレッド」である2番目のスレッドになります。そのため、COMはそのスレッドを使用することになります...

(レガシーSTAは非常に古い問題であり、実際には詳細を追跡するのは困難でした。他のほとんどすべての記事では、アパート、無料、および両方のオプションについて言及していますが、「シングル」についての詳細はめったにありません。)

于 2012-09-19T09:50:44.507 に答える