2

我使用regasm.exe注册了一个 COM DLL ,现在我正在尝试编写一个使用 DLL 中的类的 VBA 脚本。DLL 是ExcelDataReaderLibrary.dll. 在 C# 源代码中,该类描述如下(包括来自此库的代码):

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
using System.Data;
using Excel;

namespace ExcelDataReaderLibrary
{
    public class ExcelDataReader
    {
        public void readSheet(string filePath,string sheetName,string outPath)
        {
            // code for method here
        }
    }
}

我的assembly.cs文件包括以下内容:

[assembly: ComVisible(true)]

[assembly: Guid("b1e78f8f-9ab0-46d8-beac-b843656aacdb")]

当我打开 VBA 编辑器并转到参考时,我看到了ExcelDataReaderLibrary. 请注意,与此引用关联的文件是ExcelDataReaderLibrary.tlb,而不是ExcelDataReaderLibrary.dll。在我检查了这个参考之后,我想ExcelDataReader在 VBA 中创建和使用一个对象,如下所示:

Sub x()    
 Dim xyz As New ExcelDataReaderLibrary.ExcelDataReader
 xyz.readSheet "c:\mypath\testfile.xlsx", "Sheet1", "c:\outputPath"
End Sub

该对象已成功创建,但readSheet出现此错误:

Automation error
The system cannot find the file specified.

此外,ExcelDataReaderLibrary命名空间有 Intellisense,但对象没有 Intellisense ExcelDataReader。我想我的班级已经注册但没有注册它的方法——我必须对 Guid 做一些不同的事情吗?如何从我的 VBA 代码中调用该方法?

4

1 回答 1

7

对于您的问题的第一部分,问题是mscoree.dll需要从调用进程的位置/上下文中找到您的程序集,并且您的对象的程序集通常不在您的进程(技术上,.NET 融合)将查看的文件夹中. 要告诉 .NET 在哪里可以找到程序集,您有两种选择:

  • 使用/codebase参数 of REGASM(如 Hans 所述),它会在注册表中留下指向程序集位置的提示;或者

  • 对您的程序集进行强签名并将其添加到 GAC,mscoree.dll总能在其中查找和找到它。

请注意,程序集的所有依赖项必须同样“可查找”。

我必须警告您,Microsoft 非常严厉地阻止人们使用该/codebase技术。他们认为它被设计为一种开发/调试技术,不应在生产模式下使用。我不确定我是否完全理解他们的理由。您可能应该查看REGASMMSDN 中的文档和其他参考资料,并对此做出自己的决定。我个人并不觉得将我的对象添加到 GAC 比使用/codebase.

关于您问题的第二部分,关于没有获得 IntelliSense:您的问题是,默认情况下,.NET 生成的 COM 类型库公开 pure dispinterfaces(它们仅实现IDispatch,并且类型库甚至不会列出dispinterface成员)。在 VB6 术语中,您的类公开为objects,并且所有方法名称和参数都必须在运行时确定。

COM 类的生成由应用于您的类的属性控制,称为ClassInterfaceAttribute.

以下是选项:

  • ClassInterfaceType.AutoDispatch:这是默认设置,因此目前适用于您的班级。你的类被暴露为一个纯粹的调度接口,并且只能是后期绑定的。没有智能感知。TLB 甚至没有列出 dispinterface 成员,因此后期绑定的客户端永远无法缓存有关您的类公开内容的任何详细信息。

这允许最大的灵活性来添加、更改和删除成员而不受惩罚,至少在不严重破坏客户的情况下。我称之为“脚本模式”。

  • ClassInterfaceType.None:这是更严格的模式,它要求您更明确地公开您的对象,就像您在 IDL/C++ 中所做的那样。您应该使用要公开的方法声明多个接口之一,并使该类显式地从接口继承。如果您从多个接口继承,第一个将被选为[default]接口,但您可能应该通过ComDefaultInterfaceAttribute. 如果您不从任何接口继承,您的 CoClass 将直接从 IDispatch 继承。(所有作为 COM 对象公开的 .NET 类都通过 IDispatch 公开)。

这是我的首选模式,但在 COM 编程方面我更像是一个传统主义者。只要您确实从接口显式声明和继承,您就可以获得 IntelliSense。显然,您只能调用接口中列出的成员。我称之为“严格模式”或“IDL 模式”。

  • ClassInterfaceType.AutoDual这会自动为您生成一个 COM 接口,其中包含您公开的方法的所有详细信息。这意味着您可以获得 IntelliSense,而不必担心创建显式接口。然而,版本控制是一种皇家痛苦。在重新编译和/或重新注册您的对象之前,您必须非常小心地停止所有客户端,否则如果您的方法签名以任何方式发生变化,您将陷入困境。我称之为“VB6 模式”,因为对我来说它看起来很像 VB6 为您所做的(VB6 上的 COM 版本控制也是一种痛苦)。

微软也强烈不鼓励人们使用AutoDual,我想是因为生成界面的更改可能更容易发生,而您不会注意到。我实际上还没有机会使用它,但我不确定它是否比None.

总之:如果你想获得 IntelliSense,你需要应用到你的类[ClassInterface(ClassInterfaceType.None)](并将你的方法放在你显式实现的接口中),或者[ClassInterface(ClassInterfaceType.AutoDual)]。无论哪种方式,在对程序集进行更改之前,您都必须非常小心地阻止您的客户(甚至可能“删除”和“重新添加”引用)。

于 2013-05-24T05:19:25.570 回答