Does anyone have a working 64 bit version of ShellExecuteEX ?
See https://docs.microsoft.com/en-us/windows/desktop/api/shellapi/nf-shellapi-shellexecuteexa
I have a 32 bit version which works fine, but I can’t seem to get my 64 bit version to work.
Figured it out - it was a StructureAlignment problem similar to https://forum.xojo.com/47513-help-with-declare/0
It appears that on Windows x64 builds, you generally need to have StructureAlignment set to 8.
Here’s my updated code which seems to work now:
Attributes( StructureAlignment = 8 ) Protected Structure SHELLEXECUTEINFOA_64
cbSize as uint32
fMask as uint32
HWND as Ptr
lpVerb as Ptr
lpFile as Ptr
lpParameters as Ptr
lpDirectory as Ptr
nShow as uint32
hInstApp as uint64
lpIDList as Ptr
lpClass as Ptr
hkeyClass as uint64
dwHotKey as uint32
hIcon as uint64
hProcess as uint64
End Structure
Public Function ShellExecuteExW64v3(verb as string, file as string, directory as string, parameters as string = "", className as string = "", asyncOK as boolean = true) as string
' this is the 64 bit version of the call, cleaned up to use a Structure and simpler string conversions
' note that in Win64, an "int" is still 32 bits, as is DWORD,
' but all the pointer types are now 64 bits.
' including Handles (but only the lower 32 bits are used)
' IMPORTANT - the SHELLEXECUTEINFOA_64 must have StructureAlignment set to 8
' see https://docs.microsoft.com/en-us/windows/desktop/WinProg/windows-data-types
' and https://forum.xojo.com/51910-shellexecuteex-64-bit-x64-declare
' and http://msdn.microsoft.com/en-us/library/bb762154.aspx
#if TargetWin32 and Target64Bit
'typedef struct _SHELLEXECUTEINFO {
'DWORD cbSize; 0 ## 4 bytes
'ULONG fMask; 4 ## 4 bytes
'HWND hwnd; 8
'LPCTSTR lpVerb; 16
'LPCTSTR lpFile; 24
'LPCTSTR lpParameters; 32
'LPCTSTR lpDirectory; 40
'int nShow; 48 ## 4 bytes
' (note: 4 bytes padding here)
'HINSTANCE hInstApp; 56
'LPVOID lpIDList; 64
'LPCTSTR lpClass; 72
'HKEY hkeyClass; 80
'DWORD dwHotKey; 88 ### 4 bytes
' (note: 4 bytes padding here)
'union { // 96
'HANDLE hIcon;
'HANDLE hMonitor;
'} DUMMYUNIONNAME;
'HANDLE hProcess; // 104
'} SHELLEXECUTEINFO, *LPSHELLEXECUTEINFO;
dim sei as SHELLEXECUTEINFOA_64
dim SIZEOF_SHELLEXECUTEINFO as integer = sei.Size // should be 112
const SW_MAXIMIZE = 3
const SW_SHOWNORMAL = 1
const SEE_MASK_UNICODE = &h00004000
const SEE_MASK_NO_CONSOLE = 32768
const SEE_MASK_CLASSNAME =1 // used when we explicitely set the association
const SEE_MASK_ASYNCOK = &h00100000
Soft Declare Function ShellExecuteExA lib kLibShell32 (ByRef lpExecInfo as SHELLEXECUTEINFOA_64) as Boolean
Soft Declare Function ShellExecuteExW lib kLibShell32 (ByRef lpExecInfo as SHELLEXECUTEINFOA_64) as Boolean
system.debugLog "ShellExecuteEx x64 verb= " + verb + " file= " + file + " directory=" + directory + " className=" + className
dim wideVersion as boolean = false
if System.IsFunctionAvailable( "ShellExecuteW", "Shell32" ) then
wideVersion=true
end if
dim mbVerb, mbFile, mbDirectory, mbParameters, mbClassName as memoryBlock
' convert to CString or WStrings
if wideVersion then
' use UTF-16 version
mbVerb = ConvertEncoding(verb + Chr(0), Encodings.UTF16)
mbFile = ConvertEncoding(file + Chr(0), Encodings.UTF16)
mbDirectory = ConvertEncoding(directory + Chr(0), Encodings.UTF16)
mbParameters = ConvertEncoding(parameters + Chr(0), Encodings.UTF16)
mbClassName = ConvertEncoding(className + Chr(0), Encodings.UTF16)
else
' ANSI version
mbVerb = verb + Chr(0)
mbFile = file + Chr(0)
mbDirectory = directory + Chr(0)
mbParameters = parameters + Chr(0)
mbClassName = className + Chr(0)
end if
sei.cbSize = SIZEOF_SHELLEXECUTEINFO
sei.fmask = SEE_MASK_NO_CONSOLE
if asyncOK then
sei.fmask = BitwiseOr(sei.fmask, SEE_MASK_ASYNCOK )
end if
sei.hwnd = 0
sei.lpVerb = mbVerb
sei.lpFile = mbFile
sei.lpParameters = mbParameters
sei.lpDirectory = mbDirectory
sei.nShow = SW_SHOWNORMAL
sei.hInstApp = 0
sei.lpIDList = nil
if className <> "" then
sei.lpClass = mbClassName
sei.fMask = bitwise.BitOr(sei.fMask, SEE_MASK_CLASSNAME)
else
sei.lpClass = nil
end if
sei.hkeyClass = 0
sei.dwHotKey = 0
sei.hIcon = 0
sei.hProcess = 0
dim bSuccess as Boolean
dim sError as string
if wideVersion then
bSuccess = ShellExecuteExW(sei)
else
system.debugLog " ShellExecuteExW not available, using non-unicode version"
bSuccess = ShellExecuteExA(sei)
end if
dim iError as uint32 = sei.hInstApp
sError = ""
If bSuccess Then
else
'failure
sError = "Error: " + str(iError,"#")
End If
system.debugLog "ShellExecuteEx result = '" + sError + "'"
return sError
#endif
End Function
#endif
And FYI there’s an open report on this as it may be a bug? <https://xojo.com/issue/48449>
Bit of a moot point as you’ve already solved it and if your method works then go for it
I’d create two with the same name, then set each version to only include in the respective edition inside the gear icon in the inspector then you only need to use the one name in your code. The types in here adjust their size correctly and you don’t have to worry about overflowing them or edge cases with signing etc.
Personally, I wouldn’t trust StructureAlignment at the moment <https://xojo.com/issue/51124>
32 bit version
Protected Structure SHELLEXECUTEINFOA
cbSize as UInt32
fMask as UInt32
HWND as Integer
lpVerb as Ptr
lpFile as Ptr
lpParameters as Ptr
lpDirectory as Ptr
nShow as Int32
hInstApp as Integer
lpIDList as Ptr
lpClass as Ptr
hkeyClass as Integer
dwHotKey as UInt32
hIcon as Integer
hProcess as Integer
End Structure
64 bit version
Protected Structure SHELLEXECUTEINFOA
cbSize as UInt32
fMask as UInt32
HWND as Integer
lpVerb as Ptr
lpFile as Ptr
lpParameters as Ptr
lpDirectory as Ptr
nShow as Int32
_padding1 As UInt32
hInstApp as Integer
lpIDList as Ptr
lpClass as Ptr
hkeyClass as Integer
dwHotKey as UInt32
_padding2 As UInt32
hIcon as Integer
hProcess as Integer
End Structure
Hi Michael,
It is good to see that you created a solution for the ANSI version of the ShellExecuteEx function. If you would like to implement the code for Unicode to provide the path name for different characters other than English, then the Unicode 32-bit and 64-bit functions and structures can be implemented. I had a request for this exact function and took the day to create a work-around for the Xojo structure bug that Julian quoted.
Here is the example (sorry, its quite long because of the work-around bug) to open the built-in calculator on Windows. A running example is shown at the end of this conversation and it will be included in the next version of the declare book (version 3.1).
Sub Action() Handles Action
//Show the minimal information to start the
//Windows calculator program
Dim RetVal as Boolean
RetVal = ShellExecuteEx(SEE_MASK_NOCLOSEPROCESS, _
0, _ //Window handle
Nil, _ //Operation to perform
StringToMB("C:\\Windows\\System32\\calc.exe"), _ //Application path and name
Nil, _ //Additional parameters
Nil, _ //Working Directory
SW_SHOWNORMAL, _
0, _
Nil, _
Nil, _
0, _
0, _
0, _
0)
End Sub
Here is the ShellExecuteEx wrapper:
Public Function ShellExecuteEx(fMask as UInt32, hWnd as Integer, lpVerb as Ptr, lpFile as Ptr, lpParameters as Ptr, lpDirectory as Ptr, nShow as Int32, hInstApp as Integer, lpIDList as Ptr, lpClass as Ptr, hKeyClass as Integer, dwHotKey as UInt32, hIcon as Integer, hProcess as Integer) as Boolean
#If TargetWindows then
If IsUnicode Then
#If Target32Bit
Dim TmpShExInfoW as SHELLEXECUTEINFOW32
TmpShExInfoW.cbSize = TmpShExInfoW.Size
TmpShExInfoW.fMask = fMask
TmpShExInfoW.hWnd = hWnd //Window handle
TmpShExInfoW.lpVerb = lpVerb //Operation to perform
TmpShExInfoW.lpFile = lpFile //Application name
TmpShExInfoW.lpParameters = lpParameters //Additional Parameters
TmpShExInfoW.lpDirectory = lpDirectory //Working Directory
TmpShExInfoW.nShow = nShow
TmpShExInfoW.hInstApp = hInstApp
TmpShExInfoW.lpIDList = lpIDList
TmpShExInfoW.lpClass = lpClass
TmpShExInfoW.hKeyClass = hKeyClass
TmpShExInfoW.dwHotKey = dwHotKey
TmpShExInfoW.hIcon = hIcon
TmpShExInfoW.hProcess = hProcess
Return ShellExecuteExW32(TmpShExInfoW)
#Else
Dim TmpShExInfoW as SHELLEXECUTEINFOW64
TmpShExInfoW.cbSize = TmpShExInfoW.Size
TmpShExInfoW.fMask = fMask
TmpShExInfoW.hWnd = hWnd //Window handle
TmpShExInfoW.lpVerb = lpVerb //Operation to perform
TmpShExInfoW.lpFile = lpFile //Application name
TmpShExInfoW.lpParameters = lpParameters //Additional Parameters
TmpShExInfoW.lpDirectory = lpDirectory //Working Directory
TmpShExInfoW.nShow = nShow
TmpShExInfoW.hInstApp = hInstApp
TmpShExInfoW.lpIDList = lpIDList
TmpShExInfoW.lpClass = lpClass
TmpShExInfoW.hKeyClass = hKeyClass
TmpShExInfoW.dwHotKey = dwHotKey
TmpShExInfoW.hIcon = hIcon
TmpShExInfoW.hProcess = hProcess
Return ShellExecuteExW64(TmpShExInfoW)
#Endif
Else
#If Target32Bit //ANSI 32-bit
Dim TmpShExInfoA as SHELLEXECUTEINFOA32
TmpShExInfoA.cbSize = TmpShExInfoA.Size
TmpShExInfoA.fMask = fMask
TmpShExInfoA.hWnd = hWnd //Window handle
TmpShExInfoA.lpVerb = lpVerb //Operation to perform
TmpShExInfoA.lpFile = lpFile //Application name
TmpShExInfoA.lpParameters = lpParameters //Additional Parameters
TmpShExInfoA.lpDirectory = lpDirectory //Working Directory
TmpShExInfoA.nShow = nShow
TmpShExInfoA.hInstApp = hInstApp
TmpShExInfoA.lpIDList = lpIDList
TmpShExInfoA.lpClass = lpClass
TmpShExInfoA.hKeyClass = hKeyClass
TmpShExInfoA.dwHotKey = dwHotKey
TmpShExInfoA.hIcon = hIcon
TmpShExInfoA.hProcess = hProcess
Return ShellExecuteExA32(TmpShExInfoA)
#Else
Dim TmpShExInfoA as SHELLEXECUTEINFOA64
TmpShExInfoA.cbSize = TmpShExInfoA.Size
TmpShExInfoA.fMask = fMask
TmpShExInfoA.hWnd = hWnd //Window handle
TmpShExInfoA.lpVerb = lpVerb //Operation to perform
TmpShExInfoA.lpFile = lpFile //Application name
TmpShExInfoA.lpParameters = lpParameters //Additional Parameters
TmpShExInfoA.lpDirectory = lpDirectory //Working Directory
TmpShExInfoA.nShow = nShow
TmpShExInfoA.hInstApp = hInstApp
TmpShExInfoA.lpIDList = lpIDList
TmpShExInfoA.lpClass = lpClass
TmpShExInfoA.hKeyClass = hKeyClass
TmpShExInfoA.dwHotKey = dwHotKey
TmpShExInfoA.hIcon = hIcon
TmpShExInfoA.hProcess = hProcess
Return ShellExecuteExA64(TmpShExInfoA)
#Endif
End If
#Else
//System.DebugLog "This is not a Windows OS"
Return False
#Endif
End Function
Here are the ShellExecute functions:
Public Function ShellExecuteExA32(ByRef pExecInfo as SHELLEXECUTEINFOA32) as Boolean
#If TargetWindows
Declare Function ShellExecuteExA Lib "Shell32.dll" (ByRef pExecInfo as SHELLEXECUTEINFOA32) as Boolean
If System.IsFunctionAvailable("ShellExecuteExA","Shell32.dll") Then
Return ShellExecuteExA(pExecInfo)
Else
System.DebugLog "Error calling ShellExecuteExA32"
Return False
End If
#Else
//System.DebugLog "This is not a Windows OS"
Return False
#Endif
End Function
Public Function ShellExecuteExA64(ByRef pExecInfo as SHELLEXECUTEINFOA64) as Boolean
#If TargetWindows
Declare Function ShellExecuteExA Lib "Shell32.dll" (ByRef pExecInfo as SHELLEXECUTEINFOA64) as Boolean
If System.IsFunctionAvailable("ShellExecuteExA","Shell32.dll") Then
Return ShellExecuteExA(pExecInfo)
Else
System.DebugLog "Error calling ShellExecuteExA64"
Return False
End If
#Else
//System.DebugLog "This is not a Windows OS"
Return False
#Endif
End Function
Public Function ShellExecuteExW32(ByRef pExecInfo as SHELLEXECUTEINFOW32) as Boolean
#If TargetWindows
Declare Function ShellExecuteExW Lib "Shell32.dll" (ByRef pExecInfo as SHELLEXECUTEINFOW32) as Boolean
If System.IsFunctionAvailable("ShellExecuteExW","Shell32.dll") Then
Return ShellExecuteExW(pExecInfo)
Else
System.DebugLog "Error calling ShellExecuteExW32"
Return False
End If
#Else
//System.DebugLog "This is not a Windows OS"
Return False
#Endif
End Function
Public Function ShellExecuteExW64(ByRef pExecInfo as SHELLEXECUTEINFOW64) as Boolean
#If TargetWindows
Declare Function ShellExecuteExW Lib "Shell32.dll" (ByRef pExecInfo as SHELLEXECUTEINFOW64) as Boolean
If System.IsFunctionAvailable("ShellExecuteExW","Shell32.dll") Then
Return ShellExecuteExW(pExecInfo)
Else
System.DebugLog "Error calling ShellExecuteExW64"
Return False
End If
#Else
//System.DebugLog "This is not a Windows OS"
Return False
#Endif
End Function
.and here are the structures:
Structure SHELLEXECUTEINFOA32
cbSize as UInt32
fMask as UInt32
hWnd as Integer
lpVerb as Ptr
lpFile as Ptr
lpParameters as Ptr
lpDirectory as Ptr
nShow as Int32
hInstApp as Integer
lpIDList as Ptr
lpClass as Ptr
hKeyClass as Integer
dwHotKey as UInt32
hIcon as Integer
hProcess as Integer
End Structure
Structure SHELLEXECUTEINFOA64
cbSize as UInt32
fMask as UInt32
hWnd as Integer
lpVerb as Ptr
lpFile as Ptr
lpParameters as Ptr
lpDirectory as Ptr
nShow as Int32
_Padding1 as UInt32
hInstApp as Integer
lpIDList as Ptr
lpClass as Ptr
hKeyClass as Integer
dwHotKey as UInt32
_Padding2 as UInt32
hIcon as Integer
hProcess as Integer
End Structure
Structure SHELLEXECUTEINFOW32
cbSize as UInt32
fMask as UInt32
hWnd as Integer
lpVerb as Ptr
lpFile as Ptr
lpParameters as Ptr
lpDirectory as Ptr
nShow as Int32
hInstApp as Integer
lpIDList as Ptr
lpClass as Ptr
hKeyClass as Integer
dwHotKey as UInt32
hIcon as Integer
hProcess as Integer
End Structure
Structure SHELLEXECUTEINFOW64
cbSize as UInt32
fMask as UInt32
hWnd as Integer
lpVerb as Ptr
lpFile as Ptr
lpParameters as Ptr
lpDirectory as Ptr
nShow as Int32
_Padding1 as UInt32
hInstApp as Integer
lpIDList as Ptr
lpClass as Ptr
hKeyClass as Integer
dwHotKey as UInt32
_Padding2 as UInt32
hIcon as Integer
hProcess as Integer
End Structure
Here is the program that works with 32-bit, 64-bit, ANSI, and Unicode computers:
I am sure that I could make the code smaller and optimize it more, given more time My humble suggestion is to attempt to try and create the structure as Julian mentioned. I think it may save some time and increase compatibility of Xojo code with future programs.
Happy programming.