Enumerate modules's functions?

geoB

Registered User.
Local time
, 20:26
Joined
Oct 10, 2008
Messages
68
I vaguely recall seeing that someone has already solved the problem of enumerating a db's modules's functions and subs. The solution populated a table called documenter. But try as I might, none of my searching has rediscovered the solution. If you know where I could find it, please let me know.

Many thanks.

George
 
Insert the following in a module and run the function "ProjectDocumentor" from a macro. This will create a table containing all of your forms, their controls and functions associated with events on the forms as well as which modules the functions are in. It will document each function in your modules with a comment as to which form control they are associated with. Modify it as you see fit. You will need to have Visual Basic Extensibility and Microsoft Scripting Runtime both referenced.

Code:
Public Function ProjectDocumentor()
   
  Dim tdf              As TableDef
  Dim blnExists        As Boolean
  Dim strFormEvents    As String
  Dim strControlEvents As String
  Dim frmobj           As AccessObject
  Dim ctlobj           As Control
  Dim objProp          As Property
  Dim frmobj1          As Form
  Dim i                As Integer
  Dim rst              As DAO.Recordset
  Dim rst1             As DAO.Recordset
  Dim j                As Integer
  Dim qdf              As QueryDef
  Dim sProcName        As String
  Dim VBIx             As VBIDE.VBE
  Dim oCodePane        As CodePane
  Dim oCodeMod         As CodeModule
  Dim obj              As CodePane
  Dim aObj             As AccessObject
  Dim eProcKind        As vbext_ProcKind
  Dim strComment       As String
  Dim strFormName      As String
  Dim k                As Long
  Dim m                As Long
  Dim varDictItems     As Variant
  Dim strLine          As String
  Dim dictProcNames    As New Scripting.Dictionary
  Dim strProcname      As String
  Dim lngStartLine     As Long
   
  Set VBIx = Application.VBE
  strFormEvents = ".OnCurrent.BeforeInsert.AfterInsert.BeforeUpdate.AfterUpdate.OnDirty.OnUndo.OnDelete.BeforeDelConfirm.AfterDelConfirm.OnOpen.OnLoad.OnResize.OnUnload.OnClose.OnActivate.OnDeactivate.OnGotFocus.OnLostFocus.OnClick.OnDblClick.OnMouseDown.OnMouseMove.OnMouseUp.OnMouseWheel.OnKeyDown.OnKeyUp.OnKeyPress.OnError.OnFilter.OnApplyFilter.OnTimer.OnCmdEnabled.OnCmdChecked.OnCmdBeforeExecute.OnCmdExecute.OnDataChange.OnDataSetChange.OnPivotTableChange.OnSelectionChange.OnViewChange.OnConnect.OnDisconnect.BeforeQuery.OnQuery.AfterLayout.BeforeRender.AfterRender.AfterFinalRender"
  strControlEvents = ".OnUndo.OnChange.OnEnter.OnExit.OnGotFocus.OnLostFocus.OnClick.OnDblClick.OnMouseDown.OnMouseMove.OnMouseUp.OnKeyDown.OnKeyUp.OnKeyPress.BeforeUpdate.AfterUpdate.OnDirty.OnNotInList"
   
  For Each tdf In CurrentDb.TableDefs
   
    If tdf.Name = "msysProcCalls" Then
      blnExists = True
      Exit For
    End If
   
  Next
   
  If blnExists Then
   
    DoCmd.RunSQL "DELETE msysProcCalls.* FROM msysProcCalls"
  Else
    Set tdf = CurrentDb.CreateTableDef("msysProcCalls")
   
    With tdf
      .Fields.Append .CreateField("Form", dbText)
      .Fields.Append .CreateField("Function", dbText)
      .Fields.Append .CreateField("Control", dbText)
      .Fields.Append .CreateField("Module", dbText)
    End With
   
    CurrentDb.TableDefs.Append tdf
  End If
   
  Set rst = CurrentDb.OpenRecordset("msysProcCalls", dbOpenDynaset)
  blnExists = False
   
  For Each tdf In CurrentDb.TableDefs
   
    If tdf.Name = "Switchboard Items" Then
      blnExists = True
      Exit For
    End If
   
  Next
   
  If blnExists Then
    Set rst1 = CurrentDb.OpenRecordset("Switchboard items")
   
    With rst1
   
      Do While Not .EOF
        rst.AddNew
        rst!Form = "Switchboard"
        rst!Function = !Argument
        rst!Control = "Switchboard Argument"
        rst.Update
        .moveNext
      Loop
   
    End With
   
  End If
   
  For Each frmobj In CurrentProject.AllForms
   
    If Not frmobj.IsLoaded Then
   
      DoCmd.openForm frmobj.Name, acDesign, , , , acHidden
    End If
   
    Set frmobj1 = Forms(frmobj.Name)
   
    For Each objProp In frmobj1.Properties
   
      If InStr(1, strFormEvents, "." & objProp.Name, vbBinaryCompare) > 0 Then
   
        If Len(objProp.Value) > 0 Then
   
          With rst
            .AddNew
              !Form = frmobj1.Name
              !Function = objProp.Value
              !Control = frmobj1.Name & "." & objProp.Name
            .Update
          End With
   
        End If
   
      End If
   
    Next
   
    If frmobj1.Controls.COUNT > 0 Then
   
      For Each ctlobj In frmobj1.Controls
   
        For Each objProp In ctlobj.Properties
   
          If InStr(1, strControlEvents, "." & objProp.Name, vbBinaryCompare) > 0 Then
   
            If Len(objProp.Value) > 0 Then
   
              With rst
                .AddNew
                  !Form = frmobj1.Name
                  !Function = objProp.Value
                  !Control = ctlobj.Name & "." & objProp.Name
                .Update
              End With
   
            End If
   
          End If
   
        Next objProp
   
      Next ctlobj
   
    End If
   
    DoCmd.Close acForm, frmobj.Name, acSaveNo
  Next
   
  CurrentDb.Execute "DELETE msysProcCalls.Function FROM msysProcCalls WHERE (((msysProcCalls.Function) Is Null))"
   
  For i = 1 To VBIx.ActiveVBProject.VBComponents.COUNT
    VBIx.ActiveVBProject.VBComponents.item(i).Activate
  Next i
   
  For Each obj In VBIx.CodePanes
    Set oCodePane = obj
    Set oCodeMod = oCodePane.CodeModule
   
    For i = oCodeMod.CountOfDeclarationLines + 1 To oCodeMod.CountOfLines
   
      If sProcName <> oCodeMod.ProcOfLine(i, eProcKind) Then
        sProcName = oCodeMod.ProcOfLine(i, eProcKind)
        CurrentDb.Execute "UPDATE msysProcCalls SET msysProcCalls.[Module] = '" & oCodeMod.Parent.Name & "' WHERE (((InStr(1,[msysProcCalls].[Function],'" & sProcName & "'))>0) AND ((msysProcCalls.Function) Is Not Null))"
      End If
   
    Next i
   
  Next obj
   
  rst.Close
  Set rst = Nothing
   
  For Each obj In VBIx.CodePanes
    Set oCodePane = obj
    Set oCodeMod = oCodePane.CodeModule
    k = 0
    dictProcNames.removeAll
   
    For m = oCodeMod.CountOfLines To oCodeMod.CountOfDeclarationLines + 1 Step -1
   
      If InStr(1, oCodeMod.Lines(m, 1), "'Object Assn:") > 0 Then
        oCodeMod.DeleteLines m, 1
      End If
   
      If strProcname <> oCodeMod.ProcOfLine(m, eProcKind) Then
        strProcname = oCodeMod.ProcOfLine(m, eProcKind)
        k = k + 1
        dictProcNames.Add k, strProcname
      End If
   
    Next m
   
    varDictItems = dictProcNames.Items
   
    For m = 0 To dictProcNames.COUNT - 1
   
      If varDictItems(m) <> "" Then
        strComment = "'Object Assn: "
        Set rst = CurrentDb.OpenRecordset("SELECT msysProcCalls.* FROM msysProcCalls WHERE (((trimFunction([msysProcCalls]![Function]))='" & varDictItems(m) & "'))")
        lngStartLine = oCodeMod.ProcStartLine(varDictItems(m), eProcKind)
        strLine = oCodeMod.Lines(lngStartLine, 1)
   
        With rst
   
          Do While Not .EOF
   
            If strFormName <> ![Form] & "." & ![Control] Then
              strFormName = ![Form] & "." & ![Control]
              strComment = strComment & strFormName & "/"
            End If
   
            .moveNext
          Loop
   
        End With
   
        strComment = Mid(strComment, 1, Len(strComment) - 1)
   
        If Len(Trim(strLine)) < 1 Then
   
          If strComment <> "'Object Assn:" Then
            oCodeMod.ReplaceLine lngStartLine, strComment
          End If
   
        Else
   
          If strComment <> "'Object Assn:" Then
            oCodeMod.InsertLines lngStartLine, strComment
          End If
   
        End If
   
        rst.Close
        Set rst = Nothing
      End If
   
    Next
   
  Next obj
   
  rst1.Close
  Set rst1 = Nothing
  Set frmobj1 = Nothing
  Set oCodePane = Nothing
  Set oCodeMod = Nothing
  MsgBox "Documenting complete"
   
