gpt4 book ai didi

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

转载 作者:行者123 更新时间:2023-12-01 16:25:03 25 4
gpt4 key购买 nike

我们正在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消息将RPC请求发送到COM组件
跨线程STA调用使用Windows消息泵来接收RPC答复
在等待期间,出现了另一种类型的事件(例如“鼠标单击”),并且该事件在处理RPC答案之前得到处理。
此RPC答案已得到处理


我们尝试解决此问题的方法:


在所有事件处理程序中使用lock()。这不起作用,因为lock()将锁定线程,在这种情况下,它是重新进入事件处理程序的同一线程。
使用自定义锁定,例如“布尔锁定=假; if(!locked){锁定= true; InvokeMethod(); ...;锁定=假; }'。这部分起作用:它丢弃事件而不是将它们排队以备后用,并且需要对我们所有的事件处理程序进行大量更改,这不好做。
使用Dispatcher.DisableProcessing停止处理其他消息。这无济于事:由于仍在处理邮件,因此引发异常。
在新线程中创建第二个调度程序,并通过Dispatcher.Invoke()运行ocx.InvokeMehod()使其由另一个线程处理。这给出了“一个事件无法调用任何订阅者(HRESULT异常:0x80040201)”(是的,我们还订阅了ActiveX对象的COM事件)。
使用Dispatcher.PushFrame()停止事件处理。这也失败了。


一个可能有用但不知道如何实现的疯狂想法是创建一个新的消息泵作为WPF消息泵,可以将其配置为仅临时处理RPC调用。这与 http://jmorrill.hjtcentral.com/Home/tabid/428/EntryId/430/WPF-MediaKit-Updates.aspx相似,但与这种情况仍然有些不同。

因此,问题归结为如何像我们期望的那样异步进行ActiveX同步调用呢?

更新资料

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

上下文:我们有一个WPF网格,我们在其上单击了鼠标(Grid_MouseDown),有一个ActiveX对象,在该对象上执行了“ CloseShelf”方法。打开架子会花费一些时间,因此我们订阅了事件'EventShelfClosed',该事件在EventShelfClosed的事件处理程序中将调用'ListShelf'来知道剩下哪些架子。

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

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]


发生的是方法“ CloseShelf”将关闭架子,但是在这种情况下,“ CloseShelf”是如此之快,以至于在调用CloseShelf期间会发出并处理事件“ EventShelfClosed”。现在,CloseShelf将调用ListShelfs,但是ListShelfs将失败并返回null,因为ActiveX组件已被仍处于活动状态的“ CloseShelf”调用锁定。

为什么这是个问题?因为程序员不希望方法调用是异步的。在创建一个大型程序后,这让我们感到震惊,这现在意味着审核所有意外行为的调用。

在这种情况下,我们希望看到什么?我们希望看到'CloseShelf'返回而没有在通话过程中处理其他事件。该方法应该是同步的,并且在主(非递归)消息循环期间处理所有未决事件。

这里使用“锁定”类型的布尔值将无济于事,因为我们这里将丢失事件,从而锁定了应用程序。

最佳答案

尝试下面尝试3中的代码。当我坐下来写这篇文章时,电视上什么都没有。

感谢您的澄清!我看到这里只有一个线程。而且,由于CloseShelf框架仍在堆栈中,因此看起来COM调用实际上已阻塞。

从堆栈跟踪中,看起来com对象正在调用GetMessage或PeekMessage API(或者如果是VB6,DoEvents或类似的东西),它将检查消息队列和其上的PROCESS消息-不管它是否会引起重新进入。又名“泵送消息队列”-但是,如果它使用peekmessage,则在没有消息的情况下也不会阻塞,但仍会执行消息。在您的堆栈跟踪中,这些调用可能隐藏在不可见的本机部分中。堆栈跟踪实际上证明,以某种方式,COM调用正在回调您的代码!您似乎已经意识到了这一点。如果有些读者对此有些迷雾,请点击以下链接:

http://discuss.joelonsoftware.com/default.asp?joel.3.456478.15
http://en.wikipedia.org/wiki/Event_loop

协同多任务处理(整个操作系统的一条消息循环,例如win3.0):
http://en.wikipedia.org/wiki/Computer_multitasking#Cooperative_multitasking.2Ftime-sharing

报价“自愿割让时间...”实际上是通过这些调用完成的。它仍然始终在每个Windows应用程序的GUI线程上发生!

由于实际的调用在COM中,因此,如果您无法对其进行编辑,则仍然必须围绕它进行编码。 (可能是)这可能只是一种方法。

有时,程序和组件会故意检查消息循环,以达到允许事物做出响应或重新绘制屏幕的目的。就像这张海报试图做的那样:

Is there a function like PeekMessage that doesn't process messages?

在这里,有关“系统也可能处理内部事件”的报价使您感到困惑。

请注意在您说“程序员不希望方法调用是异步的”的地方-它不是异步的,否则堆栈跟踪看起来会有所不同。它“递归”回您的代码。作为老的Win3.1程序员,这是我们系统中每个应用程序都要面对的难题!

