VSTOでマルチスレッドなAddIn創ったら謎死が多発の変更点 - Kashira?Wiki

VSTOでマルチスレッドなAddIn創ったら謎死が多発の変更点

  • 追加された行はこのように表示されます。
  • 削除された行はこのように表示されます。
VSTOでアドイン内でマルチスレッドする際に色々死んだのでメモ。 

色々試行錯誤した為、私が解決する際は a -> b -> c という実装を組み込んでいきましたが、もしかしたら b は要らないかもしれない…? いや…うーん…… シーンによっては必要かも知れない…?


** 何が問題か

{{ref_image message_busy.png, 50%}}


>>
COMException が発生しました。

メッセージ フィルターはアプリケーションがビジーである事を示しています。 (HRESULT からの例外: 0x8001010A (RPC_E_SERVERCALL_RETRYLATER))
<<

とかいう例外が投げられる。 <strong>単に普通にメソッド/プロパティ読んだだけで怒られる</strong>。 ぐぇえええぇぇぇ… orz



** 先ず読むべき物

MSDNライブラリの資料。 …でも正直コレ読んでも <strong>対処方法が</strong> 良くわからなかったよ… 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スレッドに委譲させると上手くいくこと多いです。

対処方法は…

+ アドインロード時に呼ばれる関数 <tt>ThisAddIn_Startup(object sender, System.EventArgs e)</tt> で、非表示の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}}