其中一些很容易纠正,而其他部分则相当困难。但是,如果您愿意投入时间,所有这些都是可行的。
你写了:
我还看到了 Equals、GetHashCode、GetType 和 ToString(它们对于 Excel 用户来说是相当不可取的)
是的,同意,这绝对是不可取的,但可以防止。发生这种情况是因为您的类是从“System.Object”继承的,就像所有 .NET 类一样,并且您向 COM 公开的默认接口包括这些成员。例如,如果您使用“ClassInterfaceAttribute”,使用设置“ClassInterfaceType.AutoDual”,就会发生这种情况。
例如在 C# 中:
[ClassInterface(ClassInterfaceType.AutoDual)]
在 VB.NET 中:
<ClassInterface(ClassInterfaceType.AutoDual)>
但是,应避免使用“ClassInterfaceType.AutoDual”,以防止从“System.Object”继承的成员被暴露(以及防止将来出现潜在的版本问题)。相反,定义您自己的接口,在您的类中实现该接口,然后使用值为“ClassInterfaceType.None”的“ClassInterface”属性标记您的类。
例如,使用 C#:
[ComVisible(true)]
[Guid("5B88B8D0-8AF1-4741-A645-3D362A31BD37")]
public interface IClassName
{
double AddTwo(double x, double y);
}
[ComVisible(true)]
[Guid("010B0245-55BB-4485-ABAF-46DF4356DB7B")]
[ProgId("ProjectName.ClassName")]
[ComDefaultInterface(typeof(IClassName))]
[ClassInterface(ClassInterfaceType.None)]
public class ClassName : IClassName
{
public double AddTwo(double x, double y)
{
return x + y;
}
}
使用 VB.NET:
<ComVisible(True)> _
<Guid("5B88B8D0-8AF1-4741-A645-3D362A31BD37")> _
Public Interface IClassName
Function AddTwo(ByVal x As Double, ByVal y As Double) As Double
End Interface
<ComVisible(True)> _
<Guid("010B0245-55BB-4485-ABAF-46DF4356DB7B")> _
<ProgId("ProjectName.ClassName")> _
<ComDefaultInterface(GetType(IClassName))> _
<ClassInterface(ClassInterfaceType.None)> _
Public Class ClassName
Implements IClassName
Public Function AddTwo(ByVal x As Double, ByVal y As Double) As Double _
Implements IClassName.AddTwo
Return x + y
End Function
End Class
通过使用值为 'ClassInterfaceType.None' 的 'ClassInterfaceAtribute',继承的 'System.Object' 成员被排除,因为类的接口不是 COM 可见的。相反,只有实现的接口(本例中为“IClassName”)被导出到 COM。
以上也使用了“ComDefaultInterfaceAttribute”。这不是很重要,如果您只实现一个接口(如本例中所示),它什么也不做,但如果您稍后添加一个接口,例如 IDTExtensibility2,这是一个好主意。
有关这方面的更多详细信息,请参阅:
(1) Andrew Whitechapel的托管自动化插件。
(2) Gabhan Berry用 C# 编写自定义 Excel 工作表函数。
好的,现在到最难的部分。你写了:
选择我的 COM 服务器会打开
函数参数[1] 对话框,其中没有参数信息和函数描述。
我可以提供功能的描述吗?
我可以提供参数的描述吗?
我可以为我的函数提供一个类别名称,以便获得比 ProgID 更好的东西吗?
这里最简单的方法是使用Application.MacroOptions方法。此方法使您能够提供函数的描述并指定要在哪个类别下显示它。不幸的是,这种方法不允许您为函数参数指定任何信息,但允许您这样做的技术非常复杂,我将在稍后介绍。[更正:“Application.MacroOptions”方法仅适用于通过 VBA 创建的 UDF,不能用于自动化加载项。继续阅读以了解更复杂的方法来处理自动化插件中包含的 UDF 的注册——Mike Rosenblum 2009.10.20]
请注意,Excel 2003的帮助文件和 Excel 2007的帮助文件声明可以向类别参数提供一个字符串,以便提供您选择的自定义类别名称。但是请注意,Excel 2002 的帮助文件没有。我不知道这是否是 Excel 2002 帮助文件中的遗漏,或者这是否是 Excel 2003 的新功能。我猜是后者,但您必须进行测试才能确定。
将参数信息输入函数向导的唯一方法是使用涉及“Excel.Application.ExecuteExcel4Macro”方法的相当复杂的技术。但请注意:许多 Excel MVP 一直在努力使用这种方法,并且未能产生可靠的结果。不过,最近,Jan Karel Pieterse (JKP) 似乎已经解决了这个问题,并在此处发布了详细信息:Registering a User Defined Function with Excel。
浏览那篇文章,您会发现它不适合胆小的人。部分问题是他为 VBA / VB 6.0 编写了它,因此所有代码都必须转换为 VB.NET 或 C#。然而,关键命令是 'Excel.Application.ExecuteExcel4Macro' 方法,它暴露给 .NET,所以一切都应该正常工作。
然而,实际上,我更喜欢使用“Excel.Application.MacroOptions”方法,因为它简单可靠。它不提供参数信息,但我还没有强烈的需要激励我采用“ExecuteExcel4Macro”方法。
所以,祝你好运,但我的建议是使用“MacroOptions”,除非你按小时付费。;-)
——迈克
休的答复的后续行动
我尝试在 Sub New() 中调用 Application.MacroOptions。
否 Sub New() 半可接受:函数列在 ProgID 类别下。
Shared Sub New() 不可接受:构建时错误。无法注册程序集“...\Foo.dll”。调用的目标已引发异常。
Sub New() 不可接受:类别未在“插入函数”对话框中列出。我怀疑这对于 MacroOptions 和 Charles 推荐的更复杂的路线都是一个问题。
向 COM 公开您的类时,您不能使用共享(也称为“静态”)类或构造函数,因为 COM 不了解这个概念,因此它无法编译——正如您所发现的!您可能可以将值为“False”的“COMVisibleAttribute”应用于共享构造函数,以至少允许它编译。但无论如何,在这种情况下这对你没有帮助......
尝试通过自动化插件本身注册您的自动化插件可能会很棘手。我意识到这是可取的,以便将其保持为单个独立组件,但这可能是不可能的。或者至少这并不容易。
问题是自动化加载项是按需加载的。也就是说,在 Excel 尝试从您的自动化加载项访问第一个工作表函数之前,它们并不真正存在。与此相关的有两个问题:
(1) 如果您将注册代码放在类的构造函数中,那么根据定义,在第一次调用该函数之前,您的函数向导信息不存在。
(2) 当 Excel 未准备好接受自动化命令时,您的构造函数可能正在执行。例如,当用户开始键入在自动化加载项中定义的用户定义函数 (UDF) 之一的名称时,自动化加载项通常是按需加载的。结果是,当您的自动化加载项首次加载时,单元格处于编辑模式。如果在编辑模式期间您的构造函数中有自动化代码,许多命令将失败。我不知道“Excel.Application.MacroOptions”或“Excel.Application.Excel4Macro”方法是否存在问题,但是当单元格处于编辑模式时尝试执行时,许多命令会阻塞。如果因为在功能向导打开时调用自动化加载项而第一次加载自动化加载项,我不知道这些方法是否可以正常工作。
如果您希望自动化插件完全独立而没有其他支持,则没有简单的解决方案。但是,您可以创建一个托管 COM 加载项,该加载项将在 Excel 启动时通过“Excel.Application.MacroOptions”或“Excel.Application.Excel4Macro”方法为您注册自动化加载项。托管 COM 加载项类可以与自动化加载项在同一个程序集中,因此您仍然只需要一个程序集。
顺便说一句,您甚至可以使用 VBA 工作簿或 .XLA 插件来执行相同的操作 - 使用 VBA 中的 Workbook.Open 事件来调用注册码。当 Excel 启动时,您只需要一些东西来调用您的注册码。在这种情况下使用 VBA 的优点是您可以按原样使用 Jan Karel Pieterse 的用 Excel 注册用户定义函数一文中的代码,而无需将其转换为 .NET。
从好的方面来说,Mike 建议创建一个接口来实现确实消除了暴露出来的烦人的额外方法。
大声笑,我很高兴有些东西奏效了!
这篇 2007 年初的 Microsoft 文章(通过 Mike 的链接)似乎是关于该主题的相当完整的答案:
自动化插件和函数向导
每个自动化加载项在 Excel 函数向导中都有自己的类别。类别名称是插件的 ProgID;您不能为自动化插件功能指定不同的类别名称。此外,无法在函数向导中为自动化加载项函数指定函数描述、参数描述或帮助。
这仅是对“Excel.Application.MacroOptions”方法的限制。(我很抱歉,当我在上面写我的原始答案时,我忘记了“Excel.Application.MacroOptions”方法对自动化插件的限制。)更复杂的“Excel.Application”。但是, ExecuteExcel4Macro ' 方法绝对适用于自动化插件。它也应该适用于 .NET(“托管”)自动化加载项,因为 Excel 不知道它是加载通过 VB 6.0/C++ 创建的 COM 自动化加载项还是使用创建的托管 COM 自动化加载项VB.NET/C#。机制与栅栏的 COM 端完全相同,因为 Excel 不知道 .NET 是什么,或者 .NET 甚至存在。
That said, the 'Excel.Application.Excel4Macro' approach would definitely be a lot of work...