Jaafar Tribak

Well-known Member
Joined
Dec 5, 2002
Messages
9,616
Office Version
  1. 2016
Platform
  1. Windows
Spurred by the question asked recently by @audeser in this post, I took a deeper look and I arrived at the following little project which, as the title of the current thread suggests, it allows you to list all the functions exported by standard windows DLLs . This little vba project mimics to a certain extent what dll viewer softwares do (such as Dependency Walker, Dll Viewer, PE File Browser etc ). The code is entirely vba based + a few api calls. No third party dependencies required.

I hope this can be useful for someone.

DLLExportViewer.xlsm







1- API code in a Standard Module:
VBA Code:
Option Explicit

#If Win64 Then
    Private Const NULL_PTR = 0^
    Private Const PTR_SIZE = 8&
#Else
    Private Const NULL_PTR = 0&
    Private Const PTR_SIZE = 4&
#End If

#If VBA7 Then
    Private Declare PtrSafe Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As LongPtr)
    Private Declare PtrSafe Function MapAndLoad Lib "Imagehlp.dll" (ByVal ImageName As String, ByVal DLLPath As String, LoadedImage As LOADED_IMAGE, ByVal DotDLL As Long, ByVal ReadOnly As Long) As Long
    Private Declare PtrSafe Function UnMapAndLoad Lib "Imagehlp.dll" (LoadedImage As LOADED_IMAGE) As Long
    Private Declare PtrSafe Function ImageRvaToVa Lib "Imagehlp.dll" (ByVal NTHeaders As LongPtr, ByVal Base As LongPtr, ByVal RVA As LongPtr, ByVal LastRvaSection As LongPtr) As LongPtr
    Private Declare PtrSafe Function lstrlenW Lib "kernel32" (ByVal lpsz As LongPtr) As Long
    Private Declare PtrSafe Function GetModuleFileName Lib "kernel32" Alias "GetModuleFileNameA" (ByVal hModule As LongPtr, ByVal lpFileName As String, ByVal nSize As Long) As Long
    Private Declare PtrSafe Function GetModuleHandle Lib "kernel32" Alias "GetModuleHandleA" (ByVal lpModuleName As String) As LongPtr
    Private Declare PtrSafe Function GetProcAddressByOrdinal Lib "kernel32" Alias "GetProcAddress" (ByVal hModule As LongPtr, ByVal nOrdinal As Long) As LongPtr
    Private Declare PtrSafe Function LoadLibrary Lib "kernel32" Alias "LoadLibraryA" (ByVal lpLibFileName As String) As LongPtr
    Private Declare PtrSafe Function FreeLibrary Lib "kernel32" (ByVal hLibModule As LongPtr) As Long
#Else
    Private Enum LongPtr
        [_]
    End Enum
    Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As LongPtr)
    Private Declare Function MapAndLoad Lib "Imagehlp.dll" (ByVal ImageName As String, ByVal DLLPath As String, LoadedImage As LOADED_IMAGE, ByVal DotDLL As Long, ByVal ReadOnly As Long) As Long
    Private Declare Function UnMapAndLoad Lib "Imagehlp.dll" (LoadedImage As LOADED_IMAGE) As Long
    Private Declare Function ImageRvaToVa Lib "Imagehlp.dll" (ByVal NTHeaders As LongPtr, ByVal Base As LongPtr, ByVal RVA As LongPtr, ByVal LastRvaSection As LongPtr) As LongPtr
    Private Declare Function lstrlenW Lib "kernel32" (ByVal lpsz As LongPtr) As Long
    Private Declare Function GetModuleFileName Lib "kernel32" Alias "GetModuleFileNameA" (ByVal hModule As LongPtr, ByVal lpFileName As String, ByVal nSize As Long) As Long
    Private Declare Function GetModuleHandle Lib "kernel32" Alias "GetModuleHandleA" (ByVal lpModuleName As String) As LongPtr
    Private Declare Function GetProcAddressByOrdinal Lib "kernel32" Alias "GetProcAddress" (ByVal hModule As LongPtr, ByVal nOrdinal As Long) As LongPtr
    Private Declare Function LoadLibrary Lib "kernel32" Alias "LoadLibraryA" (ByVal lpLibFileName As String) As LongPtr
    Private Declare Function FreeLibrary Lib "kernel32" (ByVal hLibModule As LongPtr) As Long