End Function
   
Public Function trimFunction(strInput As String) As String
   
  If InStr(1, strInput, "=") > 0 Then
    strInput = Mid(strInput, 2)
  End If
   
  If InStr(1, strInput, "(") > 0 Then
    strInput = Mid(strInput, 1, InStr(1, strInput, "(") - 1)
  End If
   
  trimFunction = strInput
End Function
 
Folks,

Thanks for the replies. [I might have responded sooner but we fair-weather skiers take our opportunities when we can. Great conditions today!]

DCrake: I think not. I ran it against a db, it whirred for a bit.

[edit: Cool output, but not what I'm looking for. I'd like a list of the individual subs and functions in each db object.]

c_smithwick: This looks promising. Compiles OK. I tried running it and got a "Sub or Function not defined" at
Code:
lngStartLine = oCodeMod.ProcStartLine(varDictItems(m), eProcKind)
Any suggestions?

[edit: procedure runs this line for a while before hanging, as if the dim of oCodeMod got lost.]

George
 
Last edited:
Make sure you have the following added to your references:

"Microsoft Visual Basic for Application Extensibility 5.3", "Microsoft Scripting Runtime" and "Microsoft Access 11.0 Object Library"
 
Make sure you have the following added to your references:... "Microsoft Access 11.0 Object Library"

I'm working in 2007 so the object library is 12.0. Otherwise, references are as required.

The procedure hangs up when running the above quoted line against Uncle Gizmo's calendar routine clsGetActiveFrm, when varDictItems(m) evaluates to "fChkName" and eProcKind evaluates to 3. There are 26 forms and reports open.

George
 
I tried using this, gets close to the end (yes, I set break points) and the code gets modified. The if statement after 'For m = oCodeMOd.CountOfLIne to oCodeMOd.Countof DeclarationsLines + 1 Step -1' gets deleted and I get an error message.

I have not gone through the code to see what all you are doing. I tried this on a copy of a production DB. It was written several years ago by an employee that left several years ago. We are thinking about moving (at least the tables) to SQL. But wanted to be sure we had a handle on all the pieces.
 
I got a similar behavior while trying to track down the error. After several runs, the first IF statement in the trimFunction gets deleted and the procedure fails to compile. Looks like a case of autocannibalism!

George
 
I was able to complete the documentation run after removing the offending class module. Not an entirely satisfactory result.

g
 
After reviewing the results of a successful run I note that the procedure provided does not really answer the question. It does an excellent job of enumerating the procedures specified in each form in an application. It does not, however, enumerate the procedures in a module.

Where I'm trying to get is to create a table of contents and a 'where used' listing for all the source code in an application. The database documenter can create a document containing all the source code. I've written a Word macro that will, for Access 2003 or 2007, mark up each unique instance of a database object for a Table of Contents. What I do not yet have is the ability to mark up with subheadings the procedures inside each object. The ProjectDocumentor function should enable getting the subheadings for form objects.

What is not yet clear is how to identify the individual procedures inside a code module. Once identified, it should then be possible to create a cross-reference that shows where each of these procedures is used in the application.

My search continues. For now I hope to not recreate this wheel.

George
 
You can use these two functions (taken from Bob Larson's Database Merge Analyzer) to get the modules and their procedures:
Code:
Function getModuleInfo(strDbName As String, blnGetProcInfo As Boolean, lngDbNum As Long)
    Dim db As DAO.Database
    Dim rstAtt As DAO.Recordset
    Dim rstObj As DAO.Recordset
    Dim varItem As Variant
    Dim intCount As Integer
    Dim blnExist As Boolean
    Dim strDbFullName As String
    Dim appAccess As Access.Application
    Set db = OpenDatabase(strDbName, , True)
    strDbFullName = strDbName
    strDbName = Right(db.Name, Len(db.Name) - InStrRev(db.Name, "\", , vbTextCompare))
    If blnGetProcInfo Then
        Set appAccess = New Access.Application
        appAccess.OpenCurrentDatabase strDbFullName
    End If
    Set rstAtt = CurrentDb.OpenRecordset("tblAttributes")
    Set rstObj = CurrentDb.OpenRecordset("tblCompare")
    With rstObj
        For Each varItem In db.Containers("Modules").Documents
            .AddNew
            .Fields("ItemName") = varItem.Name
            .Fields("ItemType") = "Module"
            .Fields("ItemFromDatabase") = strDbName
            .Fields("DbNumber") = lngDbNum
            .Update
            intCount = intCount + 1
            If blnGetProcInfo Then
                AllProcs varItem.Name, appAccess, lngDbNum
            End If
        Next
    End With
    If rstAtt.RecordCount > 0 Then
        Set rstAtt = CurrentDb.OpenRecordset("SELECT * FROM tblAttributes WHERE DatabaseName = '" & strDbName & "'")
        With rstAtt
            .Edit
            .Fields("ModuleCount") = intCount
            .Update
            blnExist = True
        End With
    End If
    If blnExist = False Then
        With rstAtt
            .AddNew
            .Fields("DatabaseName") = strDbName
            .Fields("ModuleCount") = intCount
            .Update
        End With
    End If
    If blnGetProcInfo Then
        appAccess.CloseCurrentDatabase
        appAccess.Quit
        Set appAccess = Nothing
    End If
    db.Close
    rstAtt.Close
    rstObj.Close
    Set rstAtt = Nothing
    Set rstObj = Nothing
    Set db = Nothing
End Function
 
Public Function AllProcs(ByVal strModuleName As String, ByVal acDb As Access.Application, lngDbNum As Long)
'    Dim appAccess As Access.Application
'    Dim db As Database
    Dim mdl As Module
    Dim lngCount As Long
    Dim lngCountDecl As Long
    Dim lngI As Long
    Dim strProcName As String
    Dim astrProcNames() As String
    Dim intI As Integer
    Dim strMsg As String
    Dim lngR As Long
    Dim rstModProc As DAO.Recordset
 
    Set rstModProc = CurrentDb.OpenRecordset("tblModProcs")
 
    ' Open specified Module object.
    acDb.DoCmd.OpenModule strModuleName
    ' Return reference to Module object.
    Set mdl = acDb.Modules(strModuleName)
    ' Count lines in module.
    lngCount = mdl.CountOfLines
    ' Count lines in Declaration section in module.
    lngCountDecl = mdl.CountOfDeclarationLines
    ' Determine name of first procedure.
    strProcName = mdl.ProcOfLine(lngCountDecl + 1, lngR)
    ' Initialize counter variable.
    intI = 0        ' Redimension array.
    ReDim Preserve astrProcNames(intI)
    ' Store name of first procedure in array.
    astrProcNames(intI) = strProcName
    ' Determine procedure name for each line after declarations.
    For lngI = lngCountDecl + 1 To lngCount
        ' Compare procedure name with ProcOfLine property value.
        If strProcName <> mdl.ProcOfLine(lngI, lngR) Then
            ' Increment counter.
            intI = intI + 1
            strProcName = mdl.ProcOfLine(lngI, lngR)
            ReDim Preserve astrProcNames(intI)
            ' Assign unique procedure names to array.
            astrProcNames(intI) = strProcName
        End If
    Next lngI
    strMsg = "Procedures in module '" & strModuleName & "': " & vbCrLf & vbCrLf
 
 
    For intI = 0 To UBound(astrProcNames)
        With rstModProc
        .AddNew
        .Fields("ModuleName").Value = strModuleName
        .Fields("ProcName").Value = astrProcNames(intI)
        .Fields("DbName").Value = acDb.CurrentProject.Name
        .Fields("DbNum").Value = lngDbNum
        .Update
        End With
    Next intI
    rstModProc.Close
    Set rstModProc = Nothing
End Function
 
The code I just posted works in conjunction with a table so you may just want to download the entire program and use it, or modify for your use (I think that would be okay).
 
Fantastic! I'm now off to btab-world to read more.

Many thanks.

George
 
It looks like my wheel remains to be invented. I'll have to deconstruct Larson's code to get at a module's procedures. But it's a start. Of course, I can compare an application to itself, which does work. To get a standalone procedure it'll take a bit of work.

George
 
Well, a wheel has been invented. Just not sure how round it is. The module attached is intended to be run from Word 2007, probably in Normal.dotm. It assumes a Word document containing just the code output of an Access database documenter report for Modules, Forms, Reports and, for Queries, just the SQL.

When MarkTOC is run, it formats each unique instance of an Access database object in the document with a heading style so that a Table of Contents can be generated. It also appends to the document a where used listing showing where each procedure from a standard or class module is used. The where used listing is a Word table sorted by module and procedure.

Hope this is useful somewhere.

Comments (be kind) are welcome.

George
 

Attachments

Users who are viewing this thread

Back
Top Bottom