实现Excel UDF的VB.NET COM服务器不可使用可选的Excel.Range调用 版本

实现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

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 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
    如果我使用所有必需参数加上可选参数(包括范围G)调用该函数,则得到#VALUE!。在这种情况下,调试器中第一行代码的断点不会被命中:该函数只是不被调用
  • 所有参数
    工作
  • 除可选参数G以外的所有参数
    不起作用
  • 我无法识别可能导致此问题的任何Windows更新。我用同样的方式实现了其他COM服务器,它们没有可选范围,也没有出现此问题

    我尝试了
    可选的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