There are lots of questions on the Internt how to run the OnScreen Keyboard on a Windows 64 System from a 32bit Process. Some time ago, I investigated it but due to a lack of time I didn’t finish it. Today, I want to you show the solution of this problem.
From a 32Bit application, Windows makes sure that you cannot access the Windows\System32 folder because this is the place where Windows has all its 64bit DLLs and other files. Since a 32Bit app cannot load 64Bit DLLs, Windows redirects the access to Windows\SysWOW64 folder which is (nearly) a 32Bit copy of the System32 folder. Therefore if you call ShellExecute on the e.g. Windows\System32\osk.exe you actually run Windows\SysWOW64\osk.exe. However the 32Bit version of OnScreenKeyboard refuses to run on a 64Bit system.
Fortunately, we can disable the redirrection with Wow64DisableWow64FsRedirection.
That leads us to a problem with ShellExecute. It uses internal functions that are located in DLLs. And there is the problem. If a winapi function wasn’t called before, it will be setup by calling LoadLibrary and GetProcAddress. However, since redirection is offline, LoadLibrary will load a 64Bit DLL which, of course, fails. In such a case ShellExecuteEx returns GetLastError $7E (127) which defines the error “The module specified could not be found “. I wondered what module was not found and used ProcessExplorer to compare pre- and post situation of ShellExecute. There were many, but only one made ShellExecuteEx work with disabled redirection: MPR.dll (multi protocol router). Step by step disasm returned the that WNetGetConnectionW was loaded from the DLL. So the fix is to load this library before ShellExecuteEx (also ShellExecute) is called.
uses JwaWinType, JwaWinBase, JwaShellAPI, JwaSHFolder, JwaShlObj; function Wow64DisableWow64FsRedirection(out OldValue: PVOID): BOOL; stdcall; external 'Kernel32.dll'; //only for JWA < 2.4 function Wow64RevertWow64FsRedirection(const OldValue: PVOID): BOOL; stdcall; external 'Kernel32.dll'; //only for JWA < 2.4 procedure RunOnScreenKeyBoard; function GetNativeWindowsDirectory : String; var P : array[0..MAX_PATH] of Char; begin SHGetFolderPath(0, CSIDL_SYSTEM, 0, SHGFP_TYPE_DEFAULT, @P); result := P; end; var oldValue : Pointer; Path : String; hMpr_DLL : HMODULE; ShInfo : SHELLEXECUTEINFO; begin //CoInitializeEx(nil, COINIT_MULTITHREADED or COINIT_DISABLE_OLE1DDE); //*1 hMpr_DLL := LoadLibrary('mpr.dll'); //*2 try if not Wow64DisableWow64FsRedirection(oldValue) then RaiseLastOSError; try Path := GetNativeWindowsDirectory + '\osk.exe'; ZeroMemory(@ShInfo, sizeof(ShInfo)); ShInfo.cbSize := sizeof(ShInfo); ShInfo.lpVerb := 'open'; ShInfo.fMask := SEE_MASK_FLAG_NO_UI or SEE_MASK_NOASYNC; ShInfo.lpFile := PChar(Path); if not ShellExecuteEx(ShInfo) then RaiseLastOSError; finally Wow64RevertWow64FsRedirection(oldValue); end; finally FreeLibrary(hMpr_DLL); end; end;
*1 Has no use here if it was already called. But it is a reminder that it should be called for ShellExecute due to MSDN docs.
*2 Make sure you don’t call LoadLibrary when redirection is offline.
Delphi (addition)
I forgot to mention that in my experiences, a simple call to ShellExecute works if you run your app in Delphi. You’ll see that the app already has mpr.dll loaded if you check with Process Explorer. However, running the same app in Windows Explorer it will fail.
You see that Delphi can have an effect on your application. Therefore, always test your app separately.
Download
You can download this file from here.
Remarks
I tested this code on my Win7 64bit System only.
The string “%windir%\sysnative\osk.exe” was a first guess. However, ShellExecute fails with Error 2 (File Not Found).
Because this is a 64Bit Problem only, I did not make extra effort to check for a 64Bit Windows. The function Wow64DisableWow64FsRedirection is not available on a 32Bit platform, so using static linking is a bad idea if you want to use your app on 32Bit.
If you find this code useful, you may consider a donation? As usual, a donation can be also a WinAPI conversion, examples, bug fixes etc.