This is my first time asking for any help on stack overflow, let alone commenting so please be gentle with me :)
I am at a loss with this one, I will give as much information as possible.
Issue
I would like to preface, this code does not cause any crashes on the latest update of 0365, only on Version 1807 & earlier. It also does not crash on the 32 bit version at all which makes me think it's a 64 bit issue. My client cannot update from this version either so simply asking them to update is not going to be able to happen.
I have narrowed the crashing down to this particular section.
Public Function GetSpecialFolder(CSIDL As Long) As String
'*******************************************************************************
'* Function: GetSpecialFolder
'* Purpose: Wraps the apis to retrieve folders such as My Docs etc.
'*******************************************************************************
Dim idlstr As Long
Dim sPath As String
Dim IDL As ITEMIDLIST
Const MAX_LENGTH = 260
'Fill the IDL structure with the specified folder item.
On Error GoTo GetSpecialFolder_Error
idlstr = SHGetSpecialFolderLocation _
(0, CSIDL, IDL)
If idlstr = 0 Then
'Get the path from the IDL list, and return the folder adding final "\".
sPath = Space$(MAX_LENGTH)
**idlstr = SHGetPathFromIDList(ByVal IDL.mkid.cb, ByVal sPath)**
If idlstr Then
GetSpecialFolder = Left$(sPath, InStr(sPath, Chr$(0)) _
- 1) & "\"
End If
End If
procExit:
On Error Resume Next
Exit Function
GetSpecialFolder_Error:
CommonErrorHandler lngErrNum:=Err.Number, strErrDesc:=Err.Description, _
strProc:="GetSpecialFolder", strModule:="modWinAPI", lngLineNum:=Erl
Resume procExit
End Function
And here is the declaration
'File system
Public Declare PtrSafe Function SHGetSpecialFolderLocation Lib "shell32.dll" _
(ByVal hwndOwner As Long, ByVal nFolder As Long, pidl As ITEMIDLIST) As Long
Public Declare PtrSafe Function SHGetPathFromIDList Lib "shell32.dll" _
Alias "SHGetPathFromIDListA" (ByVal pidl As Long, ByVal pszPath As String) As Long
Private Declare PtrSafe Function GetTempPath Lib "kernel32" _
Alias "GetTempPathA" (ByVal nBufferLength As Long, ByVal lpBuffer As String) As Long
Private Declare PtrSafe Function ShellExecute Lib "shell32.dll" _
Alias "ShellExecuteA" (ByVal hwnd As LongPtr, ByVal lpOperation As String, _
ByVal lpFile As String, ByVal lpParameters As String, ByVal lpDirectory As String, _
ByVal nShowCmd As Long) As LongPtr
Private Type ITEMIDLIST
mkid As ShortItemId
End Type
Private Type ShortItemId
cb As Long
abID As Byte
End Type
I have tried adding LongPtr as suggested in documents I've found online but it hasn't helped.
Can anyone help me?
Thanks!
SHGetSpecialFolderLocation does not fill in the memory you allocate for ITEMIDLIST like Declared function usually do, it allocates a new piece of memory that you are later required to free with CoTaskMemFree. That makes it pointless to declare ITEMIDLIST as a structure in VBA to begin with (and your declaration is wrong anyway, cb must be Integer, and abID is a variable-length byte array, not a single byte).
If you needed to do something with individual members of a structure allocated in this way, you would have to copy them out of the returned pointer with CopyMemory. Luckily, you don't need to do any of that because SHGetSpecialFolderLocation returns a pointer to PIDLIST_ABSOLUTE, and SHGetPathFromIDList accepts PCIDLIST_ABSOLUTE:
Public Declare PtrSafe Function SHGetSpecialFolderLocation Lib "shell32.dll" _
(ByVal hwndOwner As LongPtr, ByVal nFolder As Long, ByRef pIdl As LongPtr) As Long
Public Declare PtrSafe Function SHGetPathFromIDList Lib "shell32.dll" _
Alias "SHGetPathFromIDListA" (ByVal pIdl As LongPtr, ByVal pszPath As String) As Long
Public Declare PtrSafe Sub CoTaskMemFree Lib "ole32.dll" (pv As Any)
Public Function GetSpecialFolder(ByVal CSIDL As Long) As String
Dim retval As Long
Dim pIdl As LongPtr
Dim sPath As String
Const MAX_LENGTH = 260
retval = SHGetSpecialFolderLocation(0, CSIDL, pIdl)
If retval = 0 Then
sPath = Space$(MAX_LENGTH)
retval = SHGetPathFromIDList(pIdl, sPath)
If retval <> 0 Then
GetSpecialFolder = Left$(sPath, InStr(sPath, Chr$(0)) - 1) & "\"
End If
CoTaskMemFree ByVal pIdl
End If
End Function
Note that it's pointless to have an On Error Goto in such function because Windows API generally do not raise exceptions, they return error codes. It would make sense if you used Err.Raise ... after finding out a return value indicates an error.
TBH, I have no clue how this was functioning correctly on a 32 bit build. The declarations for the two structures are incorrect. This one...
Private Type ShortItemId
cb As Long
abID As Byte
End Type
...is defined in the MS documentation as this:
typedef struct _SHITEMID {
USHORT cb;
BYTE abID[1];
} SHITEMID;
Note that abID is an array, and cb is an unsigned short (you can use an Integer for that in VBA, but it definitely is not a Long).
In addition, this structure (wrapped in the ITEMIDLIST) is not even supposed to be allocated by the caller, but must be freed by the caller:
It is the responsibility of the calling application to free the returned IDList by using CoTaskMemFree.
Re the pointers, the only pointers (that aren't being marshaled from String) are the
pidl parameter of SHGetSpecialFolderLocation and the pointer to ppidl in SHGetPathFromIDList. Note that you can't use a VBA defined struct, because you need to free the memory when you're done. Something like this will work:
Private Declare PtrSafe Function SHGetSpecialFolderLocation Lib "shell32.dll" _
(ByVal hwndOwner As Long, ByVal nFolder As Long, pidl As LongPtr) As Long
Private Declare PtrSafe Function SHGetPathFromIDList Lib "shell32.dll" _
Alias "SHGetPathFromIDListA" (ByVal pidl As LongPtr, ByVal pszPath As String) As Boolean
Private Declare PtrSafe Sub CoTaskMemFree Lib "ole32.dll" (ByVal pv As LongPtr)
Private Const S_OK As Long = 0
Private Const MAX_LENGTH = 260
Public Function GetSpecialFolder(ByVal CSIDL As Integer) As String
Dim result As Long
Dim path As String
Dim idl_ptr As LongPtr
'Fill the IDL structure with the specified folder item.
result = SHGetSpecialFolderLocation(0, CSIDL, idl_ptr)
If result = S_OK Then
'Get the path from the IDL list, and return the folder adding final "\".
path = Space$(MAX_LENGTH)
If SHGetPathFromIDList(idl_ptr, path) Then
GetSpecialFolder = Left$(path, InStr(path, vbNullChar) - 1) & "\"
End If
CoTaskMemFree idl_ptr
End If
End Function
Note that per the discussion in the comments, you could technically declare hwndOwner as LongPtr as well, but it shouldn't make any difference.
I've been using the same code from this answer and finding the same code whenever I search the internet for this, but I'm always getting a
type mismatch
in FindWindow function in the Sub AddIcon. Also tried this, downloaded the sample and got the same error. Any Idea why? I'm using 64-bit version. Thank you.
In 64bit version FindWindow returns a LongPtr instead of Long (32 bit).
Private Declare PtrSafe Function FindWindow Lib "user32" Alias "FindWindowA" _
(ByVal lpClassName As String, _
ByVal lpWindowName As String _
) As LongPtr '<-- FindWindow returns a LongPtr (for 64bit)
Therefore
hWnd = FindWindow(vbNullString, Me.Caption)
fails because hWnd was declared as Long in AddIcon() but should be LongPtr.
You can use
#If VBA7 Then
Dim hWnd As LongPtr
#Else
Dim hWnd As Long
#End If
To ensure it works for both 32 and 64 bit versions according to the declarations of the WinAPI functions.
Note: You might check if other variables declared as Long also need to be changed to LongPtr. Therefore just look at the 64bit declarations and what the functions return.
I used below code (got it from web site) in Access 2013. It worked without any issue.
Private Declare Function GdipCreateBitmapFromFile Lib "gdiplus.dll" (ByVal FileName As Long, bitmap As Long) As Long
If GdipCreateBitmapFromFile(StrPtr(sFileName), hPic) = 0 Then ....
After I remove 32 bit component for Access 2013 64 bit run time installation, I get compiler error. I add PtrSafe after Declare and compiler will be OK.
Private Declare PtrSafe Function GdipCreateBitmapFromFile Lib "gdiplus.dll" (ByVal FileName As Long, bitmap As Long) As Long
If GdipCreateBitmapFromFile(StrPtr(sFileName), hPic) = 0 Then ....
But, it will have run time error - type mismatch in StrPtr. Do not know how to solve it.
Use LongPtr for 64-bit instead of Long.
Private Declare PtrSafe Function GdipCreateBitmapFromFile Lib "gdiplus.dll" (ByVal FileName As LongPtr, bitmap As Long) As Long
Using VB6, I cannot change to VB .NET or anything else.
I'm trying to register a COM Library programmatically in the Form_Load() method of the invoking application.
The method I'm using below works as expected on Windows 7, both x86 and x64. However when I try and use the same application on Windows XP, I receive the Library not registered error:
http://imgur.com/zus2bK6
I have verified that the Library is being registered and it shows properly in the registry at HKEY_LOCAL_MACHINE\SOFTWARE\Classes\MyDll.Component as well as in HKEY_CLASSES_ROOT\AppID\Mydll.DLL
Here is the code I am using, can anyone tell me why this would be occurring on XP only and how to resolve it?
Private Declare Function DllRegisterServer Lib "MyDLL.dll" () As Long
Private Declare Function LoadLibrary Lib "kernel32" Alias "LoadLibraryA" (ByVal lpLibFileName As String) As Long
Private Declare Function GetProcAddress Lib "kernel32" (ByVal hModule As Long, ByVal lpProcName As String) As Long
Private Declare Function CallWindowProc Lib "user32" Alias "CallWindowProcA" (ByVal lpPrevWndFunc As Long, ByVal hwnd As Long, ByVal Msg As Any, ByVal wParam As Any, ByVal lParam As Any) As Long
Private Sub Form_Load()
ReDim ConfigFiles(0)
ReDim ConfigFilesToAdd(0)
libID = LoadLibrary("MyDLL.dll")
Dim pAdd As Long
pAdd = GetProcAddress(libID, "DllRegisterServer")
Dim lResult As Long
lResult = CallWindowProc(pAdd, 0&, 0&, 0&, 0&)
Set IGDep = CreateObject(MyDLL.Component")
End Sub
I have performed this process with all manner of permissions including the Administrator account and ensured that I had all permissions on the registry.
Thanks for any help you guys can give.
I have a not-too-complicated problem which I do not know how to solve in VB.NET 2010.
Currently I have a recursive routine for finding files/and or searching. With a recursive routine I never complete the call until the end and process growth is significant enough to radically slow down the process with the now large stack.
To make maters worse, I use precoding routines which will take wildcards. Which are these:
Public Declare Function FindFirstFile Lib "kernel32" Alias "FindFirstFileA" (ByVal lpFileName As String, ByRef lpFindFileData As WIN32_FIND_DATA) As Integer
Public Declare Function FindNextFile Lib "kernel32" Alias "FindNextFileA" (ByVal hFindFile As Integer, ByRef lpFindFileData As WIN32_FIND_DATA) As Integer
Public Declare Function GetFileAttributes Lib "kernel32" Alias "GetFileAttributesA" (ByVal lpFileName As String) As Integer
Public Declare Function FindClose Lib "kernel32" (ByVal hFindFile As Integer) As Integer
Public Declare Function LockWindowUpdate Lib "user32" Alias "LockWindowUpdate" (ByVal hwndLock As Long) As Long
How do I 'cure' this? They don't lend themselves to a non-recursive environment.
.NET 4.0 now contains the enumerate files functionality in a single method call (Directory.EnumerateFiles Method):
Returns an enumerable collection of file names that match a search
pattern in a specified path, and optionally searches subdirectories.
List<string> files = Directory.EnumerateFiles(startFolder, "*.txt",
SearchOption.AllDirectories).ToList();
VB.NET version:
Dim files As List(Of String) = Directory.EnumerateFiles(startFolder, "*.txt", SearchOption.AllDirectories).ToList()