Wininet.dll crashes excel 64 bit when extracting cookies - excel

my company moved from 32 bit excel to 64 and and now macros that used to pull cookies keeps crashing. I know about PtrSafe declaration, but this no longer works. K googled trying to find the correct declaration for it but can't seem to get it right. Maybe can someone point out where the LongLong or LongPtr needs to be used? Below code is my n-th try with no luck:
'clear current cookies
Private Declare PtrSafe Function InternetSetOption Lib "wininet.dll" Alias "InternetSetOptionA" (ByVal hInternet As Long, ByVal lOption As Long, ByVal sBuffer As String, ByVal lBufferLength As Long) As LongPtr
'retrieve cookie
Private Declare PtrSafe Function InternetGetCookieEx Lib "wininet.dll" Alias "InternetGetCookieExA" (ByVal pchURL As String, ByVal pchCookieName As String, ByVal pchCookieData As String, ByRef pcchCookieData As Integer, ByVal dwFlags As Integer, ByVal lpReserved As Integer) As Boolean
Private Const INTERNET_OPTION_END_BROWSER_SESSION = 42
Private Const INTERNET_COOKIE_HTTPONLY As Integer = &H2000
Private Sub UserForm_Initialize()
Call InternetSetOption(0, INTERNET_OPTION_END_BROWSER_SESSION, 0, 0)
WebBrowser1.Silent = True
'WebBrowser1.Navigate "salesforce.com"
WebBrowser1.Navigate "salesforce.com"
End Sub
Private Sub WebBrowser1_DocumentComplete(ByVal pDisp As Object, URL As Variant)
If URL Like "*/home.jsp*" Then
Call InternetGetCookieEx(URL, "sid", sessionId, 256, INTERNET_COOKIE_HTTPONLY, vbNull)
Unload Me
End If
End Sub

The answer is to check the Microsoft documentation for these commands. When you are calling native methods like this you will find the documentation is in C++ so we must do some translation.
We can also look at what Microsoft says about LongPtr.
LongPtr is not a true data type because it transforms to a Long in
32-bit environments, or a LongLong in 64-bit environments. Using
LongPtr enables writing portable code that can run in both 32-bit and
64-bit environments. Use LongPtr for pointers and handles.
In your case that will be at least the handle hInternet but I would also cover the buffer length to avoid overflow problems. I would also convert all Integers to Longs.
Private Declare PtrSafe Function InternetSetOption Lib "wininet.dll" Alias "InternetSetOptionA" (ByVal hInternet As LongPtr, ByVal lOption As Long, ByVal sBuffer As String, ByVal lBufferLength As LongPtr) As LongPtr
Private Declare PtrSafe Function InternetGetCookieEx Lib "wininet.dll" Alias "InternetGetCookieExA" (ByVal pchURL As String, ByVal pchCookieName As String, ByVal pchCookieData As String, ByRef pcchCookieData As Long, ByVal dwFlags As Long, ByVal lpReserved As LongPtr) As Boolean
EDIT: Let's also declare lpReserved As LongPtr because it starts with lp, which could mean Long Pointer.
ERRORS YOU MUST GUARD FROM:
To avoid unhandled exceptions in native code, check the exceptions that this command can throw and guard against them before calling in to native methods.
Return code Description
ERROR_NO_MORE_ITEMS
There is no cookie for the specified URL and all its parents.
ERROR_INSUFFICIENT_BUFFER
The value passed in lpdwSize is insufficient to copy all the cookie
data. The value returned in lpdwSize is the size of the buffer
necessary to get all the data.
ERROR_INVALID_PARAMETER
One or more of the parameters is invalid.
The lpszUrl parameter is NULL.

Related

VBA to open Onscreen keyboard in 64bit Excel

