繁体   English   中英

VBA - 识别从其他子/过程调用的子和函数

[英]VBA - Identifying subs and functions called from other subs/procedures

苦苦挣扎着为这个冠军争取一个头衔。 我有一个包含大约20个表单/ 10个代码和类模块的项目。 该项目具有多种功能,可以很好地结合在一起以解决特定目的,但也可以进行细分以满足其他项目中的类似需求。 那些其他项目可能不一定需要其他组件。

这方面的一个例子可能是:项目的一部分可以创建一个powerpoint,而项目的另一部分可以发送电子邮件。 该项目使用两者,但另一个项目可能只需要其中一个功能。

我尽力将这些功能分离到自包含的模块中。 但是,我已经放置了常用于单独模块(“全部”)的功能,这样我就可以使用相同的代码,而无需在不同的地方重复。 我是自学成才,如果这不是最好的做法,请告诉我。

问题是我现在希望能够将某些模块从一个项目导出到另一个项目。 我正在构建一个工具,它可以让我将这些模块作为功能齐全的软件包分发,无需在目标文件上进行任何设置即可运行。 为了实现这一点,我需要了解我的子/函数指向的位置。 我的模块有多相互联系? 如果我导出,让我们说PowerPoint模块,该模块是否尝试访问我的“所有”模块中的内容? 如果是这种情况,我还需要将该模块中的任何相关代码导出到新工作簿。

如果第一层程序是目标模块中列出的程序,则第二层程序将是通过T1程序调用的程序。 这意味着从T2程序调用T3程序等等......

如何编写需要更深入更深入的循环函数? 当所有T1程序都已经完全探索时,退出条件肯定是肯定的。

下面的代码只是给我一个目标模块中的过程列表,并将它们添加到一个数组中。

Sub getProcs()

Dim VBProj As VBIDE.VBProject
Dim VBComp As VBIDE.VBComponent
Dim VBMod As VBIDE.CodeModule
Dim lineNum As Integer
Dim lineText As String
Dim lineCount As Integer
Dim procArr As Variant
Dim procName As String
Dim ProcKind As VBIDE.vbext_ProcKind

'Set Target Objects
Set VBProj = ThisWorkbook.VBProject
Set VBComp = VBProj.VBComponents(2)
Set VBMod = VBComp.CodeModule
With VBMod
    If .CountOfLines > 0 Then
        lineNum = .CountOfDeclarationLines + 1
        Do Until lineNum >= .CountOfLines
            procName = .ProcOfLine(lineNum, ProcKind)
            procArr = all_dataArray(procArr, procName)
            lineNum = .ProcStartLine(procName, ProcKind) + _
                    .ProcCountLines(procName, ProcKind) + 1
        Loop
    End If
End With
End Sub

Function all_dataArray(arr As Variant, arrVal As Variant) As Variant
'Add item to array
If IsEmpty(arr) Or Not IsArray(arr) Then
    arr = Array(arrVal)
Else
    ReDim Preserve arr(0 To UBound(arr) + 1)
    arr(UBound(arr)) = arrVal
End If
all_dataArray = arr
End Function

也许我说这一切都错了,很高兴听到任何建议。 谢谢你的时间。

识别模块的成员/过程只是一步,而VBIDE API将会有所帮助。

下一步是确定过程调用的位置,并且为了成功执行此操作,您需要获取模块的文本,并以某种方式确定什么是过程调用,以及调用哪个过程:为了可靠地执行这个,你需要“以编程方式理解”VBA代码以及VBA本身。

执行此操作的唯一方法是将文本转换为标记流(这是词法分析器的作用),将标记流转换为树结构(这就是解析器的作用),然后遍历这些树并填充符号表中包含有关什么在什么范围和作用域宣称可以“看到”它的信息。 然后,您需要再次遍历解析树,找到表示过程调用的节点,在符号表中查找名称,并根据VBA的语言范围规则解析对特定符号的标识符引用。

标记化步骤需要考虑令人讨厌的烦人的事情,如注释,行继续和预编译器指令。

一旦解决了标识符引用,您需要做的就是迭代对符号表中每个过程的引用,并列出它们各自的父作用域/过程 - 然后您就知道谁在调用谁。

我想不出任何其他可靠的方法来做到这一点......我无法想象在VBA中这样做。 Rubberduck的解析器位于45,000行C#代码的大地上,通过处理根据VBA语言规范设计的Antlr4语法生成 而这甚至不是预处理器和解析器部件,它们都有自己的挑战和复杂性。


那说不,“全部”模块不是最佳实践。 事实上,这是获得任何倾销袋的必然方法:如果一个模块(或类)是一个FooUtils ,一个FooManager ,一个FooHelper ,或任何其他这样模糊的松散术语,基本上代表“无论什么东西我无法适应其他任何地方“,设计出了问题。