我发现在尝试查看Get / Peekmessage等是否可以防止处理消息时,谷歌搜索“检查消息队列peekmessage”的链接。您可以从此文献中看到...

http://msdn.microsoft.com/en-us/library/windows/desktop/ms644943(v=vs.85).aspx

...发送类似PM_QS_INPUT的消息| PM_QS_PAINT将防止该问题。不幸的是,由于您没有调用它,因此无法更改它!而且我没有看到任何方法来设置标记来控制来自后续调用的后续消息处理。

如果读者仍然感到困惑(如果不跳过此代码)...举一个简单的例子,制作一个VB Winforms应用程序并单击一个按钮,然后双击它并将此代码粘贴到-(我说VB是因为application.doevents是调用此讨厌的消息队列检查的最简便方法):

    For i As Integer = 0 To 20
Text = i.ToString
System.Threading.Thread.Sleep(100)
Application.DoEvents()
Next


现在单击按钮。请注意,您可以放大窗口并重新绘制背景-因为doevents允许通过检查消息队列来发生这些事件(REM删除doevents,它将“等待”直到计数完成;还可以尝试单击3x,您将获得3连续计数)。

现在...踢手。单击带有未注释掉的Doevents的按钮。单击按钮3次-倒数计时会相互中断,然后在上一个计时结束时恢复计时。您可以暂停IDE并查看3个单击调用堆栈。

好吃!

我还看到您尝试了一些操作来阻止消息或事件被处理。但是,如果从PeekMessage或“ Doevents”引起的重入中调用了触发EventShelfClosed的代码,则可能不会起作用。

请注意,这种做法有其不利之处: http://www.codinghorror.com/blog/2005/08/is-doevents-evil-revisited.html

最好是更改COM,以便它不进行任何检查消息循环的API调用。

祝你好运!

更改它的另一种方法是从控制EventShelfClosed的事件中移出,并在退出对CloseShelf的调用后显式调用它(因为com调用确实在发生)。不幸的是,如果不进行重大更改和/或增加内聚力以及其他污秽漂亮模特的东西(不是时尚模特会介意的话),您程序的体系结构可能不允许这样做。

一种不同的方式是使新线程对象指向一个使com调用的函数,然后启动它,然后将其加入,希望像PeekMessage这样的东西不会在新线程上找到消息泵,因此不会干扰东西。您似乎有几次尝试涉及这种情况。不幸的是,如果COM偷看消息,并且线程kaboom上没有消息泵。它可能会爆炸,而不仅仅是忽略事物。听起来就是这样。此外,如果COM依赖于只能从GUI / messagepump线程访问的其他项,则您将遇到跨线程调用的麻烦(如果COM与UI或任何UI对象进行交互,肯定会出现这种情况) )。

如果您无法停止检查消息队列,或者直到以后都无法触发EventShelfClosed,则别无选择,只能让EventShelfClosed被调用。但是您所能做的就是使它等待,然后在CloseShelf完成时掉下去。

因此,您仍然必须具有CloseShelf设置/取消设置的类级别的布尔字段,以便EventShelfClosed知道它正在运行。

不幸的是,即使在一个while循环中进行检查(即使有一个睡眠),也会阻塞您拥有的单线程并冻结应用程序。只要设置了布尔值,您就可以尝试让EventShelfClosed重新升高自身并退出函数。但是由于RaiseEvent处于托管状态,因此可以立即运行代码,并且不检查消息队列,因此它会因stackoverflow而崩溃。如果您可以通过调用PostMessage API(不是SendMessage,立即运行它)来弄清楚如何重新引发EventShelfClosed,则与COM调用一样,它将使GUI线程的消息队列保持多次,从而使Windows对其进行检查。除非出于某些愚蠢的原因,否则COM等待队列为空-另一个锁定。不推荐。

太...你可以用火扑灭。在这里,我正在实现另一个消息检查循环,以允许您在等待清除布尔值时发生事件。

请注意,这只会在这种情况下解决此问题。审核所有电话...这不是灵丹妙药。我的猜测是没有。非常混乱,这是一个完全hack。

尝试#3

这并不是真正的尝试3,更像是可能性8。但是我在旧答案中提到了这一点,并且懒得编辑它。

Boolean InCloseShelf
function CloseShelf(...)
InCloseShelf=True;
try
{
com call and all else
}
finally
InCloseShelf=False

function EventShelfClosed(...
while (InCloseShelf)
{
DoEvents
}


当然,现在WPF中没有DoEvents了,它在winforms的“应用程序”中。该博客有一个实现

http://dedjo.blogspot.com/2007/08/how-to-doevents-in-wpf.html

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


或者,您始终可以引用WinForms并调用Application.DoEvents。

我猜您已经知道这一点,但是您现在处境不好。如果这样做不行,祝您好运!如果您找到更好的方法,请更新该帖子,因为这样的恶意黑客很烂,但是现在您可以看到为什么人们说要谨慎检查消息队列了!

关于c# - 如何在ActiveX方法调用期间防止WPF事件处理程序的重新进入?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/8309132/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com