#End If


Private Type LIST_ENTRY
  FLink As LongPtr
  Blink As LongPtr
End Type

Private Type IMAGE_EXPORT_DIRECTORY
  Characteristics         As Long
  TimeDateStamp           As Long
  MajorVersion            As Integer
  MinorVersion            As Integer
  Name                    As Long
  Base                    As Long
  NumberOfFunctions       As Long
  NumberOfNames           As Long
  AddressOfFunctions      As Long
  AddressOfNames          As Long
  AddressOfNameOrdinals   As Long
End Type

Private Type LOADED_IMAGE
  ModuleName         As LongPtr
  hFile              As LongPtr
  MappedAddress      As LongPtr
  FileHeader         As LongPtr
  LastRvaSection     As LongPtr
  NumberOfSections   As Long
  Sections           As LongPtr
  Characteristics    As Long
  fSystemImage       As Byte
  fDOSImage          As Byte
  Links              As LIST_ENTRY
  SizeOfImage        As Long
End Type

Private Type IMAGE_FILE_HEADER
   Machine               As Integer
   NumberOfSections      As Integer
   TimeDateStamp         As LongPtr
   PointerToSymbolTable  As LongPtr
   NumberOfSymbols       As Long
   SizeOfOptionalHeader  As Integer
   Characteristics       As Integer
End Type

Private Type IMAGE_DATA_DIRECTORY
    RVA  As Long
    Size As Long
End Type

Private Const IMAGE_NUMBEROF_DIRECTORY_ENTRIES = 16&

Private Type IMAGE_OPTIONAL_HEADER32
   Magic                                                 As Integer
   MajorLinkerVersion                                    As Byte
   MinorLinkerVersion                                    As Byte
   SizeOfCode                                            As Long
   SizeOfInitializedData                                 As Long
   SizeOfUninitializedData                               As Long
   AddressOfEntryPoint                                   As Long
   BaseOfCode                                            As Long
   BaseOfData                                            As Long
   ImageBase                                             As Long
   SectionAlignment                                      As Long
   FileAlignment                                         As Long
   MajorOperatingSystemVersion                           As Integer
   MinorOperatingSystemVersion                           As Integer
   MajorImageVersion                                     As Integer
   MinorImageVersion                                     As Integer
   MajorSubsystemVersion                                 As Integer
   MinorSubsystemVersion                                 As Integer
   Win32VersionValue                                     As Long
   SizeOfImage                                           As Long
   SizeOfHeaders                                         As Long
   CheckSum                                              As Long
   Subsystem                                             As Integer
   DllCharacteristics                                    As Integer
   SizeOfStackReserve                                    As Long
   SizeOfStackCommit                                     As Long
   SizeOfHeapReserve                                     As Long
   SizeOfHeapCommit                                      As Long
   LoaderFlags                                           As Long
   NumberOfRvaAndSizes                                   As Long
   DataDirectory(0 To IMAGE_NUMBEROF_DIRECTORY_ENTRIES)  As IMAGE_DATA_DIRECTORY
End Type

Private Type IMAGE_NT_HEADER  '256 bytes.
    Signature           As Long
    FileHeader          As IMAGE_FILE_HEADER
    OptionalHeader      As IMAGE_OPTIONAL_HEADER32
End Type



Public Function GetPEBinaryExports(ByVal sFile As String) As Variant()

    Const IMAGE_DIRECTORY_ENTRY_EXPORT = 0&

    Dim uLoadedImage As LOADED_IMAGE
    Dim uImageHeader As IMAGE_NT_HEADER
    Dim uIEXPDIR As IMAGE_EXPORT_DIRECTORY
  
    Dim hMod As LongPtr
    Dim lRvaExportDirTable As LongPtr
    Dim lVaExportDirTable As LongPtr
    Dim lExportNamePointerTableVA As LongPtr
    Dim lNextAddr As LongPtr
    Dim lRVAFuncAddr As LongPtr
    Dim pAddrOfOrdinalsArray As LongPtr
    Dim pAddrOfFuncsArray As LongPtr
  
    Dim lFirstOrdinal As Long
    Dim lNumOfExports As Long
    Dim lOrdinalIndex As Integer
    Dim sExportName As String
    Dim lRet As Long, i As Long
 

    hMod = LoadLibrary(sFile)

    If hMod = NULL_PTR Then
        MsgBox "Module not loaded."
        Exit Function
    End If

    If Len(Dir(sFile)) = 0 Then
        sFile = ModuleFileName(sFile)
    End If

    lRet = MapAndLoad(sFile, "", uLoadedImage, True, True)
    If lRet = 0& Then
       MsgBox "MapAndLoad failed."
       Exit Function
    End If

    With uLoadedImage
  
        Call CopyMemory(ByVal VarPtr(uImageHeader), ByVal .FileHeader, LenB(uImageHeader))
        lRvaExportDirTable = uImageHeader.OptionalHeader.DataDirectory(IMAGE_DIRECTORY_ENTRY_EXPORT).RVA
      
        If lRvaExportDirTable = 0& Then
            MsgBox "No export directory."
            GoTo Xit
        End If
      
        lVaExportDirTable = ImageRvaToVa(.FileHeader, .MappedAddress, lRvaExportDirTable, NULL_PTR)
        Call CopyMemory(ByVal VarPtr(uIEXPDIR), ByVal lVaExportDirTable, LenB(uIEXPDIR))
        lNumOfExports = uIEXPDIR.NumberOfNames
  
        If lNumOfExports Then
            ReDim vExportsInfoArray(1 To lNumOfExports, 1 To 6) As Variant
            lFirstOrdinal = uIEXPDIR.Base
            lExportNamePointerTableVA = ImageRvaToVa(.FileHeader, .MappedAddress, uIEXPDIR.AddressOfNames, NULL_PTR)
            Call CopyMemory(lNextAddr, ByVal lExportNamePointerTableVA, PTR_SIZE)
            pAddrOfOrdinalsArray = hMod + uIEXPDIR.AddressOfNameOrdinals
            pAddrOfFuncsArray = hMod + uIEXPDIR.AddressOfFunctions
            For i = 0& To lNumOfExports - 1&
                lNextAddr = ImageRvaToVa(.FileHeader, .MappedAddress, lNextAddr, NULL_PTR)
                sExportName = LPSTRtoBSTR(lNextAddr)
                Call CopyMemory(lOrdinalIndex, ByVal pAddrOfOrdinalsArray + (i * 2&), PTR_SIZE)
                Call CopyMemory(lRVAFuncAddr, ByVal pAddrOfFuncsArray + (lOrdinalIndex * 4&), 4&)
                vExportsInfoArray(i + 1&, 1&) = i + 1&
                vExportsInfoArray(i + 1&, 2&) = sExportName
                vExportsInfoArray(i + 1&, 3&) = lOrdinalIndex + lFirstOrdinal
                vExportsInfoArray(i + 1&, 4&) = "&H" & Right(String$(8, "0") & Hex$(lRVAFuncAddr), 8&)
                #If Win64 Then
                    vExportsInfoArray(i + 1&, 5&) = "&H" & Right(String$(16&, "0") & Hex$(GetProcAddressByOrdinal(hMod, lOrdinalIndex + lFirstOrdinal)), 16&)
                #Else
                    vExportsInfoArray(i + 1&, 5&) = "&H" & Right(String$(8&, "0") & Hex$(GetProcAddressByOrdinal(hMod, lOrdinalIndex + lFirstOrdinal)), 8&)
                #End If
                vExportsInfoArray(i + 1&, 6&) = sFile
                GetPEBinaryExports = vExportsInfoArray
                lExportNamePointerTableVA = lExportNamePointerTableVA + 4&
                Call CopyMemory(lNextAddr, ByVal lExportNamePointerTableVA, PTR_SIZE)
            Next
        Else
            MsgBox sFile & " has no export functions."
        End If
      
    End With