I am trying to open the osk.exe from VBA within Excel 64 bit on Windows 10 64 bit.
I have pieced together the following code that works for 32bit Excel on 64bit Windows 10, but I don't know how to modify it to get it working again with 64bit Excel:
Option Explicit
Type SHELLEXECUTEINFO
cbSize As LongPtr
fMask As Long
hwnd As LongPtr
lpVerb As String
lpFile As String
lpParameters As String
lpDirectory As String
nShow As Long
hInstApp As Long
lpIDList As Long
lpClass As String
hkeyClass As Long
dwHotKey As Long
hIcon As LongPtr
hProcess As Long
End Type
Public Declare PtrSafe Function ShellExecuteEx Lib "shell32.dll" _
(lpExecInfo As SHELLEXECUTEINFO) As LongPtr
Declare PtrSafe Function Wow64DisableWow64FsRedirection Lib "kernel32.dll" (ByRef ptr As LongPtr) As Boolean
Declare PtrSafe Function Wow64RevertWow64FsRedirection Lib "kernel32.dll" (ByRef ptr As LongPtr) As Boolean
Public Function KeyboardOpen()
Dim shInfo As SHELLEXECUTEINFO
Dim lngPtr As LongPtr
With shInfo
.cbSize = Len(shInfo)
.lpFile = "C:\Windows\Sysnative\cmd.exe" 'best to use Known folders here
.lpParameters = "/c start osk.exe"
.lpDirectory = "C:\windows\system32" 'best to use Known folders here
.lpVerb = "open"
.nShow = 0
End With
Call Wow64DisableWow64FsRedirection(lngPtr)
Call ShellExecuteEx(shInfo)
Call Wow64RevertWow64FsRedirection(lngPtr)
End Function
Sub OpenKeyboard()
Call KeyboardOpen
End Sub
I have found a solution. To get the 64bit Windows 10 On screen keyboard (osk.exe) to run, add the following code to a module to a 64bit Excel, then you can call OpenKeyboardSub from within your application:
Private Declare PtrSafe Function ShellExecute Lib "shell32.dll" Alias "ShellExecuteA" (ByVal hWnd As Long, ByVal lpOperation As String, _
ByVal lpFile As String, ByVal lpParameters As String, ByVal lpDirectory As String, ByVal nShowCmd As Long) As Long
Sub OpenKeyboardSub()
ShellExecute 0, vbNullString, "osk.exe", vbNullString, "C:\", 1
End Sub

How to fix a VBA "type mismatch" error after switching to 64-bit Excel

