VSTOでアドイン内でマルチスレッドする際に色々死んだのでメモ。 色々試行錯誤した為、私が解決する際は a -> b -> c という実装を組み込んでいきましたが、もしかしたら b は要らないかもしれない…? いや…うーん…… シーンによっては必要かも知れない…? ** 何が問題か {{ref_image message_busy.png, 50%}} >> COMException が発生しました。 メッセージ フィルターはアプリケーションがビジーである事を示しています。 (HRESULT からの例外: 0x8001010A (RPC_E_SERVERCALL_RETRYLATER)) << とかいう例外が投げられる。 単に普通にメソッド/プロパティ読んだだけで怒られる。 ぐぇえええぇぇぇ… orz ** 先ず読むべき物 MSDNライブラリの資料。 …でも正直コレ読んでも 対処方法が 良くわからなかったよ… orz - [http://msdn.microsoft.com/ja-jp/library/8sesy69e%28VS.80%29.aspx:title=Office でのスレッドのサポート] ** a) 新しいスレッドは STAThread である必要がある 上記MSDNにも書いてありますが、新しくスレッドを創る際は STAThread にする必要があります。 ** b) UIスレッドに委譲する Officeアプリは UIスレッドで動いているので、自前で創ったスレッドからOfficeのオブジェクトにアクセスすると多分落ちます。 よって、UIスレッドに委譲させると上手くいくこと多いです。 対処方法は… + アドインロード時に呼ばれる関数 ThisAddIn_Startup(object sender, System.EventArgs e) で、非表示のFormを1個設ける + 自前で創ったスレッドから office のオブジェクトにアクセスする際は、その Formインスタンス経由で実行する。 つまり Form::Invoke() に delegate を投げて実行する。 ちなみに new Form() しただけではウィンドウが創られず期待通りに動きません。 よって、Form::Handle プロパティを読んで強制的にウィンドウを創る必要があります。 ** c) IMessageFilter を実装して登録する MSDNの資料内に「IMessageFilter というインターフェースが COM に用意されています。云々…」ってあり、C# から Native 弄るのはヤだなぁ… ついでに native の interface の 持ってくる方法も良くわからないし… と敬遠していたのですが、結局の所この子を弄ることに。 …とはいえ実装したところ、ようやく問題が無くなくなった感じです。 答えとしては [http://msdn.microsoft.com/ja-jp/library/ms228772.aspx:title=コレ] だったんですが、VSTOで利用する際には無駄なコードも含まれて居ますので、オレオレ版を以下に載せておきます。 利用方法は、アドインロード時に MessageFilter::Register() を、アンロード時に MessageFilter::Revoke() を呼べばOKです。 >|cs| namespace Kashira { [ComImport(), Guid("00000016-0000-0000-C000-000000000046"), InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)] interface IOleMessageFilter { [PreserveSig] int HandleInComingCall( int dwCallType, IntPtr hTaskCaller, int dwTickCount, IntPtr lpInterfaceInfo); [PreserveSig] int RetryRejectedCall( IntPtr hTaskCallee, int dwTickCount, int dwRejectType); [PreserveSig] int MessagePending( IntPtr hTaskCallee, int dwTickCount, int dwPendingType); } [Serializable()] public class MessageFilter : IOleMessageFilter { // // Class containing the IOleMessageFilter // thread error-handling functions. // Start the filter. public static void Register() { IOleMessageFilter newFilter = new MessageFilter(); IOleMessageFilter oldFilter = null; CoRegisterMessageFilter(newFilter, out oldFilter); } // Done with the filter, close it. public static void Revoke() { IOleMessageFilter oldFilter = null; CoRegisterMessageFilter(null, out oldFilter); } // // IOleMessageFilter functions. // Handle incoming thread requests. int IOleMessageFilter.HandleInComingCall(int dwCallType, System.IntPtr hTaskCaller, int dwTickCount, System.IntPtr lpInterfaceInfo) { System.Diagnostics.Debug.WriteLine( string.Format("call MessageFilter::HandleInComingCall(dwCallType={0}, hTaskCaller={1}, dwTickCount={2}, lpInterfaceInfo={3})", dwCallType, hTaskCaller, dwTickCount, lpInterfaceInfo)); return SERVERCALL_ISHANDLED; } // Thread call was rejected, so try again. int IOleMessageFilter.RetryRejectedCall(System.IntPtr hTaskCaller, int dwTickCount, int dwRejectType) { System.Diagnostics.Debug.WriteLine( string.Format("call MessageFilter::HandleInComingCall(hTaskCaller={0}, dwTickCount={1}, dwRejectType={2})", hTaskCaller, dwTickCount, dwRejectType)); if (dwRejectType == SERVERCALL_RETRYLATER) { // Retry the thread call immediately if return >=0 & // <100. return 99; } return -1; } int IOleMessageFilter.MessagePending(System.IntPtr hTaskCallee, int dwTickCount, int dwPendingType) { return PENDINGMSG_WAITDEFPROCESS; } // Implement the IOleMessageFilter interface. [DllImport("Ole32.dll")] private static extern int CoRegisterMessageFilter(IOleMessageFilter newFilter, out IOleMessageFilter oldFilter); public const int PENDINGMSG_WAITDEFPROCESS = 2; public const int SERVERCALL_ISHANDLED = 0; public const int SERVERCALL_RETRYLATER = 2; } } ||< {{category Office}}{{category devel}} {{files}} {{lastmodified}}