我不知道你的“常用”功能是什么,但你可能需要的只是进一步的模块化。 将这些辅助方法提取到更专业的模块/类中 - ListObjectExtensions模块清楚地说明了它的范围,因此您不应期望在那里找到例如Get1DArrayFromRange函数。

我过去实现了它,但没有完美的结果。 它帮助我获得了一个不完整(或有时重载)的依赖项列表,其余的让编译器抱怨修复...

也是巨大的努力 - 很少需要它。 事实上,如果你在编程方面做得很好,你会尝试将每个函数拆分为小的独立函数。 所以最后当你要求单个功能的依赖时,结果就是答案通常是你的整个项目!

此代码是一个完整的模块,引用了其他模块的自定义常规函数,用于提取令牌列表,连接数组等。

我发布了一些我用过的函数摘要 我不知道是否有帮助。

祝好运!

1。

Function GetComponentProcDependences(Optional ComponentName As String = "Module1", Optional ProjectName As String = "")
[...]
objProject = VBAEditor.VBProjects.item(ProjectName)
[...]
PutativeDependences = Quicksort(ProjectFunctionList(objProject, False, False), True)
[...]
allreadyincluded = ComponentMethodsList(ReferencesComponent, False, False)
[...]
For Each item In allreadyincluded 
Call FindProcDependences(CStr(item), ProjectName, PutativeDependences, allreadyincluded)
Next
GetComponentProcDependences = allreadyincluded
End Function

2。

Function ProjectFunctionList(Optional objProject As VBIDE.VBProject = Nothing, Optional Titles As Boolean = True, Optional args As Boolean = True)
[...]
For Each objComponent In objProject.VBComponents
    cs = ComponentMethodsList(objComponent, Titles, args)
[...]
 ProjectFunctionList= ArrayConcatenate(ex, cs)
[...]
End Function

3。

    Public Function ComponentMethodsList(objComponent As VBIDE.VBComponent, Optional inclTitle As Boolean = True, Optional args As Boolean = True)
    [...]
     Dim objCode As VBIDE.CodeModule
    [...]
    Do While iLine < objCode.CountOfLines
    sProcName = objCode.ProcOfLine(iLine, pk)
    [...]
                    pclc = objCode.ProcCountLines(sProcName, pk)
                    pblc = objCode.ProcBodyLine(sProcName, pk)
    [...]
   pcl = Replace(objCode.Lines(pblc, decllines), " _", "") 'etc
    [...]
     Call AddToListSub(IIf(inclTitle, objComponent.Name & ".", "") & sProcName & 
    IIf(args, "#" & pcl, ""), res)
    [...]
    iLine = iLine + pclc
    [...]
    Loop
    ComponentMethodsList = res
    End Function

4。

Sub FindProcDependences(ProcName As String, ProjectName As String, PutativeDependences, Optional allreadyincluded = Nothing)


compcode = GetProcCode(ProcName, ProjectName) '
residue = Array("=", """", "-", "+", ";", "(", ")", "&", " ", ",", vbTab, vbCr, vbLf, ".")
compcode = MassReplace(compcode, "+", residue)
Do
i0 = Len(compcode)
compcode = Replace(compcode, "++", "+")
Loop While i0 <> Len(compcode)
tokens = tokenizer(compcode, False, "+", allreadyincluded)
Dim temparis()
For Each item In tokens
If ArrayContains(item, PutativeDependences, "bool") And Not ArrayContains(item, allreadyincluded) Then
Call AddToArrayListIfUnique(item, temparis)
End If
Next

[...]

allreadyincluded = ArrayConcatenate(allreadyincluded, temparis)

For Each item In temparis
Call FindProcDependences(CStr(item), ProjectName, PutativeDependences, allreadyincluded)  'Recursive calls
Next

End Sub

5。

Public Function GetProcCode(ProcName As String, Optional ProjectName As String = "")
Dim objComponent As VBIDE.VBComponent
Dim objCode As VBIDE.CodeModule
Set objComponent = FindComponentContainsProc(ProcName, ProjectName)
Set objCode = objComponent.CodeModule
iLine = objComponent.CodeModule.ProcStartLine(ProcName, vbext_pk_Proc)
pclc = objCode.ProcCountLines(ProcName, pk)
GetProcCode = objCode.Lines(iLine, pclc)
Set objComponent = Nothing
Set objCode = Nothing
End Function

6。

Function FindComponentContainsProc(ProcName As String, Optional ProjectName As String = "", Optional ProcKind As VBIDE.vbext_ProcKind = vbext_pk_Proc) As VBIDE.VBComponent
[...]
Dim objComponent As VBIDE.VBComponent
For Each objComponent In objProject.VBComponents
pupProcLine = 0
Dim objCode As VBIDE.CodeModule
Set objCode = objComponent.CodeModule
On Error Resume Next
pupProcLine = objCode.ProcStartLine(ProcName, ProcKind)
If pupProcLine > 0 Then Exit For
Next
[...]
End Function

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM