VBA Windows API
The Windows API allows VBA to access functionalities of the Windows Operating System. Windows API procedures are declared in VBA and then called directly in VBA code or through a wrapper procedure written in VBA which manages usage of the Windows API procedure.
Microsoft has provided the VBA declarations for Windows API procedures to be used freely. Because VBA7 requires the PtrSafe keyword to be used in declarations and the LongLong and LongPtr types were added, there are two versions of the VBA Windows API declarations provided by Microsoft.
VBA Windows API Declarations:
Declaring Windows API Functions
To declare Windows API procedures use the Declare statement. Specify the location of the procedure using the Lib keyword. To declare a procedure using an alias, specify the original name of the procedure as declared within the code resource using the Alias clause.
To make Windows API declarations compatible with VBA7, the PtrSafe keyword must be used with the Declare statement. To ensure compatibility with 32-bit and 64-bit platforms in VBA7, use the LongPtr type for all pointers and handles.
#If VBA7 Then
Declare PtrSafe Function URLDownloadToFile Lib "urlmon" Alias "URLDownloadToFileA" ( _
ByVal pCaller As LongPtr, _
ByVal szURL As String, _
ByVal szFileName As String, _
ByVal dwReserved As LongPtr, _
ByVal lpfnCB As LongPtr) As LongPtr
#Else
Declare Function URLDownloadToFile Lib "urlmon" Alias "URLDownloadToFileA" ( _
ByVal pCaller As Long, _
ByVal szURL As String, _
ByVal szFileName As String, _
ByVal dwReserved As Long, _
ByVal lpfnCB As Long) As Long
#End If
Wrapper Procedures
Sometimes calling a Windows API procedure directly from VBA code can complicate the VBA code. To mitigate this issue it is often helpful to write a wrapper procedure in VBA to handle all the details of calling the Windows API procedure.
Public Function WinAPIURLDownloadToFile(URL$, DownloadPath$) As Boolean
'''Returns True if File Download Starts Successfully
Dim FileDir$
FileDir = Left$(DownloadPath, InStrRev(DownloadPath, "\"))
'Validate download file path
If Dir(FileDir, vbDirectory) = "" Then
Err.Raise 76 'Path not found
End If
'Validate file does not already exist
If Dir(DownloadPath) <> "" Then
Err.Raise 58 'File already exists
End If
#If VBA7 = 1 Then
Dim Result As LongPtr
#Else
Dim Result As Long
#End If
'Download file
Result = URLDownloadToFile(0, URL, DownloadPath, 0, 0)
'Return result of WinAPI function
WinAPIURLDownloadToFile = Result = &H0
End Function
Public Sub Example()
Dim FileURL As String
FileURL = "https://www.vbaplanet.com/downloads/Win32API_PtrSafe.txt"
Dim DownloadPath As String
DownloadPath = Environ$("USERPROFILE") & "\Desktop\Example_WinAPI_PtrSafe.txt"
Dim Result As Boolean
Result = WinAPIURLDownloadToFile(FileURL, DownloadPath)
Debug.Print Result
End Sub
Procedure Type
Windows API procedures that do not return a value should be declared in VBA as a Subs. Windows API procedures that do return a value should be declared in VBA as Functions.
Data Types
Data types need to be matched with equivalent types between VBA and C++.
Type | VBA | C++ |
---|---|---|
16-bit signed integer | Boolean | SHORT (True = -1, False = 0) |
8-bit unsigned integer | Byte | BYTE |
16-bit signed integer | Integer | SHORT |
32-bit signed integer | Long | INT or LONG |
64-bit signed integer | LongLong | LONGLONG |
4-byte single precision floating point | Single | FLOAT |
8-byte double precision floating point | Double | DOUBLE |
8-byte double precision floating point | Date | DATE (defined as double) |
CY data structure | Currency | CY (1 | 2) |
DECIMAL data structure | Decimal | DECIMAL (1 | 2) |
VARIANT data structure | Variant | VARIANT |
SAFEARRAY data structure | Array | SAFEARRAY |
BSTR data structure | String | BSTR |
IDispatch interface | Object | IDispatch |
Passing Pointers
VBA provides pointer functions which can be used to retrieve pointers to variables. In VBA7 these functions return a LongPtr and in VBA6 they return a Long.
Pointer Functions:
- ObjPtr - Returns a pointer to an object.
- StrPtr - Returns a pointer to the string portion of a VBA string variable.
- VarPtr - Returns a pointer to a variable.
Passing Arrays
To pass an array to a DLL procedure, pass the first element of the array ByRef.
// C++ 64-bit ExcelFunctions.dll
#define DLLExport __declspec(dllexport)
extern "C" {
DLLExport int __stdcall CppSum(int nums[], int n) {
int theSum = 0;
for (int i = 0; i < n; ++i) {
theSum += nums[i];
}
return theSum;
}
}
Option Explicit
Declare PtrSafe Function CppSum Lib "C:\ExcelFunctions.dll" ( _
ByRef Nums As Long, ByVal N As Long) As Long
Public Sub Example()
Dim Arr() As Long
ReDim Arr(0 To 4)
Arr(0) = 1
Arr(1) = 2
Arr(2) = 3
Arr(3) = 4
Arr(4) = 5
Dim ArrSize As Long
ArrSize = UBound(Arr) - LBound(Arr) + 1
Dim Result As Long
Result = CppSum(Arr(LBound(Arr)), ArrSize)
Debug.Print Result
End Sub
Passing Functions as Arguments
The AddressOf operator is used to pass the address of a procedure to an API function which requires a function pointer as a parameter.
// C++ 64-bit ExampleDLL.dll
#define DLLExport __declspec(dllexport)
extern "C" {
DLLExport void __stdcall CppCallProcedure(void (*funcPtr)()) {
funcPtr();
}
Option Explicit
Public Declare PtrSafe Sub CppCallProcedure Lib "ExcelFunctions.dll" (ByVal Func As LongPtr)
Public Sub Example()
Call CppCallProcedure(AddressOf HelloWorld)
End Sub
Public Sub HelloWorld()
MsgBox "Hello, World!"
End Sub
Passing And Returning Structs
It is possible to pass and return structs to and from Windows API procedures by using User-Defined Types in VBA.
Certain structs contain a member representing the size of the struct. The LenB function can be used to find the size in bytes of the user-defined type.
Example: GetOpenFileName
This example declares the GetOpenFileNameA function which utilizes the OPENFILENAMEA data structure.
Option Explicit
Public Sub Example()
On Error GoTo HandleError
Dim FileName As String
FileName = GetOpenFileNameWinAPI()
Debug.Print FileName
Exit Sub
HandleError:
If Err.Number - (vbObjectError + 512) = 2 Then
MsgBox "No file selected."
Else
Err.Raise _
Number:=Err.Number, _
Description:=Err.Description
End If
End Sub
typedef struct tagOFNA {
DWORD lStructSize;
HWND hwndOwner;
HINSTANCE hInstance;
LPCSTR lpstrFilter;
LPSTR lpstrCustomFilter;
DWORD nMaxCustFilter;
DWORD nFilterIndex;
LPSTR lpstrFile;
DWORD nMaxFile;
LPSTR lpstrFileTitle;
DWORD nMaxFileTitle;
LPCSTR lpstrInitialDir;
LPCSTR lpstrTitle;
DWORD Flags;
WORD nFileOffset;
WORD nFileExtension;
LPCSTR lpstrDefExt;
LPARAM lCustData;
LPOFNHOOKPROC lpfnHook;
LPCSTR lpTemplateName;
LPEDITMENU lpEditInfo;
LPCSTR lpstrPrompt;
void *pvReserved;
DWORD dwReserved;
DWORD FlagsEx;
} OPENFILENAMEA, *LPOPENFILENAMEA;
Option Explicit
#If VBA7 = 1 Then
Private Type OPENFILENAME
lStructSize As Long
hwndOwner As LongPtr
hInstance As LongPtr
lpstrFilter As String
lpstrCustomFilter As String
nMaxCustFilter As Long
nFilterIndex As Long
lpstrFile As String
nMaxFile As Long
lpstrFileTitle As String
nMaxFileTitle As Long
lpstrInitialDir As String
lpstrTitle As String
flags As Long
nFileOffset As Integer
nFileExtension As Integer
lpstrDefExt As String
lCustData As LongPtr
lpfnHook As LongPtr
lpTemplateName As String
'#if (_WIN32_WINNT >= 0x0500)
pvReserved As LongPtr
dwReserved As Long
FlagsEx As Long
'#endif // (_WIN32_WINNT >= 0x0500)
End Type
Private Declare PtrSafe Function GetOpenFileName _
Lib "comdlg32.dll" _
Alias "GetOpenFileNameA" ( _
pOpenfilename As OPENFILENAME) As Long
Private Declare PtrSafe Function CommDlgExtendedError _
Lib "comdlg32.dll" () As Long
#Else
Private Type OPENFILENAME
lStructSize As Long
hwndOwner As Long
hInstance As Long
lpstrFilter As String
lpstrCustomFilter As String
nMaxCustFilter As Long
nFilterIndex As Long
lpstrFile As String
nMaxFile As Long
lpstrFileTitle As String
nMaxFileTitle As Long
lpstrInitialDir As String
lpstrTitle As String
flags As Long
nFileOffset As Integer
nFileExtension As Integer
lpstrDefExt As String
lCustData As Long
lpfnHook As Long
lpTemplateName As String
'#if (_WIN32_WINNT >= 0x0500)
pvReserved As Long
dwReserved As Long
FlagsEx As Long
'#endif // (_WIN32_WINNT >= 0x0500)
End Type
Private Declare Function GetOpenFileName _
Lib "comdlg32.dll" _
Alias "GetOpenFileNameA" ( _
pOpenfilename As OPENFILENAME) As Long
Private Declare Function CommDlgExtendedError _
Lib "comdlg32.dll" () As Long
#End If
Private Const OFN_READONLY = &H1
Private Const OFN_OVERWRITEPROMPT = &H2
Private Const OFN_HIDEREADONLY = &H4
Private Const OFN_NOCHANGEDIR = &H8
Private Const OFN_SHOWHELP = &H10
Private Const OFN_ENABLEHOOK = &H20
Private Const OFN_ENABLETEMPLATE = &H40
Private Const OFN_ENABLETEMPLATEHANDLE = &H80
Private Const OFN_NOVALIDATE = &H100
Private Const OFN_ALLOWMULTISELECT = &H200
Private Const OFN_EXTENSIONDIFFERENT = &H400
Private Const OFN_PATHMUSTEXIST = &H800
Private Const OFN_FILEMUSTEXIST = &H1000
Private Const OFN_CREATEPROMPT = &H2000
Private Const OFN_SHAREAWARE = &H4000
Private Const OFN_NOREADONLYRETURN = &H8000&
Private Const OFN_NOTESTFILECREATE = &H10000
Private Const OFN_NONETWORKBUTTON = &H20000
Private Const OFN_NOLONGNAMES = &H40000 ' force no long names for 4.x modules
Private Const OFN_EXPLORER = &H80000 ' new look commdlg
Private Const OFN_NODEREFERENCELINKS = &H100000
Private Const OFN_LONGNAMES = &H200000 ' force long names for 3.x modules
Private Const OFN_SHAREFALLTHROUGH = 2
Private Const OFN_SHARENOWARN = 1
Private Const OFN_SHAREWARN = 0
Private Const CDERR_DIALOGFAILURE = &HFFFF&
Private Const CDERR_GENERALCODES = &H0
Private Const CDERR_STRUCTSIZE = &H1
Private Const CDERR_INITIALIZATION = &H2
Private Const CDERR_NOTEMPLATE = &H3
Private Const CDERR_NOHINSTANCE = &H4
Private Const CDERR_LOADSTRFAILURE = &H5
Private Const CDERR_FINDRESFAILURE = &H6
Private Const CDERR_LOADRESFAILURE = &H7
Private Const CDERR_LOCKRESFAILURE = &H8
Private Const CDERR_MEMALLOCFAILURE = &H9
Private Const CDERR_MEMLOCKFAILURE = &HA
Private Const CDERR_NOHOOK = &HB
Private Const CDERR_REGISTERMSGFAIL = &HC
Public Function GetOpenFileNameWinAPI() As String
Const MAX_BUFFER As Long = 255
Dim OFN As OPENFILENAME
With OFN
'.hwndOwner
'.hInstance
.lpstrFilter = "All Files" & vbNullChar & "*.*" & String$(2, vbNullChar)
'.lpstrCustomFilter
'.nMaxCustFilter
.nFilterIndex = 1
.lpstrFile = String$(MAX_BUFFER - 1, " ") & vbNullChar
.nMaxFile = Len(.lpstrFile)
.lpstrFileTitle = String$(MAX_BUFFER - 1, " ") & vbNullChar
.nMaxFileTitle = Len(.lpstrFileTitle)
.lpstrInitialDir = "C:\" & vbNullChar
'.lpstrTitle
.flags = OFN_FILEMUSTEXIST Or OFN_PATHMUSTEXIST
'.nFileOffset
'.nFileExtension
'.lpstrDefExt
'.lCustData
'.lpfnHook
'.lpTemplateName
'.pvReserved
'.dwReserved
'.FlagsEx
.lStructSize = LenB(OFN)
End With
Dim Result As Long
Result = GetOpenFileName(OFN)
If Result <> 0 Then
GetOpenFileNameWinAPI = Trim$(Replace(OFN.lpstrFile, vbNullChar, " "))
Else
Dim CDErr As Long
CDErr = CommDlgExtendedError()
Dim UDErr As Long
Dim ErrMsg As String
Select Case CDErr
Case CDERR_DIALOGFAILURE
UDErr = 1
ErrMsg = "DIALOG FAILURE"
Case CDERR_GENERALCODES
UDErr = 2
ErrMsg = "GENERAL CODES"
Case CDERR_STRUCTSIZE
UDErr = 3
ErrMsg = "STRUCT SIZE"
Case CDERR_INITIALIZATION
UDErr = 4
ErrMsg = "INITIALIZATION"
Case CDERR_NOTEMPLATE
UDErr = 5
ErrMsg = "NO TEMPLATE"
Case CDERR_NOHINSTANCE
UDErr = 6
ErrMsg = "NO HINSTANCE"
Case CDERR_LOADSTRFAILURE
UDErr = 7
ErrMsg = "LOAD STR FAILURE"
Case CDERR_FINDRESFAILURE
UDErr = 8
ErrMsg = "FIND RES FAILURE"
Case CDERR_LOADRESFAILURE
UDErr = 9
ErrMsg = "LOAD RES FAILURE"
Case CDERR_LOCKRESFAILURE
UDErr = 10
ErrMsg = "LOCK RES FAILURE"
Case CDERR_MEMALLOCFAILURE
UDErr = 11
ErrMsg = "MEM ALLOC FAILURE"
Case CDERR_MEMLOCKFAILURE
UDErr = 12
ErrMsg = "MEM LOCK FAILURE"
Case CDERR_NOHOOK
UDErr = 13
ErrMsg = "NO HOOK"
Case CDERR_REGISTERMSGFAIL
UDErr = 14
ErrMsg = "REGISTER MSG FAIL"
Case Else
UDErr = 15
ErrMsg = "Unknown Error"
End Select
Err.Raise _
Number:=vbObjectError + 512 + UDErr, _
Description:=ErrMsg
End If
End Function
Example: ChooseColor
This example declares the ChooseColor function which utilizes the CHOOSECOLOR struct.
Option Explicit
Public Sub Example()
On Error GoTo HandleError
Dim C As Long
C = ChooseColorWinAPI()
Debug.Print C
Range("A1").Interior.Color = C
Exit Sub
HandleError:
If Err.Number - (vbObjectError + 512) = 2 Then
MsgBox "No color selected."
Else
Err.Raise _
Number:=Err.Number, _
Description:=Err.Description
End If
End Sub
typedef struct tagCHOOSECOLORA {
DWORD lStructSize;
HWND hwndOwner;
HWND hInstance;
COLORREF rgbResult;
COLORREF *lpCustColors;
DWORD Flags;
LPARAM lCustData;
LPCCHOOKPROC lpfnHook;
LPCSTR lpTemplateName;
LPEDITMENU lpEditInfo;
} CHOOSECOLORA, *LPCHOOSECOLORA;
Option Explicit
#If VBA7 = 1 Then
Private Type ChooseColor
lStructSize As Long
hwndOwner As LongPtr
hInstance As LongPtr
rgbResult As Long
lpCustColors As LongPtr
flags As Long
lCustData As LongPtr
lpfnHook As LongPtr
lpTemplateName As String
End Type
Private Declare PtrSafe Function ChooseColor _
Lib "comdlg32.dll" _
Alias "ChooseColorA" ( _
pChoosecolor As ChooseColor) As Long
Private Declare PtrSafe Function CommDlgExtendedError _
Lib "comdlg32.dll" () As Long
#Else
Private Type ChooseColor
lStructSize As Long
hwndOwner As Long
hInstance As Long
rgbResult As Long
lpCustColors As Long
flags As Long
lCustData As Long
lpfnHook As Long
lpTemplateName As String
End Type
Private Declare Function ChooseColor _
Lib "comdlg32.dll" _
Alias "ChooseColorA" ( _
pChoosecolor As ChooseColor) As Long
Private Declare Function CommDlgExtendedError _
Lib "comdlg32.dll" () As Long
#End If
Private Const CC_RGBINIT = &H1
Private Const CC_FULLOPEN = &H2
Private Const CC_PREVENTFULLOPEN = &H4
Private Const CC_SHOWHELP = &H8
Private Const CC_ENABLEHOOK = &H10
Private Const CC_ENABLETEMPLATE = &H20
Private Const CC_ENABLETEMPLATEHANDLE = &H40
Private Const CC_SOLIDCOLOR = &H80
Private Const CC_ANYCOLOR = &H100
Private Const CDERR_DIALOGFAILURE = &HFFFF&
Private Const CDERR_GENERALCODES = &H0
Private Const CDERR_STRUCTSIZE = &H1
Private Const CDERR_INITIALIZATION = &H2
Private Const CDERR_NOTEMPLATE = &H3
Private Const CDERR_NOHINSTANCE = &H4
Private Const CDERR_LOADSTRFAILURE = &H5
Private Const CDERR_FINDRESFAILURE = &H6
Private Const CDERR_LOADRESFAILURE = &H7
Private Const CDERR_LOCKRESFAILURE = &H8
Private Const CDERR_MEMALLOCFAILURE = &H9
Private Const CDERR_MEMLOCKFAILURE = &HA
Private Const CDERR_NOHOOK = &HB
Private Const CDERR_REGISTERMSGFAIL = &HC
Public Function ChooseColorWinAPI() As Long
Dim CC As ChooseColor
Dim CustomColors(16) As Long
CustomColors(0) = RGB(0, 140, 140)
With CC
.hwndOwner = Application.Windows(1).Hwnd
'.hInstance
'.rgbResult
.lpCustColors = VarPtr(CustomColors(0))
.flags = CC_ANYCOLOR Or CC_FULLOPEN Or CC_PREVENTFULLOPEN Or CC_RGBINIT
'.lCustData
'.lpfnHook
'.lpTemplateName
.lStructSize = LenB(CC)
End With
Dim Result As Long
Result = ChooseColor(CC)
If Result <> 0 Then
ChooseColorWinAPI = CC.rgbResult
Else
ChooseColorWinAPI = RGB(0, 0, 0)
Dim CDErr As Long
CDErr = CommDlgExtendedError()
Dim UDErr As Long
Dim ErrMsg As String
Select Case CDErr
Case CDERR_DIALOGFAILURE
UDErr = 1
ErrMsg = "DIALOG FAILURE"
Case CDERR_GENERALCODES
UDErr = 2
ErrMsg = "GENERAL CODES"
Case CDERR_STRUCTSIZE
UDErr = 3
ErrMsg = "STRUCT SIZE"
Case CDERR_INITIALIZATION
UDErr = 4
ErrMsg = "INITIALIZATION"
Case CDERR_NOTEMPLATE
UDErr = 5
ErrMsg = "NO TEMPLATE"
Case CDERR_NOHINSTANCE
UDErr = 6
ErrMsg = "NO HINSTANCE"
Case CDERR_LOADSTRFAILURE
UDErr = 7
ErrMsg = "LOAD STR FAILURE"
Case CDERR_FINDRESFAILURE
UDErr = 8
ErrMsg = "FIND RES FAILURE"
Case CDERR_LOADRESFAILURE
UDErr = 9
ErrMsg = "LOAD RES FAILURE"
Case CDERR_LOCKRESFAILURE
UDErr = 10
ErrMsg = "LOCK RES FAILURE"
Case CDERR_MEMALLOCFAILURE
UDErr = 11
ErrMsg = "MEM ALLOC FAILURE"
Case CDERR_MEMLOCKFAILURE
UDErr = 12
ErrMsg = "MEM LOCK FAILURE"
Case CDERR_NOHOOK
UDErr = 13
ErrMsg = "NO HOOK"
Case CDERR_REGISTERMSGFAIL
UDErr = 14
ErrMsg = "REGISTER MSG FAIL"
Case Else
UDErr = 15
ErrMsg = "Unknown Error"
End Select
Err.Raise _
Number:=vbObjectError + 512 + UDErr, _
Description:=ErrMsg
End If
End Function