Xit:

    If hMod Then
        Call FreeLibrary(hMod)
    End If

    lRet = UnMapAndLoad(uLoadedImage)
    If lRet = 0& Then
      MsgBox "UnMapAndLoad failed."
    End If

End Function

Private Function LPSTRtoBSTR(ByVal lpString As LongPtr) As String
   Dim lStrLen As Long
   Dim lPosNullChar As Long

   lStrLen = lstrlenW(lpString)
   LPSTRtoBSTR = String$(lStrLen, 0&)
   Call CopyMemory(ByVal StrPtr(LPSTRtoBSTR), ByVal lpString, lStrLen)
   LPSTRtoBSTR = StrConv(LPSTRtoBSTR, vbUnicode)
   lPosNullChar = InStr(1&, LPSTRtoBSTR, Chr(0&), vbBinaryCompare)
   If lPosNullChar > 0& Then
      LPSTRtoBSTR = Left$(LPSTRtoBSTR, lPosNullChar - 1&)
   End If
End Function

Private Function ModuleFileName(ByVal ModuleName As String) As String
    Dim sBuffer As String, lRet As Long, hMod As LongPtr

    hMod = GetModuleHandle(ModuleName)
    If hMod <> GetModuleHandle("") Then
        sBuffer = Space(256)
        lRet = GetModuleFileName(hMod, sBuffer, Len(sBuffer))
        ModuleFileName = Left(sBuffer, lRet)
    End If
End Function



2- Code Usage example ( as per the workbook example)
VBA Code:
Option Explicit

Private Sub ComboBox1_Change()
    DllExportsInfoToSheet Me.ComboBox1
End Sub

Private Sub ComboBox2_Change()
    DllExportsInfoToSheet Me.ComboBox2
End Sub

Private Sub DllExportsInfoToSheet(ByVal Combo As ComboBox)
    Dim vExportsInfoArray() As Variant
    Dim sFileName As String
  
    sFileName = Combo.Text
    vExportsInfoArray = GetPEBinaryExports(sFileName)
    Range("A4:F3000, A2").ClearContents
    If Not (Not vExportsInfoArray) Then
        Range("A2") = "Exports Found" & vbNewLine & UBound(vExportsInfoArray)
        Range("A4:F" & UBound(vExportsInfoArray, 1) + 3) = vExportsInfoArray
        Range("A4:F" & UBound(vExportsInfoArray, 1) + 3).EntireColumn.AutoFit
    End If
End Sub


Tested on Excel 2007 Win7 x32bit and Excel 2016 x64bit Win10 x64bit.
 
Last edited:

Excel Facts

How to calculate loan payments in Excel?
Use the PMT function: =PMT(5%/12,60,-25000) is for a $25,000 loan, 5% annual interest, 60 month loan.
Thanks for sharing!
---------------------
Works perfectly on:
Microsoft Windows 10 Pro / Version: 10.0.19045 Build 19045 64-bit
Microsoft® Excel® für Microsoft 365 MSO / Version 2211 Build 16.0.15831.20098 32-Bit
VBA 7.1 Version 1127
 
Upvote 0
Hi Jaafar. Thank you for this - it has come in handy a number of times now, and have added it to my toolbox.
One thing I've noticed is that the last function name seems to be chopped off for some reason. This doesn't occur for the earlier functions, from what I can see, so not sure what the problem is. I played around with the API calls and tried lstrlenA instead of lstrlenW in the LPSTRtoBSTR function and that seems to fix it, though, as above, it isn't clear to me why...

