完全な答えは次のとおりです。
問題: データを Excel に書き込むクラスが、Excel からの「busy/reject」応答メッセージを処理できません。
解決策:ここでIMessageFilter
説明されているようにインターフェースを実装します
IMessageFilter
定義(リンクから):
namespace ExcelAddinMessageFilter
{
[StructLayout(LayoutKind.Sequential, Pack = 4)]
public struct INTERFACEINFO
{
[MarshalAs(UnmanagedType.IUnknown)]
public object punk;
public Guid iid;
public ushort wMethod;
}
[ComImport, ComConversionLoss, InterfaceType((short)1),
Guid("00000016-0000-0000-C000-000000000046")]
public interface IMessageFilter
{
[PreserveSig, MethodImpl(MethodImplOptions.InternalCall,
MethodCodeType = MethodCodeType.Runtime)]
int HandleInComingCall([In] uint dwCallType, [In] IntPtr htaskCaller,
[In] uint dwTickCount,
[In, MarshalAs(UnmanagedType.LPArray)] INTERFACEINFO[]
lpInterfaceInfo);
[PreserveSig, MethodImpl(MethodImplOptions.InternalCall,
MethodCodeType = MethodCodeType.Runtime)]
int RetryRejectedCall([In] IntPtr htaskCallee, [In] uint dwTickCount,
[In] uint dwRejectType);
[PreserveSig, MethodImpl(MethodImplOptions.InternalCall,
MethodCodeType = MethodCodeType.Runtime)]
int MessagePending([In] IntPtr htaskCallee, [In] uint dwTickCount,
[In] uint dwPendingType);
}
}
IMessageFilter
私のクラスの実装部分(リンクを参照):
#region IMessageFilter Members
int ExcelAddinMessageFilter.IMessageFilter.
HandleInComingCall(uint dwCallType, IntPtr htaskCaller, uint dwTickCount, ExcelAddinMessageFilter.INTERFACEINFO[] lpInterfaceInfo)
{
// We're the client, so we won't get HandleInComingCall calls.
return 1;
}
int ExcelAddinMessageFilter.IMessageFilter.
RetryRejectedCall(IntPtr htaskCallee, uint dwTickCount, uint dwRejectType)
{
// The client will get RetryRejectedCall calls when the main Excel
// thread is blocked. We can handle this by attempting to retry
// the operation. This will continue to fail so long as Excel is
// blocked.
// As an alternative to simply retrying, we could put up
// a dialog telling the user to close the other dialog (and the
// new one) in order to continue - or to tell us if they want to
// abandon this call
// Expected return values:
// -1: The call should be canceled. COM then returns RPC_E_CALL_REJECTED from the original method call.
// Value >= 0 and <100: The call is to be retried immediately.
// Value >= 100: COM will wait for this many milliseconds and then retry the call.
return 1;
}
int ExcelAddinMessageFilter.IMessageFilter.
MessagePending(IntPtr htaskCallee, uint dwTickCount, uint dwPendingType)
{
return 1;
}
#endregion
IMessageFilter
インターフェイスを定義して実装したら、次のように STA と をセットアップしますThread
。Timers.Timer
スレッド:
thread = new Thread(WriteToExcel);
thread.SetApartmentState(ApartmentState.STA);
タイマー:
timer = new System.Timers.Timer();
timer.Interval = 2000;
timer.Elapsed += new ElapsedEventHandler(StartSTAThread_Handler);
は次のようにStartSTAThread_Handler
定義されます。
void StartSTAThread_Handler(object source, ElapsedEventArgs e)
{
thread.Start();
thread.Join();
thread = null;
}
このスレッドは、Excel への書き込みに使用するメソッドを呼び出し、IMessageFilter
上記のインターフェイスで拒否されたメッセージを処理します。私がしなければならなかった最後のことは、Excel OM 参照を完全に修飾することでした。それ以外の:
Excel.Range rng = app.ActiveSheet.Range["range_name"];
rng.Copy(); // ERRROR: message filter's RetryRejectedCall is NOT called
完全修飾参照を使用する必要がありました。
app.ActiveSheet.Range["range_name"].Copy // OK: calls RetryRejectedCall when excel dialog etc is showing
これは私のニーズには合っているようですが、ここの別のポスターで説明されている「2ドットルール」と矛盾しているようです...