VBA中的自定义回调

VBA中的自定义回调,vba,callback,Vba,Callback,注意标签:VBA,不是VB6,不是VB.NET 这是MS Access中的VBA特有的。我在一个称为“Enumerable”的模块中构建了一组方法。它做了很多让人想起.NET中可枚举类和接口的事情。我想实现的一件事是ForEach方法,它与.NET非常相似 我构建了一个版本,使用该方法为每个元素调用函数,但Application.Run仅适用于用户定义的方法。例如,以下工作: ' User-defined wrapper function: Public Function MyReplace(

注意标签:VBA,不是VB6,不是VB.NET

这是MS Access中的VBA特有的。我在一个称为“Enumerable”的模块中构建了一组方法。它做了很多让人想起.NET中可枚举类和接口的事情。我想实现的一件事是ForEach方法,它与.NET非常相似

我构建了一个版本,使用该方法为每个元素调用函数,但Application.Run仅适用于用户定义的方法。例如,以下工作:

' User-defined wrapper function:
Public Function MyReplace( _
    Expression As String, Find As String, StrReplace As String, _
    Optional Start As Long = 1, _
    Optional Count As Long = 1, _
    Optional Compare As VbCompareMethod = vbBinaryCompare)
    MyReplace = Replace(Expression, Find, StrReplace, Start, Count, Compare)
End Function

' Using Application.Run to call a method by name
Public Sub RunTest()
    Debug.Print Run("MyReplace", "Input", "In", "Out")
End Sub
运行测试按预期打印“输出”。以下操作不起作用:

Debug.Print Run("Replace", "Input", "In", "Out")
它抛出运行时错误430:“类不支持自动化或不支持预期接口”。这是预期的,因为文档说明Application.Run仅适用于用户定义的方法

VBA确实有一个AddressOf运算符,但它只在将函数指针传递给外部API函数时起作用;使用AddressOf创建的函数指针在VBA中不可用。同样,文档中也提到了这一点(或参见示例)


那么,有没有其他方法来识别和调用使用变量的方法呢?或者,我的回调尝试将通过Application.Run方法局限于用户定义的函数吗?

