【问题标题】:Optional Argument of COM Add-in vs Automation Add-in Written in C#COM 插件与用 C# 编写的自动化插件的可选参数
【发布时间】:2018-08-22 20:56:06
【问题描述】:

我正在开发一个 COM 插件库和 Excel 自动化插件库,其核心代码是用 C# 编写的。我想为函数设置一个可选参数,我知道这对于 C# 和 VBA 甚至 Excel WorksheetFunction 都是合法的。但我发现最后可选参数只适用于 COM 和自动化插件,这意味着如果一个插件首先运行,然后运行良好,但另一个的可选参数将不起作用。

请看下面的例子:

在VS 2013解决方案中,我有两个项目:一个叫TestVBA,另一个叫TestExcel

TestVBA 用于 COM 插件,通过“Excel 2013 插件”构建,有两个.cs 文件:

  1. ThisAddIn.cs

这个文件是自动生成的,稍作修改。代码是

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using Excel = Microsoft.Office.Interop.Excel;
using Office = Microsoft.Office.Core;
using Microsoft.Office.Tools.Excel;

namespace TestVBA
{
    public partial class ThisAddIn
    {
        private void ThisAddIn_Startup(object sender, System.EventArgs e)
        {
        }

        private void ThisAddIn_Shutdown(object sender, System.EventArgs e)
        {
        }

        private ExcelVBA oExcelVBA;

        protected override object RequestComAddInAutomationService()
        {
            if (oExcelVBA == null)
            {
                oExcelVBA = new ExcelVBA();
            }
            return oExcelVBA;
        }
        #region VSTO generated code

        /// <summary>
        /// Required method for Designer support - do not modify
        /// the contents of this method with the code editor.
        /// </summary>
        private void InternalStartup()
        {
            this.Startup += new System.EventHandler(ThisAddIn_Startup);
            this.Shutdown += new System.EventHandler(ThisAddIn_Shutdown);
        }

        #endregion
    }
}
  1. TestVBA.cs

该文件是COM插件的主要计算文件。代码是

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using System.Runtime.InteropServices;
using Excel = Microsoft.Office.Interop.Excel;

using System.Reflection;


namespace TestVBA
{
    [ComVisible(true)]
    [ClassInterface(ClassInterfaceType.AutoDual)]
    public class ExcelVBA
    {
        public int TestAddVBA(int a = 1, int b = 1)
        {
            return a + b;
        }
    }
}

另一个 TestExcel 用于 Excel 自动化加载项并通过 C#“类库”构建,并且有两个 .cs 文件:

  1. BaseUDF.cs

这个文件定义了两个属性的装饰。代码是

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using System.Runtime.InteropServices;
using Microsoft.Win32;

namespace BaseUDF
{
    [ClassInterface(ClassInterfaceType.AutoDual)]
    [ComVisible(true)]
    public abstract class BaseUDF
    {
        [ComRegisterFunctionAttribute]
        public static void RegisterFunction(Type type)
        {
            // Add the "Programmable" registry key under CLSID.
            Registry.ClassesRoot.CreateSubKey(
              GetSubKeyName(type, "Programmable"));
            // Register the full path to mscoree.dll which makes Excel happier.
            RegistryKey key = Registry.ClassesRoot.OpenSubKey(
              GetSubKeyName(type, "InprocServer32"), true);
            key.SetValue("",
              System.Environment.SystemDirectory + @"\mscoree.dll",
              RegistryValueKind.String);
        }

        [ComUnregisterFunctionAttribute]
        public static void UnregisterFunction(Type type)
        {
            // Remove the "Programmable" registry key under CLSID.
            Registry.ClassesRoot.DeleteSubKey(
              GetSubKeyName(type, "Programmable"), false);
        }

        private static string GetSubKeyName(Type type,
          string subKeyName)
        {
            System.Text.StringBuilder s =
              new System.Text.StringBuilder();
            s.Append(@"CLSID\{");
            s.Append(type.GUID.ToString().ToUpper());
            s.Append(@"}\");
            s.Append(subKeyName);
            return s.ToString();
        }

        // Hiding these methods from Excel.
        [ComVisible(false)]
        public override string ToString()
        {
            return base.ToString();
        }

        [ComVisible(false)]
        public override bool Equals(object obj)
        {
            return base.Equals(obj);
        }

        [ComVisible(false)]
        public override int GetHashCode()
        {
            return base.GetHashCode();
        }
    }
}
  1. TestExcel.cs

此文件是 Excel 自动化插件的主要计算文件。代码是

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using Microsoft.Win32;
using System.Runtime.InteropServices;
using Excel = Microsoft.Office.Interop.Excel;
using Extensibility;

namespace TestExcel
{
    [Guid("7127696E-AB87-427a-BC85-AB3CBA301CF3")]
    [ClassInterface(ClassInterfaceType.AutoDual)]
    [ComVisible(true)]
    public class TestExcel : BaseUDF.BaseUDF
    {
        public int TestAddExcel(int a = 1, int b = 1)
        {
            return a + b;
        }
    }
}

构建完成后,这两个插件已经在系统中注册,在Excel中我们可以成功使用了。

对于自动化加载项,我们在电子表格中将它们称为=TestAddExcel(2,3)=TestAddExcel(),它们都工作得很好,并给出了正确的结果52。但是,当我尝试通过

调用 COM 加载项时
Sub TestVBA_Click()

Dim addIn As COMAddIn
Dim TesthObj As Object

Set addIn = Application.COMAddIns("TestVBA")
Set TestObj = addIn.Object

Range("Output").Value2 = TestObj.TestAddVBA(2, 3)
Range("Output").Offset(1, 0).Value2 = TestObj.TestAddVBA()

End Sub

所有参数都存在的第一个调用运行良好,但第二个缺少参数的调用显示错误Type mismatch

有趣的是,当我关闭测试excel文件并再次打开它时,这次我先测试COM插件,仍然通过上面的VBA代码,两个调用都很好。然后,当我测试两个曾经运行良好的电子表格函数时,只有第一个很好,第二个缺少参数=TestAddExcel() 失败并显示#VALUE!

如果有人能帮助解决这个奇怪的问题,那就太好了。

【问题讨论】:

