如何从 Excel VBA 释放进程内 COM 服务器对象

Posted

技术标签:

【中文标题】如何从 Excel VBA 释放进程内 COM 服务器对象【英文标题】:How to release inprocess COM Server object from Excel VBA 【发布时间】:2011-11-12 02:03:11 【问题描述】:

如何强制 Excel (2007) VBA 释放对 COM 服务器对象的引用?

我在 Visual Foxpro 9 SP2 中编写了一个进程内(单实例 DLL)COM 服务器,它是从我的开发机器上的 Excel 2007 VBA 代码实例化的。 Excel 似乎持有对 COM 对象/dll 的引用,即使我将它设置为 = 无。这会阻止我重建 DLL,因为“文件访问被拒绝 TestCOM.dll”消息,直到我退出 Excel,每次我想要进行更改并测试它时都很痛苦。

我将代码简化为一个非常简单的测试设置: VFP9 项目(TestCOM)只有一个 .prg 文件,内容如下

DEFINE CLASS TestClass As Session OLEPUBLIC

ENDDEFINE

VBA代码如下:

Sub Test()

    Set objTest = CreateObject("TestCOM.TestClass")
    Set objTest = Nothing

End Sub

我尝试在 VBA 项目中删除对 COM 服务器库的引用,但这没有任何区别。 我尝试过使用和不使用 DIMing 对象变量,这没有区别。 我已尝试创建一个新的 VFP DLL 项目,但问题仍然存在。

如果我将 VFP 应用程序/dll 构建为 INPROCESS/DLL 并运行 VBA 代码,我会遇到此问题,但如果我将其构建为 OUTOFPROCESS/EXE 并运行 VBA 代码,我不会遇到此问题。

我在COM Object Cleanup 中发现了一个非常相似的问题,除了我的 COM 服务器是用 Visual Foxpro 9 SP2 编写的,而这与 C# 相关,并且 OP 没有详细解释他们如何解决问题,所以我不知道如何绕过它;如果这是可能的话。

【问题讨论】:

对于阅读此线程的任何其他人,我随后意识到,如果项目是作为进程外 (EXE) COM 服务器构建的,那么如果您没有引用 VBA/Excel 中的类型库。 【参考方案1】:

用于从 DLL 中的代码实例化 COM 类的过程是 Excel 调用 COM 库层以使用 ProgID 或 ClassID 查找您的实现。当您有一个 inproc 服务器时,这意味着它会找到您的 DLL 的路径并使用 LoadLibrary 将其加载到您的客户端进程中,然后创建类工厂并调用 DLL 中的方法。所以最终结果是 Excel 在您的 DLL 上调用 LoadLibrary,这会锁定文件,直到 Excel 在句柄上调用 FreeLibrary。

使用 COM 接口您无法控制这一点。您调用 CoCreateInstance() (或从 VBA 中使用 New 或 CreateObject 创建对象,它在下面调用此 Win32 API)。这个实现会处理 LoadLibrary 和其他所有东西,直到你得到一个接口指针来处理。一些应用程序会定期调用 CoFreeUnusedLibraries() 以尝试释放当前未使用的已加载 COM dll。默认类工厂实现维护一个创建的对象计数器,可用于确定 DLL 是否在使用中 - 但这并不总是可靠的,因为 COM 类编写者可能不遵守规则。退出 Excel 显然会释放对文件的锁定。

当您将 COM 类创建为进程外服务器时 - 它存在于单独的可执行文件或 DLL 中,其生命周期的管理方式不同。 Excel 不再锁定 DLL,释放 COM 实例可能会允许宿主进程退出。

您可以将 DLL 转换为用作本地服务器(进程外),方法是安排它由 DllHost 托管。如果您使用 OleView 实用程序并找到您的类 ProgId,那么您可以在代理进程 (dllhost) 中启用托管。自从我这样做以来已经有一段时间了,但是网络上应该有关于使用代理托管的信息。显然,在进程外托管 COM 对象会使一切变慢,并引入各种编组问题的可能性。如果你保持 oleautomation 兼容的接口应该没问题。

【讨论】:

感谢您出色的详细技术回复。所以简而言之,你基本上是在说 COM 接口我受客户端的支配,在这种情况下是 Excel,我没有办法强制释放 DLL?我应该在我的问题中提到,当从 VFP 的另一个副本调用 COM 服务器时不会发生此问题,因此故障似乎与客户端而不是服务器有关。我认为作为一种解决方法,我将在开发期间将此项目构建为进程外服务器 (EXE),然后将其切换到进程内 (DLL) 以进行最终测试和发布。 需要一种额外的成分。 COM 服务器必须从其 DllCanUnloadNow() 入口点返回 S_OK。在 VBA + Foxpro 环境中测试并不简单。【参考方案2】:

添加更短的答案 ....

发布DLL 是一个棘手的问题,对于在COM 中使用的Excel 组件的开发人员来说是一个熟悉的问题。

需要满足两个条件

1) 不要使用早期绑定库引用 (Tools->Reference) ,而是使用后期绑定。早期绑定工具参考将持有锁。

2) 调用CoFreeUnusedLibraries 卸载不再有客户端的COM 服务器。

根据您的示例代码,您已经是后期绑定,但请检查您的参考资料。虽然payyhoyts answer 中提到了第 2) 点,但没有给出代码。

这是一个可复制和可粘贴的声明

Private Declare Sub CoFreeUnusedLibraries Lib "ole32.dll" ()

【讨论】:

以上是关于如何从 Excel VBA 释放进程内 COM 服务器对象的主要内容,如果未能解决你的问题,请参考以下文章

从 Excel 与 VBA 调用时,VBA UDF 给出不同的答案

AutoHotkey使用Excel的Com对象可能导致进程残留问题的原因及解决方案

VBA如何批量抓取数据

访问 VBA - 在大范围内更改 Excel 单元格值的更快方法?

VBA中如何打开一个文件夹内的所有EXCEL文件?

如何使用 Excel VBA 打开 Outlook excel 附件,在特定时间范围内发送到特定 Outlook 文件夹?