一周内没有其他答案……为了解决这个问题,这里是我能想到的最好答案:

  • 为了调用
    CallByName
    ,我构建了一个助手模块,将ParamArray解析为各个参数。如果将ParamArray传递给
    CallByName
    ,它会将所有参数合并成一个实际的
    Array
    ,并将其传递给尝试调用的方法中的第一个参数
  • 我构建了两个
    ForEach
    方法:一个调用
    Application.Run
    ,另一个调用
    CallByName
    。如问题中所述,
    Application.Run
    仅适用于用户定义的全局(公共模块)方法。反过来,
    CallByName
    只对实例方法有效,并且需要一个对象参数 这仍然使我无法按名称直接调用内置全局方法(例如
    Trim()
    )。我的解决方法是构建只调用内置全局方法的用户定义包装器方法,例如:

    Public Function FLeft( _
       str As String, _
       Length As Long) As String
        FLeft = Left(str, Length)
    End Function
    
    Public Function FLTrim( _
       str As String) As String
        FLTrim = LTrim(str)
    End Function
    
    Public Function FRight( _
       str As String, _
       Length As Long) As String
        FRight = Right(str, Length)
    End Function
    
    ...etc...
    
    我现在可以用这些来做以下事情:

    ' Trim all the strings in an array of strings
    trimmedArray = ForEachRun(rawArray, "FTrim")
    
    ' Use RegExp to replace stuff in all the elements of an array
    ' --> Remove periods that aren't between numbers
    Dim rx As New RegExp
    rx.Pattern = "(^|\D)\.(\D|$)"
    rx.Global = True
    resultArray = ForEachCallByName(inputArray, rx, "Replace", VbMethod, "$1 $2")   
    

    非常老的问题,但对于那些寻求更通用方法的人,请使用
    stdCallback
    stdLambda
    以及
    stdicalable
    。这些可以作为图书馆的一部分找到


    您将无法使用addressof,但VBA支持类,因此基于接口的回调是可以的<代码>子对象(arg作为字符串,myCallBack作为IWhatever)。。。myCallBack.call(…)将方法塞入一个类中,您可以使用“CallByName”按名称调用它们-由于处理动态数量的参数,这有点弱,另一种选择是我对您实际需要的帮助有点困惑。您的第一段讨论了为…每个实现
    ,但问题的其余部分讨论了如何尝试使用
    应用程序。在VBA函数上运行
    。@MISHAB1-查看任何.NET IEnumerable扩展方法。在大多数情况下,您提供了一个回调来对集合的每个成员执行。我正试图在VBA中做同样的事情。VBA没有委托、函数指针或任何东西。我使用了Application.Run作为替代,但它的适用性有限。您的意思是,
    FLTrim
    这里
    trimmedArray=ForEachRun(rawArray,“FTrim”)
    ?@bonCodigo No、
    Trim
    LTrim
    是独立的VBA函数。在上面的代码中,我展示了
    LTrim
    的包装器示例,而在下面的示例中,我使用
    Trim
    的包装器。我有几个备选建议,您是否仍然对这个问题的答案感兴趣?@Blackhawk,我刚刚通过搜索引擎找到了这个问题,所以我想可以安全地假设答案是肯定的,有人会的interested@user357269有关VBA中第一类函数的实现(例如,可以存储在变量中、传递给函数并匿名调用的函数),请参阅。
    sub Main()
      'Create an array
      Dim arr as stdArray
      set arr = stdArray.Create(1,2,3,4,5,6,7,8,9,10) 'Can also call CreateFromArray
    
      'Demonstrating join, join will be used in most of the below functions
      Debug.Print arr.join()                                                 '1,2,3,4,5,6,7,8,9,10
      Debug.Print arr.join("|")                                              '1|2|3|4|5|6|7|8|9|10
    
      'Basic operations
      arr.push 3
      Debug.Print arr.join()                                                 '1,2,3,4,5,6,7,8,9,10,3
      Debug.Print arr.pop()                                                  '3
      Debug.Print arr.join()                                                 '1,2,3,4,5,6,7,8,9,10
      Debug.Print arr.concat(stdArray.Create(11,12,13)).join                 '1,2,3,4,5,6,7,8,9,10,11,12,13
      Debug.Print arr.join()                                                 '1,2,3,4,5,6,7,8,9,10 'concat doesn't mutate object
      Debug.Print arr.includes(3)                                            'True
      Debug.Print arr.includes(34)                                           'False
    
      'More advanced behaviour when including callbacks! And VBA Lamdas!!
      Debug.Print arr.Map(stdLambda.Create("$1+1")).join          '2,3,4,5,6,7,8,9,10,11
      Debug.Print arr.Reduce(stdLambda.Create("$1+$2"))           '55 ' I.E. Calculate the sum
      Debug.Print arr.Reduce(stdLambda.Create("Max($1,$2)"))      '10 ' I.E. Calculate the maximum
      Debug.Print arr.Filter(stdLambda.Create("$1>=5")).join      '5,6,7,8,9,10
      
      'Execute property accessors with Lambda syntax
      Debug.Print arr.Map(stdLambda.Create("ThisWorkbook.Sheets($1)")) _ 
                    .Map(stdLambda.Create("$1.Name")).join(",")            'Sheet1,Sheet2,Sheet3,...,Sheet10
      
      'Execute methods with lambdas and enumerate over enumeratable collections:
      Call stdEnumerator.Create(Application.Workbooks).forEach(stdLambda.Create("$1#Save")
      
      'We even have if statement!
      With stdLambda.Create("if $1 then ""lisa"" else ""bart""")
        Debug.Print .Run(true)                                              'lisa
        Debug.Print .Run(false)                                             'bart
      End With
      
      'Execute custom functions
      Debug.Print arr.Map(stdCallback.CreateFromModule("ModuleMain","CalcArea")).join  '3.14159,12.56636,28.274309999999996,50.26544,78.53975,113.09723999999999,153.93791,201.06176,254.46879,314.159
      
      'Creating from an object property
      Debug.Print arr.Map(stdCallback.CreateFromObjectProperty(arr,"item", vbGet))  '1,2,3,4,5,6,7,8,9,10
    
      'Creating from an object method
      Debug.Print arr.Map(stdCallback.CreateFromObjectMethod(someObj,"getStuff"))
    End Sub
    
    Public Function CalcArea(ByVal radius as Double) as Double
      CalcArea = 3.14159*radius*radius
    End Function