ShellExecuteEX 64 bit x64 Declare

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 :slight_smile:

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:

Example13-4

I am sure that I could make the code smaller and optimize it more, given more time :slight_smile: 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. :slight_smile: