对提示这个问题的评论太长了,所以我会在这里问。
在 VBA 代码中使用回调函数指针是否安全(至少从我下面讨论的问题来看);考虑到它与宿主应用程序的关系和解释器的怪癖有点不寻常?
这个关于使用 WinAPI 注册回调的问题讨论了操作系统调用回调函数时导致崩溃的潜在陷阱。具体来说,它询问为什么与工作表交互偶尔会导致 TIMERPROC 回调出现问题。阅读说明后,我立即想到 Excel 对象模型不适用于异步编程。
该问题包括“有效”的代码摘录,但我不会在此处包含它,因为我认为对相关问题的回答实际上更简洁地说明了它:
我能够解决它
On Error Resume Next[...]
Sub TimerProc(ByVal HWnd As Long, ByVal uMsg As Long, ByVal nIDEvent As Long, ByVal dwTimer As Long) On Error GoTo BeforeExit Range("A1") = "test" BeforeExit: End Sub
...这证实了我的想法,即问题可能来自与对象模型 ( Range("A1") = "test") 的异步交互,而它处于某种锁定状态,并且可以通过使用 OERN 捕获错误来解决。
在最初的问题中,@NigelHeffernan给出了一个很好的答案,即当 Windows 调用回调函数时可能导致崩溃的原因,他将其归结为 3 个原因:
[使用 API 的开发人员] 假定已在调用代码中放置了错误处理和应急管理。
这些假设是:
- 该指针后面肯定有一个有效的函数;
- 调用时肯定可用;
- ...而且它不会给调用者带来任何错误。
第 3 点(我认为)与具体问题最相关,我认为是 OP 崩溃的原因。
我担心的是,一些评论(1 , 2)暗示(至少在我看来)第 2 点可能是罪魁祸首;Excel 处于“编辑模式”会以某种方式暂停 VBA 代码的运行,这意味着操作系统无法运行回调,而是出现“Excel 正忙”错误。
我知道 VBA 代码通常在主机的主/UI 线程中运行,这意味着如果 Excel 忙于运行其他 UI 内容,则 VBA 无法执行。但是,读取WM_TIMER消息并最终运行回调的消息循环必须与我的 VBA 代码位于同一线程中;因此,当 Excel 实际上忙于处理其他 UI 消息时,不会调用回调。当然 Excel 对象模型可能处于某种状态,这意味着尝试访问它会引发错误,但问题是向调用者提出错误,而不是回调不可用。
同时,来自 OP 的另一条评论和稍后给出的一些解释暗示他们的问题实际上是第三个因素(第 1 点 - 函数指针无效)。他们所写的似乎表明函数指针后面的 TimerProc 在执行过程中的某个时刻被垃圾收集和失效,OERN 通过持有对它的引用来保持它的活力。
不过,这整个想法对我来说似乎很奇怪。函数不是 VBA 中的一等公民,我不认为它们遵循 COM 引用计数(无论如何都会持有对它们的引用,指针只是一个数字而不是引用?)。我假设函数指针是在编译时设置的,即使在语句和重置按钮之间它们似乎仍然有效End,所以我不确定是什么会让它们超出 OP 建议的范围。我还认为,如果他们这样做了,通常会对 VBA 代码产生影响,而不仅仅是在使用 API 时。所以我不确定这是否真的是一个需要担心的问题。
TL;博士
在异步单线程代码(例如使用SetTimerAPI)中使用回调时,是否存在以下风险:
- 回调在调用之间超出范围,从而提供给 API 的函数指针变得无效
- Excel/主机应用程序在调用回调时处于“忙碌状态”,因此调用失败
我可以想象为什么这些可能是跨线程的问题,但我认为构建 Windows 的整个消息队列系统将防止这成为单线程 VBA 代码的问题。
或者换句话说,我是否正确地认为:
- 宿主应用程序不应影响该线程上 VBA 代码的“可用性”
- 没有理由让函数指针在我点击后变得无效(不可调用)F5- 可能除了一些手动内存恶作剧
哦,请在上下文中阅读这些评论,我很可能完全误解了它们:)(我在这方面没有太多经验)