我通过VSTO有一个Excel功能区。单击一个按钮时,将进行一些处理,并在当前工作表上填充行。在此过程中,Excel锁定-用户无法继续使用其程序。我的解决方法涉及实现异步解决方案,如下所示:
<Subject>
<NameID>S40rgb3XjhFTv6EQTETkEzcgVmToHKRkZUIsJlmLdVc</NameID>
<SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer" />
</Subject>
此方法的一个大问题是,当用户单击时,会弹出以下错误:
System.Runtime.InteropServices.COMException:'来自HRESULT的异常: 0x800AC472'
但是,与键盘的交互不会触发此类事件。
我不确定如何调试此错误,但这使我提出了一些问题:
答案 0 :(得分:-1)
可能是2018年,但基础架构没有改变,仍然不建议使用多线程。
尽管如此,还是有办法的。 Here's是我所知道的关于正确执行操作的最佳资源……但是它会提前警告您:
首先警告:这是一种高级情况,除非您确定知道自己在做什么,否则请勿尝试使用此技术。发出此警告的原因是,尽管此处描述的技术非常简单,但也很容易以可能会严重干扰主机应用程序的方式出错。
其余:
问题描述:您构建了一个Office加载项,该加载项会定期向宿主对象模型中调用。有时呼叫会失败,因为主机正忙于做其他事情。也许它正在重新计算工作表;或(最常见),它可能正在显示一个模式对话框,并等待用户输入才能继续。
如果您没有在外接程序中创建任何后台线程,因此在您创建外接程序的同一个线程上进行所有OM调用,则您的调用不会失败,只会被调用直到主机被解除阻止。然后,将按顺序对其进行处理。这是正常情况,建议在大多数情况下,这是设计Office解决方案的方式–即,不创建任何新线程。
但是,如果您确实创建了其他线程,并尝试在这些线程中的任何一个上进行OM调用,则如果主机被阻止,则调用将仅失败。您将获得一个COMException,通常是这样的:System.Runtime.InteropServices.COMException,来自HRESULT的异常:0x800AC472。
要解决此问题,您可以在外接程序中实现IMessageFilter,然后在其他线程上注册消息过滤器。如果执行此操作,并且在该线程上进行调用时Excel处于繁忙状态,则COM将回调您的IMessageFilter.RetryRejectedCall实现。这使您有机会处理失败的呼叫-通过重试和/或采取其他缓解措施,例如显示消息框,告诉用户如果他们希望继续操作,请关闭任何打开的对话框。 / p>
请注意,通常定义2个IMessageFilter接口。一个在System.Windows.Forms中–您不想要那个。取而代之的是,您需要在objidl.h中定义的一个,您需要像这样导入:
[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);
}
然后,在您的ThisAddIn类中实现此接口。请注意,IMessageFilter也是在服务器上实现的(在本例中为Excel),并且IMessageFilter.HandleInComingCall调用仅在服务器上进行。其他2个方法将在客户端上调用(在此示例中,即我们的外接程序)。在应用程序进行COM方法调用并且在调用返回之前出现Windows消息之后,我们将获得MessagePending调用。重要的方法是RetryRejectedCall。在下面的实现中,我们显示一个消息框,询问用户是否要重试该操作。如果他们说“是”,则返回1,否则返回-1。 COM希望此调用返回以下值:
- -1:应取消通话。然后,COM从原始方法调用中返回RPC_E_CALL_REJECTED。
- 值> = 0和<100:该呼叫将立即重试。
- 值> = 100:COM将等待这么多毫秒,然后重试该调用。
public int HandleInComingCall([In] uint dwCallType, [In] IntPtr htaskCaller, [In] uint dwTickCount,
[In, MarshalAs(UnmanagedType.LPArray)] INTERFACEINFO[] lpInterfaceInfo)
{
Debug("HandleInComingCall");
return 1;
}
public int RetryRejectedCall([In] IntPtr htaskCallee, [In] uint dwTickCount, [In] uint dwRejectType)
{
int retVal = -1;
Debug.WriteLine("RetryRejectedCall");
if (MessageBox.Show("retry?", "Alert", MessageBoxButtons.YesNo) == DialogResult.Yes)
{
retVal = 1;
}
return retVal;
}
public int MessagePending([In] IntPtr htaskCallee, [In] uint dwTickCount, [In] uint dwPendingType)
{
Debug("MessagePending");
return 1;
}
最后,使用CoRegisterMessageFilter向COM注册消息过滤器。消息筛选器是每个线程的,因此必须在创建的用于调用OM的后台线程上注册筛选器。在下面的示例中,外接程序提供一个方法InvokeAsyncCallToExcel,将从功能区按钮中调用该方法。在此方法中,我们创建一个新线程并确保这是一个STA线程。在我的示例中,线程过程RegisterFilter完成了注册过滤器的工作,然后休眠3秒钟,使用户有机会进行可能会阻止的操作,例如在Excel中弹出对话框。显然,这只是出于演示目的,因此您可以看到Excel在进行后台线程调用之前阻塞时会发生什么。 CallExcel方法在Excel的OM上进行呼叫。
[DllImport("ole32.dll")]
static extern int CoRegisterMessageFilter(IMessageFilter lpMessageFilter, out IMessageFilter lplpMessageFilter);
private IMessageFilter oldMessageFilter;
internal void InvokeAsyncCallToExcel()
{
Thread t = new Thread(this.RegisterFilter);
t.SetApartmentState(ApartmentState.STA);
t.Start();
}
private void RegisterFilter()
{
CoRegisterMessageFilter(this, out oldMessageFilter);
Thread.Sleep(3000);
CallExcel();
}
private void CallExcel()
{
try
{
this.Application.ActiveCell.Value2 = DateTime.Now.ToShortTimeString();
}
catch (Exception ex)
{
Debug.WriteLine(ex.ToString());
}
}
请注意,由于原始代码未编译,因此我将返回类型从uint更改为int。我已经在Word中尝试过此方法,并且它确实可以工作,但是我没有将其包含在软件中,主要是因为我不确定该方法的爆发方式。作者没有说。