C# 如何防止在ActiveX方法调用期间WPF事件处理程序重新进入?

C# 如何防止在ActiveX方法调用期间WPF事件处理程序重新进入?,c#,.net,wpf,com,activex,C#,.net,Wpf,Com,Activex,我们正在从WPF和STA应用程序中调用ActiveX组件上的方法。此调用通过以下方式执行: res = ocx.GetType().InvokeMember(methodName, flags, null, ocx, args); …其中ocx是使用System.Windows.Forms.AxHost.GetOcx()方法检索的ActiveX对象 此调用在WPF事件处理程序中执行,例如“鼠标单击” 现在问题来了。如果我们双击“鼠标点击”事件将触发,运行InvokeMember()。但是,在此

我们正在从WPF和STA应用程序中调用ActiveX组件上的方法。此调用通过以下方式执行:

res = ocx.GetType().InvokeMember(methodName, flags, null, ocx, args);
…其中ocx是使用System.Windows.Forms.AxHost.GetOcx()方法检索的ActiveX对象

此调用在WPF事件处理程序中执行,例如“鼠标单击”

现在问题来了。如果我们双击“鼠标点击”事件将触发,运行InvokeMember()。但是,在此调用过程中,我们看到“鼠标单击”事件被重新输入。因此,在同一线程中,我们在调用堆栈上看到两次事件处理程序。这是非常出乎意料的,我们正在努力防止这种情况发生。我们怎样才能防止这种情况发生

我们能想到为什么会发生这种情况的唯一原因是:

  • COM对象是在另一个STA中创建的,因此我们正在执行需要编组的跨STA调用
  • 跨线程STA调用使用Windows消息向COM组件发送RPC请求
  • 跨线程STA调用使用Windows消息泵接收RPC应答
  • 在等待过程中,会出现另一种类型的事件(如“鼠标单击”),并且在处理RPC应答之前处理该事件
  • 这个RPC答案得到处理
我们试图解决问题的方法:

  • 在所有事件处理程序中使用lock()。这不起作用,因为lock()将锁定线程,在本例中,是同一个线程重新进入事件处理程序
  • 使用自定义锁定,如“bool locked=false”;如果(!locked){locked=true;InvokeMethod();…;locked=false;}'。这在一定程度上是可行的:它会丢弃事件,而不是将它们排队等待以后,并且需要对所有事件处理程序进行大量更改,这是不好的
  • 使用Dispatcher.DisableProcessing停止处理(其他)消息。这并没有帮助:它会抛出一个异常,因为消息无论如何都会被处理
  • 在新线程中创建第二个调度程序,并通过dispatcher.Invoke()运行ocx.invokeMhod(),让另一个线程处理它。这给出了“一个事件无法调用任何订阅服务器(HRESULT的异常:0x80040201)”(是的,我们还订阅了ActiveX对象的COM事件)
  • 使用Dispatcher.PushFrame()停止事件处理。这也失败了
一个可能可行但不知道如何实现的疯狂想法是创建一个新的消息泵作为WPF消息泵,可以将其配置为仅临时处理RPC调用。这是大致的,但仍与这种情况有所不同

因此,问题归结为,我们如何才能像预期的那样同步调用ActiveX,而不是异步调用

更新

为了更清楚地说明所涉及的机制不仅与鼠标事件有关,而且与“执行旧事件时处理新事件”这一更一般的问题有关,我将给出另一个堆栈跟踪示例:

上下文:我们有一个WPF网格,在它上面有一个鼠标点击(Grid\u MouseDown),我们有一个ActiveX对象,在它上面执行“CloseShelf”方法。打开shelf需要时间,因此我们订阅了事件“EventShelfClosed”,该事件在EventShelfClosed的事件处理程序中将调用“listself”,以了解剩下哪些shelf

这就是托管堆栈跟踪的样子(Hans要求提供非托管堆栈跟踪,但我不知道如何获得):

发生的情况是,方法“CloseShelf”将关闭工具架,但在本例中,“CloseShelf”速度太快,以至于在调用CloseShelf期间会发出并处理事件“EventShelfClosed”。现在CloseShelf将调用ListShelfs,但ListShelfs将失败并返回null,因为ActiveX组件被仍然处于活动状态的“CloseShelf”调用锁定

