【问题标题】:In VB6 why does Event processing stop in sleep loop?在 VB6 中,为什么事件处理在睡眠循环中停止?
【发布时间】:2018-11-22 06:52:47
【问题描述】:

我有一个 VB6 ActiveX EXE,它具有它使用的 OCX 控件的事件处理程序。在 ActiveX 的终止函数中,我需要确保所有事件都已完成处理,然后才允许终止完成。在终止函数中,我在开头添加了以下代码:

While EventsInProgressFlags <> 0
    DoEvents
    Sleep(200)
WEnd

EventsInProgressFlags 是一个整数,我用它来设置正在进行的事件。但是我观察到的是,当这个循环运行时,正在进行的事件永远不会完成。通过向事件和终止例程添加许多日志消息,我可以看到在调用 Terminate 函数时事件处理正在进行中,但即使我正在调用 DoEvents,这些正在进行的事件在 Terminate 函数处于活动状态时也不会执行.如果我强制 Terminate 函数退出循环,那么事件处理会从它停止的地方继续,但当然会出现错误,因为 Terminate 已经破坏了事件使用的组件。

在我的睡眠循环中有没有办法让正在进行的事件代码完成执行?

================2018 年 11 月 20 日更新===================

经过一些在线研究,我相信我更好地理解了原因,但还没有解决方案。

阅读Multithreading in VB6 后,我了解到 VB6 是单线程的,所以是的,我的 OCX 事件处理和 Terminate 函数都在同一个线程中。所以这就是为什么让 Terminate 函数进入睡眠状态不起作用的原因。因为它们都在同一个 VB6 线程中,所以我也将事件处理置于睡眠状态。我想知道我的 ActiveX Terminate 函数是如何在我的事件处理代码中间被调用的。我的理论是,由于我的事件处理在进行系统调用时执行 VB6 的系统调用,因此除了执行系统调用之外,它还调用 DoEvents 来为 Windows 事件队列提供服务。这样做是为了使 UI 更具响应性。调用 DoEvents 然后为 ActiveX Terminate Call 处理传入的 Windows 事件。如果一些 VB6 大师可以在这里证实我的结论,我会很感兴趣。

因此,一种可能的解决方案是,在我处理 OCX 事件时,是否有某种方法可以阻止系统调用 DoEvents。有谁知道这样做的方法吗?

另一种可能的解决方案是,如果有一种方法可以从 Terminate 函数返回,以便事件处理可以继续,但在事件处理完成之前不会将 Terminate 函数的响应发送到主机。不过,这听起来不太合理。

【问题讨论】:

  • 我从来没有遇到过这种情况,也没有调查过,但我预计发生的事情是在您的终止事件触发之前,ocx 的事件处理程序已经被取消挂钩。如果是这样,则您无法在终止事件中执行任何操作来允许 ocx 事件运行。最好的办法可能是在所有预期的 ocx 事件完成之前,不要在主应用程序中释放(取消实例化)ActiveX exe(因此不会导致 ocx 事件处理程序解除挂钩并触发终止事件)。
  • DoEvents 调用 API 的 Sleep(0)Sleep 函数将当前线程的执行暂停指定的时间间隔。 MSDN.
  • @MarkL 您的结论与我的观察不符。负责销毁表单的是 ActiveX EXE 的 API Terminate 命令,因此我的睡眠等待循环阻止了对关联表单和 ActiveX EXE COM 对象的任何破坏。我还知道(从日志消息中)正在进行的事件在 Terminate 命令从主机进入时已经在执行。主机不在我的控制之下,所以当终止呼叫发生时我无法更改。我能看到的唯一选择是一旦调用终止以延迟它,直到其他事件完成。
  • 但是睡眠循环似乎没有释放执行线程以允许已经在进行的事件处理完成。我有兴趣了解与 vB6 中处理事件相关的线程。正在进行的事件由 OCX 控件触发,然后生成一些软件事件。我可以从日志中看到 OCX 硬件事件和相关的软件事件在 ActiveX EXE 时已经启动
  • Terminate 被调用,但事件处理的所有执行都会暂停,直到我从 Terminate 函数返回。在 VB6 中,睡眠(来自 kernel32)是否不会释放当前线程的执行,并且来自 OCX 的事件处理是在与 ActiveX EXE 主事件处理循环不同的线程或相同的线程中执行的吗?

标签: events vb6


【解决方案1】:

因此,一旦我了解 VB6 是单线程的,我就返回调试器并确认当 Terminate 函数执行 VB6 堆栈时,显示 OCX 事件处理在堆栈上进一步等待,直到 Terminate 函数返回。显然,我想要完成的事件处理(在 Terminate 退出之前)正在同一个线程中运行。 Terminate 被“VB6 调用”有效地中断了事件处理,这意味着完成事件处理的唯一方法是从 Terminate 命令返回。但是从 Terminate 返回会向(ActiveX EXE 的)主机发出 ActiveX 对象被终止的信号,而实际上它不是。结果是 Terminate 函数返回后 OCX 事件处理出错。

我想要的是一种禁止处理传入 ActiveX EXE 的命令的方法。这将在事件处理正在进行时推迟 Terminate 函数的执行。我找不到任何方法来做到这一点。

所以我想出的不太优雅的解决方案是添加一个全局 HasTerminated 由 Terminate 子例程设置的布尔标志。然后错误 生成错误 #91 "Object variable or With block variable not set" 的按钮事件例程的处理程序已修改为忽略错误 #91,但前提是 HasTerminated 布尔标志是真的。这样做是中止任何导致错误的事件处理,但前提是 ActiveX 已终止。这是一个可接受的解决方案,因为 OCX 事件不需要完成,因为组件正在终止。他们只需要不引起错误。这是我添加到所有事件处理例程中的示例错误处理程序。

M_SIGN_CAPTURE_FORM_CLEAR_CLICKED_ERR_HANDLER:
40  If HasTerminated And Err.Number = 91 Then
50      Resume 30 ' Resume at Exit Sub
60  End If

70  ErrBox "ERROR #" & Err.Number & " " & FILE__ _
        & ":m_SignCaptureForm_ClearClicked:" & Erl & Err.Description
80  Resume Next
End Sub

【讨论】:

  • 实验第二次引用 COM EXE。直到引用计数为 0 才会退出。如果它在运行对象表中,请使用 GetObject
  • 不关注。如果您的意思是创建不可行的 ActiveX 的新实例,也不是创建新进程。 OCX 事件处理需要访问当前 ActiveX 实例中包含的组件。 ActiveX 实例由第 3 方创建。问题是终止函数不是直接从事件处理代码调用的,而是由 VB6 本身通过事件队列调用的。需要完成的代码在同一线程的堆栈中较高。所以没有办法从 Terminate 得到它。答案是在事件处理期间禁用 cmd 处理,这似乎是不可能的。
  • 也许发布你的调用堆栈。
猜你喜欢
  • 2019-09-26
  • 1970-01-01
  • 1970-01-01
  • 2015-04-28
  • 2021-11-27
  • 1970-01-01
  • 1970-01-01
  • 2015-11-23
  • 2020-12-21
相关资源
最近更新 更多