I was using code that was working fine when I was running the 32-bit version of Excel. After I was switched to the 64-Bit version, the macro broke. I updated the dll calls to use LongPtr everywhere instead of Long.
Is there any way to determine which arguments and return types need to be changed for VBA7, and which don't, for a specific Declare Function?
Here is an example of some of the "Declare Functions" that I have updated (there were actually several more too).
#If VBA7 Then
Private Declare PtrSafe Function CreateDC Lib "gdi32.dll" Alias "CreateDCA" (ByVal lpDriverName As String, ByVal lpDeviceName As String, ByVal lpOutput As String, lpInitData As LongPtr) As LongPtr
Private Declare PtrSafe Function CreateCompatibleBitmap Lib "gdi32.dll" (ByVal hdc As LongPtr, ByVal nWidth As LongPtr, ByVal nHeight As LongPtr) As LongPtr
Private Declare PtrSafe Function DeleteDC Lib "gdi32.dll" (ByVal hdc As LongPtr) As LongPtr
Private Const LOGPIXELSY As Long = 90
#Else
Private Declare CreateDC Lib "gdi32.dll" Alias "CreateDCA" (ByVal lpDriverName As String, ByVal lpDeviceName As String, ByVal lpOutput As String, lpInitData As Long) As Long
Private Declare Function CreateCompatibleBitmap Lib "gdi32.dll" (ByVal hdc As Long, ByVal nWidth As Long, ByVal nHeight As Long) As Long
Private Const LOGPIXELSY As Long = 90
#End If
This code was adapted from an answer to this question:
vb macro string width
Relevant snippet is copied here below:
Private Function GetLabelSize(text As String, font As StdFont) As SIZE
Dim tempDC As Long
Dim tempBMP As Long
Dim f As Long
Dim lf As LOGFONT
Dim textSize As SIZE
tempDC = CreateDC("DISPLAY", vbNullString, vbNullString, ByVal 0)
tempBMP = CreateCompatibleBitmap(tempDC, 1, 1)
I get a runtime error that just says "Compile Error: Type Mismatch." The function call to CreateDC is highlighted, and the debugger breaks on the function GetLabelSize. I have no idea which variable is now causing the error. I'm also assuming that once I fix this first error, I'll have other errors too.
Do I need to pass the value of that last argument (ByVal 0) as an explicitly typed variable? If so how?
I updated the dll calls to use LongPtr everywhere instead of Long.
You should have not done that.
By adding PtrSafe to a function declaration, you promise to the compiler that you have put LongPtr in all places where it needs to be, and nowhere else.
LongPtr is a pointer-sized integer. It must be used for things that have the same size as a pointer.
To learn which Windows API types should be described as LongPtr, you must look at the original function signature, consult https://learn.microsoft.com/en-us/windows/win32/winprog/windows-data-types, track the used data types through all the typedefs down to the basic types, and use LongPtr for those that are pointers to things.
For the functions that you have shown, that would be
#If VBA7 Then
Private Declare PtrSafe Function CreateDC Lib "gdi32.dll" Alias "CreateDCA" (ByVal lpDriverName As String, ByVal lpDeviceName As String, ByVal lpOutput As String, ByVal lpInitData As LongPtr) As LongPtr
Private Declare PtrSafe Function CreateCompatibleBitmap Lib "gdi32.dll" (ByVal hdc As LongPtr, ByVal nWidth As Long, ByVal nHeight As Long) As LongPtr
Private Declare PtrSafe Function DeleteDC Lib "gdi32.dll" (ByVal hdc As LongPtr) As Long
#Else
Private Declare Function CreateDC Lib "gdi32.dll" Alias "CreateDCA" (ByVal lpDriverName As String, ByVal lpDeviceName As String, ByVal lpOutput As String, ByVal lpInitData As Long) As Long
Private Declare Function CreateCompatibleBitmap Lib "gdi32.dll" (ByVal hdc As Long, ByVal nWidth As Long, ByVal nHeight As Long) As Long
Private Declare Function DeleteDC Lib "gdi32.dll" (ByVal hdc As Long) As Long
#End If
When you declare variables to hold your LongPtr results, you need to use #If VBA7 too:
#If VBA7 Then
Dim tempDC As LongPtr
Dim tempBMP As LongPtr
#Else
Dim tempDC As Long
Dim tempBMP As Long
#End If
If you don't have to support Office 2007 and older, you can ditch the #If VBA7s and only use the LongPtr branch.

HeapFree is crashing

Summary:
I have VBA code that collects lots of info and writes it out into one or more worksheets. To improve perf writing lots of info into sheets, I created a class that acts kind of like a buffered copy/paste stream: the caller sends it CSV format strings which it buffers in memory until the buffer is full; when full, it pastes into a sheet, clears the buffer and continues.
Initially, I used Global memory, but then saw on MSDN a recommendation to use Heap rather than Global or Local due to less overhead. So now I'm using Heap.
I'm in the process of adapting everything for 64-bit Office. After doing all the PtrSafe stuff, I can run the code. But now Excel crashes when HeapFree() is called.
Question: Why is it crashing, and what do I need to change to avoid it?
Details:
I've come up with the following that is a minimized sample that repro's the crash: there's a module with a sub I can run for the repro, and the class. This doesn't do the buffering; every call to .SendText will put the text on the clipboard and paste into the active cell.
First the module. This has the following declare statements
' memory APIs
Public Const HEAP_ZERO_MEMORY = &H8
Declare PtrSafe Function GetProcessHeap Lib "kernel32" () As LongPtr 'returns HANDLE
Declare PtrSafe Function HeapAlloc Lib "kernel32" (ByVal hHeap As LongPtr, ByVal dwFlags As Long, ByVal dwBytes As LongPtr) As LongPtr 'returns HANDLE
Declare PtrSafe Function HeapFree Lib "kernel32" (ByVal hHeap As LongPtr, ByVal dwFlags As Long, lpMem As Any) As Long 'returns BOOL
Declare PtrSafe Function lstrcpy Lib "kernel32" Alias "lstrcpyW" (ByVal lpDestString As Any, ByVal lpSrcString As Any) As LongPtr 'returns HANDLE
' clipboard APIs
Public Const CF_UNICODETEXT = 13
Declare PtrSafe Function OpenClipboard Lib "user32" (ByVal hwnd As LongPtr) As Long 'returns BOOL
Declare PtrSafe Function EmptyClipboard Lib "user32" () As Long 'returns BOOL
Declare PtrSafe Function CloseClipboard Lib "user32" () As Long 'returns BOOL
Declare PtrSafe Function SetClipboardData Lib "user32" (ByVal wFormat As Long, ByVal hMem As LongPtr) As LongPtr 'returns HANDLE
Then the sub:
Private Sub TestMemoryBugRepro()
Dim s As String
Dim clip As clsHeapBugRepro
s = Chr(34) & "a" & vbLf & "b" & Chr(34) & ",c" & vbCrLf & "d" & vbTab & ",e" & vbCrLf
Set clip = New clsHeapBugRepro
clip.Initialize &H100
clip.SendText s
'Crash happens during the following
Set clip = Nothing
End Sub
Now the class. The crash occurs during Class_Terminate() when HeapFree is called.
Option Explicit
Private m_hHeap As LongPtr 'handle to the process heap
Private m_hMem As LongPtr 'handle to memory
Private m_pMem As LongPtr 'pointer to locked memory
Private m_cbMem As Long 'size of the memory buffer
Private m_BytesWritten As Long '
'**************************************
' Event procedures
Private Sub Class_Initialize()
m_hHeap = GetProcessHeap()
End Sub
Private Sub Class_Terminate()
If m_hMem <> 0 And m_hHeap <> 0 Then
HeapFree m_hHeap, 0, m_hMem 'CRASH OCCURS HERE
End If
End Sub
'**************************************
' Public methods
Public Function Initialize(Optional bufferSize As Long = &H8000) As Boolean
Initialize = False
m_BytesWritten = 0
If m_hHeap <> 0 Then
m_cbMem = bufferSize
m_hMem = HeapAlloc(m_hHeap, (HEAP_ZERO_MEMORY), m_cbMem)
End If
If m_hMem <> 0 Then Initialize = True
End Function
Public Function SendText(text As String) As Boolean
Dim nStrLen As Long
nStrLen = LenB(text) + 2&
Debug.Assert nStrLen < (m_cbMem + m_BytesWritten)
m_pMem = m_hMem 'in lieu of locking heap memory
lstrcpy m_pMem, StrPtr(text)
m_pMem = 0 'in lieu of unlocking heap memory
m_BytesWritten = m_BytesWritten + nStrLen
DoEvents
OpenClipboard 0&
EmptyClipboard
SetClipboardData CF_UNICODETEXT, m_hMem
CloseClipboard
ActiveCell.PasteSpecial
DoEvents
SendText = True
End Function
There's an issue in how you're calling HeapFree, but your real issue is that you shouldn't be using HeapAlloc/HeapFree at all.
Memory allocated by HeapAlloc is not movable, whereas SetClipboardData requires it to be moveable.
Another consideration is that SetClipboardData transfers the ownership of the memory to the system, which means you should not free it yourself. (The application may not write to or free the data once ownership has been transferred to the system.)
So, I would try to rewrite your logic using GlobalAlloc, not HeapAlloc, and don't try to free memory after it is put on the clipboard.
Now, there was an issue in how you were calling HeapFree, if you were going to use it. HeapFree wants the pointer returned by HeapAlloc.
You were instead passing a pointer to that pointer, because you declared the third argument of HeapFree As Any, which means ByRef As Any.
Either redeclare the argument ByVal As LongPtr / ByVal As Any, e.g.:
Declare PtrSafe Function HeapFree Lib "kernel32" (ByVal hHeap As LongPtr, _
ByVal dwFlags As Long,
ByVal lpMem As LongPtr) As Long 'returns BOOL
or specify ByVal when calling it:
HeapFree m_hHeap, 0, ByVal m_hMem

VBA URLDownloadToFileA or URLOpenStreamA from URLMON - Asynchronously

