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

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

VSTOでアドイン内でマルチスレッドする際に色々死んだのでメモ。

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


何が問題か


COMException が発生しました。

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

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


先ず読むべき物

MSDNライブラリの資料。 …でも正直コレ読んでも 対処方法が 良くわからなかったよ… orz


a) 新しいスレッドは STAThread である必要がある

上記MSDNにも書いてありますが、新しくスレッドを創る際は STAThread にする必要があります。


b) UIスレッドに委譲する

Officeアプリは UIスレッドで動いているので、自前で創ったスレッドからOfficeのオブジェクトにアクセスすると多分落ちます。 よって、UIスレッドに委譲させると上手くいくこと多いです。

対処方法は…

  1. アドインロード時に呼ばれる関数 ThisAddIn_Startup(object sender, System.EventArgs e) で、非表示のFormを1個設ける
  2. 自前で創ったスレッドから office のオブジェクトにアクセスする際は、その Formインスタンス経由で実行する。 つまり Form::Invoke() に delegate を投げて実行する。

ちなみに new Form() しただけではウィンドウが創られず期待通りに動きません。 よって、Form::Handle プロパティを読んで強制的にウィンドウを創る必要があります。


c) IMessageFilter を実装して登録する

MSDNの資料内に「IMessageFilter というインターフェースが COM に用意されています。云々…」ってあり、C# から Native 弄るのはヤだなぁ… ついでに native の interface の 持ってくる方法も良くわからないし… と敬遠していたのですが、結局の所この子を弄ることに。 …とはいえ実装したところ、ようやく問題が無くなくなった感じです。

答えとしては コレ だったんですが、VSTOで利用する際には無駄なコードも含まれて居ますので、オレオレ版を以下に載せておきます。

利用方法は、アドインロード時に MessageFilter::Register() を、アンロード時に MessageFilter::Revoke() を呼べばOKです。

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;
    }
}

[Office][devel]

message_busy.png

最終更新時間:2012年04月13日 15時10分43秒