  • 您好,您需要我的回答吗?
  • @JeremyThompson,非常感谢您的回答。然而,我的妻子这周刚刚生下了我们的孩子。这几天完全是一团糟。我会尽快尝试您的建议并尽快回复您。

标签: c# excel vsto add-in excel-addins


【解决方案1】:

我不确定你是如何在没有注册 COM 的情况下引用类库的?我现在明白了,您正在使用后期绑定。我不知道你能做到这一点(不认为它会让你)并且怀疑这是问题所在,它也符合 Type mismatch 错误。

按照我的canonical answer here on the 3 methods to call .Net from Excel or VBA 中的第二个解决方案并确保您注册 COM

单击“构建”选项卡并选中“注册 COM 互操作”复选框。如果您在 Windows Vista 或更高版本上运行,此时您还有一个额外的步骤。必须以管理员权限运行 Visual Studio 才能注册 COM 互操作。保存您的项目并退出 Visual Studio。然后在开始菜单中找到 Visual Studio 并右键单击它并选择“以管理员身份运行”。在 Visual Studio 中重新打开您的项目。然后选择“构建”来构建加载项。


如果上述方法不起作用,请按照我的答案中的第三个解决方案并参考自动化插件并使用早期绑定,我已经对此进行了测试,并且效果很好:

Sub TestVBA1_Click()

Dim addIn As COMAddIn
Dim TesthObj As Object

Set addIn = Application.COMAddIns("TestVBA")
Set TestObj = addIn.Object

Debug.Print TestObj.TestAddVBA(2, 3)
Debug.Print TestObj.TestAddVBA()


Dim dotNetClass As TestExcel.TestExcel
Set dotNetClass = New TestExcel.TestExcel

Debug.Print dotNetClass.TestAddExcel(7, 3)
Debug.Print dotNetClass.TestAddExcel()

End Sub

【讨论】:

  • 嗨 Jeremy,我认为 Visual Studio 在使用“Excel 加载项”项目类型创建时在机器上完成了所有注册(自从我创建任何此类工作以来已经有一段时间了)......我不知道 OP 是否会解决无参数函数调用的问题。
  • 在后台 Visual Studio 使用 Regasm 进行注册,请参阅链接答案中的 cmets。我相信 OP 会从我的例子中获得运气:)
  • @JeremyThompson,非常感谢您的建议。您的两种解决方案都非常有效。但我仍然有点想知道这背后的理由是什么?正如 MacroMarc 所说,它会由 VS 自己自动注册。
  • Excel 加载项 TestVBA 在构建时自行注册,但您需要勾选 Class Library TestExcel 的 Register for COM 复选框。没有它,它在 VB Editors References 列表框中是不可见的。所以与被发现有关。
  • 如果你们想走得更远,请在它发生时快速运行 ProcessMonitor。干杯
【解决方案2】:

这是一个彻头彻尾的尝试,但您能否创建该方法的重载版本,以模仿在 C# 具有可选参数之前完成此操作的方式,看看是否可行?

public int TestAddExcel(int a, int b)
{
    return a + b;
}

public int TestAddExcel(int a)
{
    return a + 1;
}

public int TestAddExcel()
{
    return 2;
}

【讨论】:

  • 感谢您的建议。我已经尝试过,但它不起作用。如果我首先运行 Excel 自动化加载项,这两个函数都运行良好,然后我运行 COM 加载项,第二个函数出现相同的错误“类型不匹配”。但是,当我第一次运行 COM 加载项时,第二个函数直接显示错误“无效的过程调用或参数”。然后我运行 Excel 自动化插件,第二个函数再次显示错误“类型不匹配”。
  • COM 不支持方法重载。需要时的典型做法是为重载添加后缀,例如 TestAddExcel2、TestAddExcel3 等。
猜你喜欢
  • 2011-01-01
  • 2019-01-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-10-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多