实现Excel UDF的VB.NET COM服务器不可使用可选的Excel.Range调用 版本
Excel 2007(12.0.6425.1000)SP2、Visual Studio 2008和VB.NET、Windows Vista 收尾 我有一个用VB.NET编写的Excel自定义项,它有以下签名:实现Excel UDF的VB.NET COM服务器不可使用可选的Excel.Range调用 版本,.net,excel,com,.net,Excel,Com,Excel 2007(12.0.6425.1000)SP2、Visual Studio 2008和VB.NET、Windows Vista 收尾 我有一个用VB.NET编写的Excel自定义项,它有以下签名: Public Function NonExcelName(ByVal A As Integer, _ ByRef B As Range, ByRef C As Range, _ Optional
Public Function NonExcelName(ByVal A As Integer, _
ByRef B As Range, ByRef C As Range, _
Optional ByVal D As Integer = 1, _
Optional ByVal E As Double = 0, _
Optional ByVal F As Range = Nothing, _
Optional ByVal G As Boolean = True, _
Optional ByVal H As Integer = 1) As Object
我在Excel中使用以下可选参数调用它时没有遇到任何问题:
=NonExcelName(99,$D$2:$D$65,$B$2:$B$65,12,,,,0)
If optRange Is Nothing Then
Return "<No Range Provided>"
Else
Return optRange.Address
End If
几个月来一直到昨晚。现在,这是一个问题。不知道为什么。病征包括:
如果我提供了所有必需的参数并省略了可选参数,那么该函数是可调用的:我将在VB.NET调试器中命中一个断点
如果我提供了所有必需的参数,加上可选范围为G的可选参数,那么该函数是可调用的:我将在VB.NET调试器中找到一个断点
如果我使用所有必需参数加上可选参数(包括范围G)调用该函数,则得到#VALUE!。在这种情况下,调试器中第一行代码的断点不会被命中:该函数只是不被调用
工作
不起作用
可选的ByVal F,因为Range=Nothing
没有效果
我还没有将源代码回滚到昨晚。下一步我会试试的
球场 我违反了COM/.NET交互的一些规则吗?Windows世界中有什么改变了吗?如何解释Excel/COM拒绝使用逗号分隔的可选参数调用此函数
编辑2009-10-07:将整型参数更改为双精度参数没有帮助。将
可选ByVal F As Range=Nothing
更改为可选ByRef F As Range=Nothing
没有帮助
编辑2009-10-10:我将UDF从DB更改为非ExcelName,以强调它与Excel函数名不冲突的事实。真正的名字是在网上发布的定制软件,所以我不想透露这个名字
编辑2009-10-11:我已经运行了Mike的代码,得到的结果与我自己的代码基本相同:当我用逗号省略可选的Range参数时,它失败了 我可以输入强制参数,我很好,但是一旦我用逗号省略了可选参数(不管是否输入其余参数),函数调用就会生成一个#值 编辑2009-10-11:可能是这样的:可能我的可选范围是作为字符串传递的,而不是作为零传递的。我将如何进行测试/确认?我似乎没有在代码中找到断点
编辑2009-10-12:哇。修复分为两部分 (1) VB.NET IIf()语句的使用 这不像C/C++/C#中的三元运算符那样短路。您不能编写这样的代码并期望它在VB/VBA/VB.NET中工作:
Return IIf(optRange Is Nothing, "<No Range Provided>", optRange.Address)
这就是:
Function OptionalRange3(Optional ByVal optRange1 As Object = Nothing, Optional ByVal optRange2 As Object = Nothing) As Object
编辑2009-10-12:我探讨了多重范围解析假设。有意思,但据我所知,不是原因。此函数签名不应存在此问题,因为它中间有一个整数:
Function OptionalRange4(Optional ByVal optRange1 As Excel.Range = Nothing, _
Optional ByVal optInt As Integer = 0, _
Optional ByVal optRange2 As Excel.Range = Nothing) As Object _
Implements IFunctions.OptionalRange4
但它却产生了价值!对于=可选范围4(,12,)
我有一个修正(声明可选对象,而不是范围,并强制转换参数),但我不知道为什么会出现问题。更新了关于可选范围参数的答案。 好的,在与一些Excel MVP讨论了这个问题之后,我可以向这个奇怪的问题补充几点,关于在使用VB.NET创建的用户定义函数(UDF)中使用可选范围参数 (1) 首先,这似乎是一个关于Excel如何调用.NET用户定义函数的微妙问题;无法使用VBA复制此问题。例如,以下用VBA编写的函数不会出现相同的问题:
Function OptionalRange4(Optional ByVal optRange1 As Excel.Range = Nothing, _
Optional ByVal optInt As Integer = 0, _
Optional ByVal optRange2 As Excel.Range = Nothing) _
As Variant
Dim arg1 As String
Dim arg2 As String
Dim arg3 As String
If optRange1 Is Nothing Then
arg1 = "<Nothing>"
Else
arg1 = optRange1.Address
End If
arg2 = CStr(optInt)
If optRange2 Is Nothing Then
arg3 = "<Nothing>"
Else
arg3 = optRange2.Address
End If
OptionalRange4 = arg1 + "|" + arg2 + "|" + arg3
End Function
因此,您可以使用的两种解决方案似乎是:
(a) 将参数类型从范围更改为对象,然后将对象强制转换为代码中的范围。这似乎是最干净、最简单的方法
(b) 创建一个VBA包装器作为前端,然后调用Visual Basic.NET代码。我肯定这不是你想要的,但我想我应该提一下。无论如何,如果希望使用VSTO中的UDF,至少从VisualStudio2008开始,这种方法是强制性的。Paul Stubbs的文章描述了这种方法
这就是我能补充的,休。这是Excel调用试图通过.NET互操作方式工作的一个细微缺陷。我不知道为什么它会失败,但它确实失败了。幸运的是,解决这一问题的办法并不繁重
--迈克
关于可选范围参数的先前回答
我还是不知道为什么我不能
这项工作:
Function OptionalRange2(Optional ByVal optRange1 As Excel.Range =
没什么_
可选的ByVal optRange2作为Excel.Range=
无)作为对象_
实现IFunctions.OptionalRange2
两者=可选范围2(E2:E7,F2:F7)
First=可选范围2(E2:E7,)秒=可选范围2(,F2:F7)
两者都不=可选范围2()
都不使用逗号=可选范围2(,) [同时调用两个和两个都未成功。]放置 在逗号中更改调用。就像 如果没有逗号,Excel将发送两个 除了逗号,什么都没有 还有别的事-- •发送 System.Type.Missing/System.Reflection.Missing.Value •调用Range.Value(),默认值 函数在范围上,传递一个参数 不同类型的 不管怎样,都是 就好像有一个类型不匹配 运行时,因为它无法 进入调试程序
=MyFunction((A1:C3,D4:E5), G11)
Function OptionalRange2(Optional ByVal optRange1 As Excel.Range =
Function DualOptionalRanges(Optional ByVal optRange1 As Excel.Range = Nothing, _
Optional ByVal optRange2 As Excel.Range = Nothing) _
As Object _
Implements IFunctions.DualOptionalRanges
Dim arg1 As String = If(optRange1 IsNot Nothing, optRange1.Address, "<Nothing>")
Dim arg2 As String = If(optRange2 IsNot Nothing, optRange2.Address, "<Nothing>")
Return arg1 + "|" + arg2
End Function
Function DualOptionalVariants(Optional ByVal optVariant1 As Object = Nothing, _
Optional ByVal optVariant2 As Object = Nothing) _
As Object _
Implements IFunctions.DualOptionalVariants
Dim range1 As Excel.Range
Try
range1 = CType(optVariant1, Excel.Range)
Catch ex As Exception
range1 = Nothing
End Try
Dim range2 As Excel.Range
Try
range2 = CType(optVariant2, Excel.Range)
Catch ex As Exception
range2 = Nothing
End Try
Dim arg1 As String
If range1 IsNot Nothing Then
arg1 = range1.Address
Else
arg1 = If(optVariant1 IsNot Nothing, optVariant1.ToString(), "<Nothing>")
End If
Dim arg2 As String
If range2 IsNot Nothing Then
arg2 = range2.Address
Else
arg2 = If(optVariant2 IsNot Nothing, optVariant2.ToString(), "<Nothing>")
End If
Return arg1 + "|" + arg2
End Function
Imports System
Imports System.Collections.Generic
Imports System.Runtime.InteropServices
Imports Microsoft.Win32
Imports System.Text
Imports Excel = Microsoft.Office.Interop.Excel
<ComVisible(True)> _
<Guid("7F1A3650-BEE4-4751-B790-3A527195C7EF")> _
Public Interface IFunctions
' * User-Defined Worksheet Functions Definitions *
Function MyDb(ByVal A As Integer, _
ByRef B As Excel.Range, _
ByRef C As Excel.Range, _
Optional ByVal D As Integer = 1, _
Optional ByVal E As Double = 0, _
Optional ByVal F As Excel.Range = Nothing, _
Optional ByVal G As Boolean = True, _
Optional ByVal H As Integer = 1) As Object
Function OptionalBoolean(Optional ByVal optBoolean As Boolean = False) As Object
Function OptionalDouble(Optional ByVal optDouble As Double = 0.0) As Object
Function OptionalInteger(Optional ByVal optInteger As Integer = 0) As Object
Function OptionalRange(Optional ByVal optRange As Excel.Range = Nothing) As Object
Function OptionalVariant(Optional ByVal optVariant As Object = Nothing) As Object
End Interface
<ComVisible(True)> _
<Guid("FCC8DC2F-4B44-4fb6-93B5-769E57A908A1")> _
<ProgId("VbOptionalParameters.Functions")> _
<ComDefaultInterface(GetType(IFunctions))> _
<ClassInterface(ClassInterfaceType.None)> _
Public Class Functions
Implements IFunctions
' * User-Defined Worksheet Functions */
Function MyDb(ByVal A As Integer, _
ByRef B As Excel.Range, _
ByRef C As Excel.Range, _
Optional ByVal D As Integer = 1, _
Optional ByVal E As Double = 0, _
Optional ByVal F As Excel.Range = Nothing, _
Optional ByVal G As Boolean = True, _
Optional ByVal H As Integer = 1) As Object _
Implements IFunctions.MyDb
Return "MyDb Successfully called"
End Function
Function OptionalBoolean(Optional ByVal optBoolean As Boolean = False) As Object _
Implements IFunctions.OptionalBoolean
Return optBoolean.ToString()
End Function
Function OptionalDouble(Optional ByVal optDouble As Double = 0.0) As Object _
Implements IFunctions.OptionalDouble
Return optDouble.ToString()
End Function
Function OptionalInteger(Optional ByVal optInteger As Integer = 0) As Object _
Implements IFunctions.OptionalInteger
Return optInteger.ToString()
End Function
Function OptionalRange(Optional ByVal optRange As Excel.Range = Nothing) As Object _
Implements IFunctions.OptionalRange
If optRange Is Nothing Then
Return "<No Range Provided>"
Else
Return optRange.Address
End If
End Function
Function OptionalVariant(Optional ByVal optVariant As Object = Nothing) As Object _
Implements IFunctions.OptionalVariant
If optVariant Is Nothing Then
Return "<No Argument Provided>"
Else
Return optVariant.ToString()
End If
End Function
' * automation add-in Registration *
<ComRegisterFunctionAttribute()> _
Public Shared Sub RegisterFunction(ByVal type As Type)
Registry.ClassesRoot.CreateSubKey( _
GetSubKeyName(type, "Programmable"))
Dim key As RegistryKey = _
Registry.ClassesRoot.OpenSubKey( _
GetSubKeyName(type, "InprocServer32"), _
True)
key.SetValue( _
String.Empty, _
System.Environment.SystemDirectory + "\mscoree.dll", _
RegistryValueKind.String)
End Sub
<ComUnregisterFunctionAttribute()> _
Public Shared Sub UnregisterFunction(ByVal type As Type)
Registry.ClassesRoot.DeleteSubKey( _
GetSubKeyName(type, "Programmable"), _
False)
End Sub
Private Shared Function GetSubKeyName(ByVal type As Type, ByVal subKeyCategory As String) As String
Dim sb As System.Text.StringBuilder = New System.Text.StringBuilder()
sb.Append("CLSID\{")
sb.Append(type.GUID.ToString().ToUpper())
sb.Append("}\")
sb.Append(subKeyCategory)
Return sb.ToString()
End Function
End Class
Optional ByVal F As Range = Nothing