Using lstrlenW
1678301836207.png


Using lstrlenA
1678301982773.png
 
Upvote 0
One thing I've noticed is that the last function name seems to be chopped off for some reason. This doesn't occur for the earlier functions, from what I can see, so not sure what the problem is. I played around with the API calls and tried lstrlenA instead of lstrlenW in the LPSTRtoBSTR function and that seems to fix it, though, as above, it isn't clear to me why
Hi Dan, Good catch!
I never would have noticed that mistake since only some functions seem to be chopped off ... So thank you very much for bringing that stealth bug to my attention.

The problem was that lstrlenW returns the number of characters (not the number of bytes) in the string and since a vba BSTR is 2* bytes per character (unicode), we need to multipy the returned value from lstrlenW by 2.

Download with bug fixed:
DLLExportViewer.xlsm


So the mistake was in the CopyMemory call:

Private Function LPSTRtoBSTR(ByVal lpString As LongPtr) As String
Dim lstrlen As Long
Dim lPosNullChar As Long

lstrlen = lstrlenW(lpString)
LPSTRtoBSTR = String$(lstrlen, 0&)
Call CopyMemory(ByVal StrPtr(LPSTRtoBSTR), ByVal lpString, lstrlen )
LPSTRtoBSTR = StrConv(LPSTRtoBSTR, vbUnicode)
lPosNullChar = InStr(1&, LPSTRtoBSTR, Chr(0&), vbBinaryCompare)
If lPosNullChar > 0& Then
LPSTRtoBSTR = Left$(LPSTRtoBSTR, lPosNullChar - 1&)
End If
End Function



The correct call should be :
Call CopyMemory(ByVal StrPtr(LPSTRtoBSTR), ByVal lpString, lstrlen * 2&)
 
Last edited:
Upvote 0
As always, thank you very much for taking the time to explain it. It makes more sense now.
 
Upvote 0
Hi Jaafar. Something just occurred to me, and I was wondering if perhaps you knew the answer given that you've made reference to it in your project here:- do you know if it's possible to call WinRT APIs from VBA? I ask because you reference DLLs like Windows.Media.Ocr.dll in your sample DLL list.
 
Upvote 0
Hi Jaafar. Something just occurred to me, and I was wondering if perhaps you knew the answer given that you've made reference to it in your project here:- do you know if it's possible to call WinRT APIs from VBA? I ask because you reference DLLs like Windows.Media.Ocr.dll in your sample DLL list.
Sorry Dan, I don't have a clue about WinRT let alone calling its apis from vba.
That dll is probably there bacause of net framework that comes with Win 10.
 
Upvote 0

Forum statistics

Threads
1,214,918
Messages
6,122,246
Members
449,075
Latest member
staticfluids

We've detected that you are using an adblocker.

We have a great community of people providing Excel help here, but the hosting costs are enormous. You can help keep this site running by allowing ads on MrExcel.com.
Allow Ads at MrExcel

Which adblocker are you using?

Disable AdBlock

Follow these easy steps to disable AdBlock

1)Click on the icon in the browser’s toolbar.
2)Click on the icon in the browser’s toolbar.
2)Click on the "Pause on this site" option.
Go back

Disable AdBlock Plus

Follow these easy steps to disable AdBlock Plus

1)Click on the icon in the browser’s toolbar.
2)Click on the toggle to disable it for "mrexcel.com".
Go back

Disable uBlock Origin

Follow these easy steps to disable uBlock Origin

1)Click on the icon in the browser’s toolbar.
2)Click on the "Power" button.
3)Click on the "Refresh" button.
Go back

Disable uBlock

Follow these easy steps to disable uBlock

1)Click on the icon in the browser’s toolbar.
2)Click on the "Power" button.
3)Click on the "Refresh" button.
Go back
Back
Top