为什么这是一个问题?因为程序员不希望方法调用是异步的。这是在创建了一个大型程序之后发生的,现在这意味着审核所有调用以发现意外行为

在这种情况下,我们希望看到什么?我们希望看到“CloseShelf”在调用期间返回,而不处理其他事件。该方法应该是同步的,并且在主(非递归)消息循环期间处理任何挂起的事件

在这里使用“锁定”类型的布尔值并没有帮助,因为我们在这里会丢失事件,从而锁定应用程序。

您已经找到了答案! 这只是一个老把戏吗?不,这不是,当涉及任何类型的再进入时,这是标准的操作程序。从简陋的单面板VB3 crud弹出窗口到巨大的MVVM/DDD可供使用的企业管理应用程序,这在我记忆中无法想象的情况下对我来说是完美的

这就是您提到的:'使用自定义锁定,如'static bool locked=false;如果(!locked){locked=true;InvokeMethod();…;locked=false;}'

编辑 注意OP的评论。好吧,这样就解决不了问题了!第二个事件不是虚假的点击;这是一个不同的事件,对系统的正常运行至关重要


请看我的下一个答案,再尝试几次#3是最丑的,但应该可以工作。

如果你已经有了异步行为,我会试试Jeffrey Richter的
PowerThreading
库。它有
AsyncEnumerator
来简化异步编程。它还具有锁定原语,可以帮助您实现场景。据我所知,这个原语不同于常规的
监视器
类,因为它不允许在同一个线程中重新输入代码,所以它可能会对您有所帮助。不幸的是,我还没有尝试过那个原始版本,所以无法添加太多内容


这里有一篇关于这个原语的文章:

我在过去处理过类似的问题,尽管不是来自WPF

