Question

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

enter image description here

Was it helpful?

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:

  1. On a 32 bit system, you can simply call ShellExecuteEx to open osk.
  2. On a 64 bit system, if your process is 64 bit, you can again call ShellExecuteEx to open osk.
  3. 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!

OTHER TIPS

Function Wow64DisableWow64FsRedirection(Var Wow64FsEnableRedirection: LongBool): LongBool; StdCall;
  External 'Kernel32.dll' Name 'Wow64DisableWow64FsRedirection';

Var
  Wow64FsEnableRedirection: LongBool;
begin
  if Wow64DisableWow64FsRedirection(Wow64FsEnableRedirection) then ShellExecute(0,nil, 'osk.exe', nil, nil, SW_show);
end;

Another, much simpler, option is to use SysNative in ShellExecute as follows:

{  
If UTExistFile(GetWindowsSystemDir() + '\OSK.exe') then  
// we can "see" OSK.exe in the System32 folder, so we are running on 
// 32-bit Windows, so no problem accessing OSK.EXE in System32.
ShellExecute(Application.Handle,       // HWND hwnd
       'open',                   // LPCTSTR lpOperation
        LPCTSTR(GetWindowsSystemDir() + '\OSK.exe'), // LPCTSTR lpFile
        '',                        // LPCTSTR lpParameters
        '',                        // LPCTSTR lpDirectory,
        SW_Show)                   // INT nShowCmd
else     
// Use SysNative to get at OSK.EXE. This will not work for 64-bit OS 
// before Vista (e.g. XP), but it won't lock or crash your system and at
// least you can compile and run the application on all versions of Windows;
// both 32 and 64 bit.
ShellExecute(Application.Handle,    // HWND hwnd
    'open',                         // LPCTSTR lpOperation
    LPCTSTR(GetWindowsDir() + '\SysNative\OSK.EXE'),  // LPCTSTR lpFile
    '',                            // LPCTSTR lpParameters
    '',                            // LPCTSTR lpDirectory,
    SW_Show) ;                     // INT nShowCmd
}

It works well on 64 bit Windows 10. I haven't tried it with other versions yet, bit in theory, this should work with all versions of the OS, except 64-bit pre-Vista versions in which case the OSK won't show, but the 32-bit compiled application will run on all versions of Windows 32-bit and 64-bit.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top