I'm using URLDownloadToFileA API call to download files in VBA, which works fine (and is MUCH faster than WinHTTP or XMLHTTP), but it's synchronous only.
I've been searching for ways to use a URLMon API call asynchronously (perhaps using URLOpenStream instead of download-to-file), but haven't figured out a way to do this.
I stumbled upon VB6 code that might be able to do it here: http://www.mvps.org/emorcillo/download/vb6/adl.zip but I am not versed enough in coding to convert this to working VBA.
Please note: I do realize how to do this through XMLHTTP and WinHTTP with a class, but those are significantly slower than using the URLMon DLL API, so am hoping to find a solution there.
Code to do it synchronously with URLMon:
Private Declare PtrSafe Function URLDownloadToFileA Lib "URLMON" (ByVal pcaller As Long, ByVal szurl As String, ByVal szFileName As String, ByVal dwReserved As Long, ByVal lpfnCB As Long) As LongPtr
Private Declare PtrSafe Function URLOpenPullStreamA Lib "URLMON" (ByVal pcaller As Long, ByVal szurl As String, ByVal dwReserved As Long, ByVal lpfnCB As Long) As LongPtr
Sub test()
dim a&, b&, URL$
URL = "http://ipv4.download.thinkbroadband.com/100MB.zip"
a = URLOpenPullStreamA (0, URL, 0, 0)
b = URLDownloadToFileA(0, URL, "c:\testfiles\100MB.zip", 0, 0)
End Sub
So, this works - but I don't know how to capture the callback for a, and while downloading b it locks excel until the file is fully downloaded.
Any help would be greatly appreciated!
One way would be to start the VBA code in a separate Office application, have it exit upon the end of the synchronous download and monitor when it exits from the main application.

CheckTokenMembership in VB6 - Crashing on FreeSID on Windows 7 and Windows 2008

I am using the CheckTokenMembership Windows API to check if the user is an Administrator.
Here's the code:
Option Explicit
Private Const SECURITY_BUILTIN_DOMAIN_RID As Long = &H20
Private Const DOMAIN_ALIAS_RID_ADMINS As Long = &H220
Private Declare Function AllocateAndInitializeSid Lib "advapi32.dll" (pIdentifierAuthority As Any, ByVal nSubAuthorityCount As Byte, ByVal nSubAuthority0 As Long, ByVal nSubAuthority1 As Long, ByVal nSubAuthority2 As Long, ByVal nSubAuthority3 As Long, ByVal nSubAuthority4 As Long, ByVal nSubAuthority5 As Long, ByVal nSubAuthority6 As Long, ByVal nSubAuthority7 As Long, lpPSid As Long) As Long
Private Declare Sub FreeSid Lib "advapi32.dll" (ByVal pSid As Long)
Private Declare Function CheckTokenMembership Lib "advapi32.dll" (ByVal hToken As Long, ByVal pSidToCheck As Long, pbIsMember As Long) As Long
Private Type SID_IDENTIFIER_AUTHORITY
Value(0 To 5) As Byte
End Type
Private Function pvIsAdmin() As Boolean
Dim uAuthNt As SID_IDENTIFIER_AUTHORITY
Dim pSidAdmins As Long
Dim lResult As Long
uAuthNt.Value(5) = 5
If AllocateAndInitializeSid(uAuthNt, 2, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, 0, pSidAdmins) <> 0 Then
If CheckTokenMembership(0, pSidAdmins, lResult) <> 0 Then
pvIsAdmin = (lResult <> 0)
End If
Call FreeSid(pSidAdmins)
End If
End Function
Problem is that on Windows 7 and Windows 2008 SP2, the call to FreeSID is causing the app to crash. The crash is intermittent.
Has anyone encountered this problem?
Thanks!
EDIT:
I just rechecked my code and I found out that I declared FreeSID as such:
Private Declare Sub FreeSid Lib "advapi32.dll" (pSid As Long)
As compared to the above code, the pSid parameter here is not flagged as ByVal. I added the ByVal flag and the problem is no longer present. Somehow, I am not convinced that this fixed the problem. Can this possibly have fixed the problem?
Separate pvIsAdmin in a completely separate module and copy function declarations verbatim from the snippet. In AllocateAndInitializeSid lpPSid is ByRef. In FreeSid param is ByVal.

Resources