I am currently adding Windows 7 support to an existing Vb6 project and I have ran into a problem with locating special folder paths using SHGetFolderPath which is not supported on Windows versions starting with Vista. I know I should use SHGetKnownFolderPath but I cannot find a good example implementing using SHGetKnownFolderPath API call in VB6.
4 Answers
Easier to use the Shell object Late binding is advised because Microsoft haven't been careful about compatibility with this object.
Const ssfCOMMONAPPDATA = &H23
Const ssfLOCALAPPDATA = &H1c
Const ssfAPPDATA = &H1a
Dim strAppData As String
strAppData = _
CreateObject("Shell.Application").NameSpace(ssfAPPDATA).Self.Path
Using SHGetFolderPath
from shfolder.dll
just works fine under Vista and Win7:
Private Declare Function SHGetFolderPath Lib "shfolder" Alias "SHGetFolderPathA" (ByVal hWnd As Long, ByVal csidl As Long, ByVal hToken As Long, ByVal dwFlags As Long, ByVal szPath As String) As Long
Then declare an enum on those CSIDL_Xxx
constants:
Public Function GetSpecialFolder(ByVal eType As MySpecialFolderType) As String
GetSpecialFolder = String(1000, 0)
Call SHGetFolderPath(0, eType, 0, 0, GetSpecialFolder)
GetSpecialFolder = Left$(GetSpecialFolder, InStr(GetSpecialFolder, Chr$(0)) - 1)
End Function
Using the code the the following this article vba/vb6 Declaring the API call at the top of module WINAPI32.bas
Private Declare Function SHGetSpecialFolderLocation Lib "shell32.dll" _
(ByVal hwndOwner As Long, ByVal nFolder As Long, _
pidl As ITEMIDLIST) As Long
Private Declare Function SHGetPathFromIDList Lib "shell32.dll" Alias "SHGetPathFromIDListA" _
(ByVal pidl As Long, ByVal pszPath As String) As Long
Private Type SHITEMID
cb As Long
abID As Byte
End Type
Private Type ITEMIDLIST
mkid As SHITEMID
End Type
Added a new public function:
Public Function SHGetSpecialFolderLocationVB(ByVal lFolder As Long) As String
Dim lRet As Long, IDL As ITEMIDLIST, sPath As String
lRet = SHGetSpecialFolderLocation(100&, lFolder, IDL)
If lRet = 0 Then
sPath = String$(512, chr$(0))
lRet = SHGetPathFromIDList(ByVal IDL.mkid.cb, ByVal sPath)
SHGetSpecialFolderLocationVB = Left$(sPath, InStr(sPath, chr$(0)) - 1)
Else
SHGetSpecialFolderLocationVB = vbNullString
End If
End Function
Added a new function to check for Windows versions Vista or higher
Public Function IsVistaOrHigher() As Boolean
Dim osinfo As OSVERSIONINFO
Dim retvalue As Integer
Dim bVista As Boolean
bVista = False
osinfo.dwOSVersionInfoSize = 148
osinfo.szCSDVersion = Space$(128)
retvalue = GetVersionExA(osinfo)
If osinfo.dwPlatformId = 2 Then
If osinfo.dwMajorVersion >= 6 Then
bVista = True
End If
End If
IsVistaOrHigher = bVista
End Function
Altered the previous method calling SHGetFolderPath
Public Function SHGetFolderPathVB(ByVal lFolder As Long) As String
Dim path As String
If IsVistaOrHigher() Then
SHGetFolderPathVB = SHGetSpecialFolderLocationVB(lFolder)
Else
path = Space$(MAX_PATH)
SHGetFolderPath 0, lFolder, 0, SHGFP_TYPE_CURRENT, path
SHGetFolderPathVB = Left(path, InStr(path, vbNullChar) - 1)
End If
End Function
Works great!
A very late answer. But it actually shows how to use SHGetKnownFolderPath
in x64 VBA and no workarounds to avoid it.
I used this German source: https://dbwiki.net/wiki/VBA_Tipp:_Spezielle_Verzeichnisse_ermitteln
The solution given there doesn’t work on x64 Office. So I changed it. Calling a native DLL from VBA requires
- Usage of the new keyword
PtrSafe
. - Usage of
LongPtr
instead ofLong
for all pointers. - Conversion of VBA strings to
LongPtr
objects by means of the functionStrPtr
. - Calling the Unicode version of the DLL, usually marked with a “W.”
Code:
Public Const FOLDERID_ProgramFiles1 As String = "{905E63B6-C1BF-494E-B29C-65B732D3D21A}"
Public Type GUID
Data1 As Long
Data2 As Integer
Data3 As Integer
Data4(7) As Byte
End Type
Public Const S_OK As Long = 0
Public Const WIN32_NULL As Long = 0
Public Declare PtrSafe Sub CoTaskMemFree Lib "ole32" (ByVal hMem As LongPtr)
Public Declare PtrSafe Function CLSIDFromString Lib "ole32" ( _
ByVal lpszGuid As LongPtr, _
ByRef pGuid As GUID) As Long
Public Declare PtrSafe Function lstrlenW Lib "kernel32" ( _
ByVal lpString As LongPtr) As Long
Public Declare PtrSafe Function SHGetKnownFolderPath Lib "shell32" ( _
ByRef rfid As GUID, _
ByVal dwFlags As Long, _
ByVal hToken As Long, _
ByRef pszPath As LongPtr) As Long
Public Declare PtrSafe Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" ( _
ByVal Destination As LongPtr, _
ByVal Source As LongPtr, _
ByVal length As Long)
Public Function GetBstrFromWideStringPtr(ByVal lpwString As LongPtr) As String
Dim length As Long
If (lpwString) Then length = lstrlenW(lpwString)
If (length) Then
GetBstrFromWideStringPtr = Space$(length)
CopyMemory StrPtr(GetBstrFromWideStringPtr), lpwString, length * 2
End If
End Function
Public Function GetKnownFolder(ByVal KnownFolderID As String) As String
'Returns empty String on any error.
Dim ref As GUID
Dim pszPath As LongPtr
If (CLSIDFromString(StrPtr(KnownFolderID), ref) = S_OK) Then
If (SHGetKnownFolderPath(ref, 0, WIN32_NULL, pszPath) = S_OK) Then
GetKnownFolder = GetBstrFromWideStringPtr(pszPath)
CoTaskMemFree pszPath
End If
End If
End Function
Sub TestKnownFolder()
MsgBox GetKnownFolder(FOLDERID_ProgramFiles1)
End Sub
At the above link, you can find all FOLDERID_Blah
strings.