在win32应用程序中,推荐的方法是使用IMessageFilter::MessagePending-这可以配置为
MyAxWrapper.dll!MyAxWrapper.MyAxWrapper.InvokeMember(string methodName, System.Reflection.BindingFlags flags, object[] args, int refArgIdx) Line 53 C#
MyAxWrapper.dll!MyAxWrapper.LoggingMyAxWrapper.InvokeMember(string methodName, System.Reflection.BindingFlags flags, object[] args, int refArgIdx) Line 151 + 0x14 bytes    C#
MyAxWrapper.dll!MyAxWrapper.MyAxWrapper.InvokeMethod(string methodName, object[] args) Line 92 + 0x18 bytes C#
MyAxWrapper.dll!MyAxWrapper.MyAxAppWrapper.ListShelfs(string CanvasPageId) Line 300 + 0x42 bytes    C#
PACS.dll!PACS.MyAxDatabase.GetShelfIdsOn(string canvasPageId) Line 223 + 0xf bytes  C#
MyAxCanvas.dll!MyAxCanvas.MyAxCanvasPlugin.UpdateTimeLineSelection(string canvasPageId) Line 123 + 0x10 bytes   C#
MyAxCanvas.dll!MyAxCanvas.MyAxCanvasPlugin.EventShelfClosed(string canvasPageId, string shelfId) Line 180 + 0xb bytes   C#
MyAxWrapper.dll!MyAxWrapper.MyAxAppWrapper.FireEvent(string eventName, object[] args) Line 21 + 0x73 bytes  C#
MyAxWrapper.dll!MyAxWrapper.MyAxEventForwarder.EventShelfClosed(string CanvasPageID, string ShelfID) Line 177 + 0x58 bytes  C#
[Native to Managed Transition]  
[Native to Managed Transition]  
MyAxWrapper.dll!MyAxWrapper.MyAxWrapper.InvokeMember(string methodName, System.Reflection.BindingFlags flags, object[] args, int refArgIdx) Line 75 + 0x2b bytes    C#
MyAxWrapper.dll!MyAxWrapper.LoggingMyAxWrapper.InvokeMember(string methodName, System.Reflection.BindingFlags flags, object[] args, int refArgIdx) Line 151 + 0x14 bytes    C#
MyAxWrapper.dll!MyAxWrapper.MyAxWrapper.InvokeMethod(string methodName, object[] args) Line 92 + 0x18 bytes C#
MyAxWrapper.dll!MyAxWrapper.MyAxAppWrapper.CloseShelf(string a) Line 218 + 0x42 bytes   C#
MyAxCanvas.dll!MyAxCanvas.MyAxCanvasPlugin.EventCanvasPageCreated.AnonymousMethod__0(DataModel.Item exam) Line 110 + 0x1d bytes C#
ItemPresenter.dll!ItemPresenter.ItemPresenter.OnItemClicked(DataModel.Item study) Line 36 + 0x14 bytes  C#
ItemPresenter.dll!ItemPresenter.ItemPresenter.ItemPresenterPerYearControls_Click(object sender, System.Windows.RoutedEventArgs e) Line 215 + 0x1e bytes C#
PresentationCore.dll!System.Windows.RoutedEventHandlerInfo.InvokeHandler(object target, System.Windows.RoutedEventArgs routedEventArgs) + 0x78 bytes    
PresentationCore.dll!System.Windows.EventRoute.InvokeHandlersImpl(object source, System.Windows.RoutedEventArgs args, bool reRaised) + 0x1ae bytes  
PresentationCore.dll!System.Windows.UIElement.RaiseEventImpl(System.Windows.DependencyObject sender, System.Windows.RoutedEventArgs args) + 0x79 bytes  
PresentationCore.dll!System.Windows.UIElement.RaiseEvent(System.Windows.RoutedEventArgs e) + 0x17 bytes 
ItemPresenter.dll!ItemPresenter.ItemPresenterControl.Grid_MouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e) Line 47 + 0x29 bytes    C#
PresentationCore.dll!System.Windows.Input.MouseButtonEventArgs.InvokeEventHandler(System.Delegate genericHandler, object genericTarget) + 0x31 bytes    
PresentationCore.dll!System.Windows.RoutedEventArgs.InvokeHandler(System.Delegate handler, object target) + 0x29 bytes  
PresentationCore.dll!System.Windows.RoutedEventHandlerInfo.InvokeHandler(object target, System.Windows.RoutedEventArgs routedEventArgs) + 0x3e bytes    
PresentationCore.dll!System.Windows.EventRoute.InvokeHandlersImpl(object source, System.Windows.RoutedEventArgs args, bool reRaised) + 0x1ae bytes  
PresentationCore.dll!System.Windows.UIElement.RaiseEventImpl(System.Windows.DependencyObject sender, System.Windows.RoutedEventArgs args) + 0x79 bytes  
PresentationCore.dll!System.Windows.UIElement.RaiseTrustedEvent(System.Windows.RoutedEventArgs args) + 0x41 bytes   
PresentationCore.dll!System.Windows.UIElement.RaiseEvent(System.Windows.RoutedEventArgs args, bool trusted) + 0x2c bytes    
PresentationCore.dll!System.Windows.Input.InputManager.ProcessStagingArea() + 0x1ff bytes   
PresentationCore.dll!System.Windows.Input.InputManager.ProcessInput(System.Windows.Input.InputEventArgs input) + 0x45 bytes 
PresentationCore.dll!System.Windows.Input.InputProviderSite.ReportInput(System.Windows.Input.InputReport inputReport) + 0x62 bytes  
PresentationCore.dll!System.Windows.Interop.HwndMouseInputProvider.ReportInput(System.IntPtr hwnd, System.Windows.Input.InputMode mode, int timestamp, System.Windows.Input.RawMouseActions actions, int x, int y, int wheel) + 0x263 bytes 
PresentationCore.dll!System.Windows.Interop.HwndMouseInputProvider.FilterMessage(System.IntPtr hwnd, MS.Internal.Interop.WindowMessage msg, System.IntPtr wParam, System.IntPtr lParam, ref bool handled) + 0x46d bytes 
PresentationCore.dll!System.Windows.Interop.HwndSource.InputFilterMessage(System.IntPtr hwnd, int msg, System.IntPtr wParam, System.IntPtr lParam, ref bool handled) + 0x75 bytes   
WindowsBase.dll!MS.Win32.HwndWrapper.WndProc(System.IntPtr hwnd, int msg, System.IntPtr wParam, System.IntPtr lParam, ref bool handled) + 0xbe bytes    
WindowsBase.dll!MS.Win32.HwndSubclass.DispatcherCallbackOperation(object o) + 0x7d bytes    
WindowsBase.dll!System.Windows.Threading.ExceptionWrapper.InternalRealCall(System.Delegate callback, object args, int numArgs) + 0x53 bytes 
WindowsBase.dll!MS.Internal.Threading.ExceptionFilterHelper.TryCatchWhen(object source, System.Delegate method, object args, int numArgs, System.Delegate catchHandler) + 0x42 bytes    
WindowsBase.dll!System.Windows.Threading.Dispatcher.InvokeImpl(System.Windows.Threading.DispatcherPriority priority, System.TimeSpan timeout, System.Delegate method, object args, int numArgs) + 0xb4 bytes    
WindowsBase.dll!MS.Win32.HwndSubclass.SubclassWndProc(System.IntPtr hwnd, int msg, System.IntPtr wParam, System.IntPtr lParam) + 0x104 bytes    
[Native to Managed Transition]  
[Managed to Native Transition]  
WindowsBase.dll!System.Windows.Threading.Dispatcher.PushFrameImpl(System.Windows.Threading.DispatcherFrame frame) + 0xc1 bytes  
WindowsBase.dll!System.Windows.Threading.Dispatcher.PushFrame(System.Windows.Threading.DispatcherFrame frame) + 0x49 bytes  
WindowsBase.dll!System.Windows.Threading.Dispatcher.Run() + 0x4c bytes  
PresentationFramework.dll!System.Windows.Application.RunDispatcher(object ignore) + 0x17 bytes  
PresentationFramework.dll!System.Windows.Application.RunInternal(System.Windows.Window window) + 0x6f bytes 
PresentationFramework.dll!System.Windows.Application.Run(System.Windows.Window window) + 0x26 bytes 
PresentationFramework.dll!System.Windows.Application.Run() + 0x1b bytes 
MyAxCanvasStandalone.exe!MyAxCanvasStandalone.App.Main(string[] args) Line 37 + 0xa bytes   C#
[Native to Managed Transition]  
[Managed to Native Transition]  
mscorlib.dll!System.AppDomain.ExecuteAssembly(string assemblyFile, System.Security.Policy.Evidence assemblySecurity, string[] args) + 0x6d bytes    
Microsoft.VisualStudio.HostingProcess.Utilities.dll!Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly() + 0x2a bytes  
mscorlib.dll!System.Threading.ThreadHelper.ThreadStart_Context(object state) + 0x63 bytes   
mscorlib.dll!System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state, bool ignoreSyncCtx) + 0xb0 bytes    
mscorlib.dll!System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state) + 0x2c bytes    
mscorlib.dll!System.Threading.ThreadHelper.ThreadStart() + 0x44 bytes   
[Native to Managed Transition]  
    For i As Integer = 0 To 20
        Text = i.ToString
        System.Threading.Thread.Sleep(100)
        Application.DoEvents()
    Next
Boolean InCloseShelf
function CloseShelf(...)
    InCloseShelf=True;
    try
    {
         com call and all else
     }
     finally
         InCloseShelf=False

function EventShelfClosed(...
    while (InCloseShelf)
    {
         DoEvents
     }
void DoEvents(){ 
DispatcherFrame f = new DispatcherFrame(); 
Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Background,  
(SendOrPostCallback)delegate(object arg) { 
    DispatcherFrame fr =  arg as DispatcherFrame; 
    fr.Continue=True; 
}, f); 
Dispatcher.PushFrame(frame); 
}
static void DoEvents()
{
DispatcherFrame frame = new DispatcherFrame(true);
Dispatcher.CurrentDispatcher.BeginInvoke
(
DispatcherPriority.Background, 
(SendOrPostCallback) delegate(object arg)
{
var f = arg as DispatcherFrame; 
f.Continue = false;
}, 
frame
);
Dispatcher.PushFrame(frame);
} 
.... OnClick(...)
{
   if(SelectionChanged!=null)
       SelectionChanged(...)
}
.... OnClick(...)
{
   Dispatcher.BeginInvoke(delegate(){
   if(SelectionChanged!=null)
       SelectionChanged(...)
   });
}