我正在研究COM加载项和Excel Automation加载项的库,其核心代码用C#编写。我想为该函数设置一个可选参数,并且我知道这对于C#和VBA甚至Excel WorksheetFunction都是合法的。但是我发现,最后,可选参数仅适用于COM和Automation加载项,这意味着,如果首先运行一个加载项,则效果很好,但另一个加载项则无效。
下面请参见示例:
在VS 2013解决方案中,我有两个项目:一个叫做TestVBA
,另一个叫做TestExcel
。
TestVBA
用于COM加载项,并通过“ Excel 2013加载项”构建,并且有两个.cs
文件:
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
}
}
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
文件:
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();
}
}
}
TestExcel.cs
此文件是Excel Automation加载项的主要计算文件。代码是
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()
,它们都工作得很好,并给出正确的结果5
和2
。但是,当我尝试通过
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文件并再次打开它时,这次我仍然通过上述VBA代码首先测试COM加载项,两个调用都工作得很好。然后,当我测试过去两个可以正常工作的电子表格功能时,只有第一个功能良好,而第二个参数丢失了=TestAddExcel()
的操作会失败,并返回#VALUE!
。
如果有人可以帮助解决这个奇怪的问题,那就太好了。
答案 0 :(得分:3)
我不确定在不注册COM的情况下如何引用类库?我现在知道,您正在使用后期绑定。我不知道您可以这样做(以为不会让您) 怀疑是问题所在,它也与Type mismatch
错误匹配。
按照我的canonical answer here on the 3 methods to call .Net from Excel or VBA中的第二个解决方案进行操作,并确保您注册COM :
单击“生成”选项卡,然后选中“注册COM Interop”复选框。此时,如果您在Windows Vista或更高版本上运行,则需要执行额外的步骤。 Visual Studio必须以管理员权限运行才能注册COM Interop。保存您的项目并退出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
答案 1 :(得分:0)
这是一片黑暗,但是您是否可以创建该方法的重载版本来模仿在C#具有可选参数之前可以完成此操作的方式,看看是否可行?
public int TestAddExcel(int a, int b)
{
return a + b;
}
public int TestAddExcel(int a)
{
return a + 1;
}
public int TestAddExcel()
{
return 2;
}