Delphi - On Screen Keyboard (osk.exe) works on Win32 but fails on Win64
delphi, delphi-xe, wow64
Solution
There is something special about osk under UAC. This code fails with error code 740, `ERROR_ELEVATION_REQUIRED`, The requested operation requires elevation.
var
si: TStartupInfo;
pi: TProcessInformation;
....
si.cb := SizeOf(si);
GetStartupInfo(si);
Win32Check(CreateProcess('C:\Windows\system32\osk.exe', nil, nil, nil,
False, 0, nil, nil, si, pi));
This fails under both 32 and 64 bit processes on machines with UAC. You can find some discussion of the issue here: https://web.archive.org/web/20170311141004/http://blog.delphi-jedi.net/2008/05/17/the-case-of-shellexecute-shellexecuteex-createprocess-and-oskexe/
So your problem is not related to 32 or 64 bit, rather it is down to your XP system not having UAC.
More broadly I think this should be enough to convince you never to call `ShellExecute` again. It only exists for 16 bit compatibility and is singularly useless at reporting errors. If you want errors call `ShellExecuteEx`. However, since we are starting a new process, `CreateProcess` would normally be the right API to call.
That said, in this specific case the design of osk is such that it cannot be started programmatically by `CreateProcess`. It does need to be invoked by `ShellExecute`, or `ShellExecuteEx`. This allows the shell to perform its UAC magic. Now, it turns out that magic cannot happen from a 32 bit WOW64 process. The solution then is to start osk from a 64 bit process with a call to `ShellExecuteEx`.
Here is your workaround:
- On a 32 bit system, you can simply call `ShellExecuteEx` to open `osk`.
- On a 64 bit system, if your process is 64 bit, you can again call `ShellExecuteEx` to open `osk`.
- On a 64 bit system, if your process is 32 bit WOW64 process, you need to start a separate 64 bit process which in turn calls `ShellExecuteEx` to open `osk`.
Since you don't appear to be using a 64 bit version of Delphi, you'll need to find a 64 bit compiler. You could use the 64 bit fpc, or a 64 bit C++ compiler. The following C++ program is enough:
#include <Windows.h>
#include <Shellapi.h>
int CALLBACK WinMain(
HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow
)
{
SHELLEXECUTEINFOW sei = { sizeof(sei) };
sei.lpVerb = L"open";
sei.lpFile = L"osk.exe";
sei.nShow = SW_SHOW;
ShellExecuteExW(&sei);
}
You can compile that with a 64 bit C++ compiler and then call it from your 32 bit WOW64 process. Long winded I know, but it does have the merit of actually working!
Problem
I'm trying to run the on screen keyboard from my application. It works correctly under Windows XP 32 bits, but incorrect under Win 7 64 bits. ``` unit Unit5; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, ShellAPI; type TForm5 = class(TForm) procedure FormCreate(Sender: TObject); private { Private declarations } public class function IsWOW64: Boolean; { Public declarations } end; var Form5: TForm5; implementation {$R *.dfm} procedure TForm5.FormCreate(Sender: TObject); var path:String; res : Integer; function GetSysDir: string; var Buf: array[0..MAX_PATH] of Char; Len: UINT; S: String; begin {$IFNDEF WIN64} if TForm5.IsWOW64 then begin Len := GetWindowsDirectory(Buf, MAX_PATH); if Len = 0 then RaiseLastOSError; SetString(S, Buf, Len); Result := IncludeTrailingPathDelimiter(S) + 'Sysnative\'; Exit; end; {$ENDIF} Len := GetSystemDirectory(Buf, MAX_PATH); if Len = 0 then RaiseLastOSError; SetString(S, Buf, Len); Result := IncludeTrailingPathDelimiter(S); end; begin path := GetSysDir; path := path + 'osk.exe'; res := ShellExecute(self.Handle,'open',Pchar(path),nil,nil,SW_NORMAL); if res <> 42 then begin ShowMessage(path); RaiseLastOSError; end; end; class function TForm5.IsWOW64: Boolean; type TIsWow64Process = function( // Type of IsWow64Process API fn Handle: THandle; var Res: BOOL ): BOOL; stdcall; var IsWow64Result: BOOL; // result from IsWow64Process IsWow64Process: TIsWow64Process; // IsWow64Process fn reference begin // Try to load required function from kernel32 IsWow64Process := GetProcAddress( GetModuleHandle('kernel32'), 'IsWow64Process' ); if Assigned(IsWow64Process) then begin // Function is implemented: call it if not IsWow64Process(GetCurrentProcess, IsWow64Result) then RaiseLastOSError; // Return result of function Result := IsWow64Result; end else // Function not implemented: can't be running on Wow64 Result := False; end; end. ``` Running the application under x64 reveals the path C:\Windows\Sysnative\osk.exe , and raise a 'call to an OS function failed' error. Searching on windows directories reveals that osk.exe exists