简体   繁体   English

Delphi - 屏幕键盘(osk.exe)在Win32上运行但在Win64上运行失败

[英]Delphi - On Screen Keyboard (osk.exe) works on Win32 but fails on Win64

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. 它在Windows XP 32位下正常工作,但在Win 7 64位下不正确。

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. 在x64下运行应用程序会显示路径C:\\ Windows \\ Sysnative \\ osk.exe,并引发“调用OS函数失败”错误。

Searching on windows directories reveals that osk.exe exists 在Windows目录中搜索显示osk.exe存在

在此输入图像描述

There is something special about osk under UAC. 在UAC下,osk有一些特别之处。 This code fails with error code 740, ERROR_ELEVATION_REQUIRED , The requested operation requires elevation . 此代码失败,错误代码为740, ERROR_ELEVATION_REQUIRED请求的操作需要提升

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. 在具有UAC的计算机上,这在32位和64位进程下都会失败。 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/ 您可以在此处找到有关该问题的一些讨论: https//web.archive.org/web/20170311141004/http//blog.delphi-jedi.net/2008/05/17/the-case-of-shellexecute-的ShellExecuteEx-的CreateProcess和- oskexe /

So your problem is not related to 32 or 64 bit, rather it is down to your XP system not having UAC. 因此,您的问题与32位或64位无关,而是由于您的XP系统没有UAC。

More broadly I think this should be enough to convince you never to call ShellExecute again. 更广泛地说,我认为这足以说服你永远不要再打电话给ShellExecute It only exists for 16 bit compatibility and is singularly useless at reporting errors. 它仅存在16位兼容性,在报告错误时非常无用。 If you want errors call ShellExecuteEx . 如果要错误,请调用ShellExecuteEx However, since we are starting a new process, CreateProcess would normally be the right API to call. 但是,由于我们正在开始一个新进程, CreateProcess通常是正确的API调用。

That said, in this specific case the design of osk is such that it cannot be started programmatically by CreateProcess . 也就是说,在这种特殊情况下,osk的设计是不能通过CreateProcess以编程方式启动的。 It does need to be invoked by ShellExecute , or ShellExecuteEx . 它确实需要由ShellExecuteShellExecuteEx调用。 This allows the shell to perform its UAC magic. 这允许shell执行其UAC魔法。 Now, it turns out that magic cannot happen from a 32 bit WOW64 process. 现在,事实证明,32位WOW64进程不会发生魔术。 The solution then is to start osk from a 64 bit process with a call to ShellExecuteEx . 然后解决方案是通过调用ShellExecuteEx从64位进程启动osk。

Here is your workaround: 这是你的解决方法:

  1. On a 32 bit system, you can simply call ShellExecuteEx to open osk . 在32位系统上,您只需调用ShellExecuteEx即可打开osk
  2. On a 64 bit system, if your process is 64 bit, you can again call ShellExecuteEx to open osk . 在64位系统上,如果您的进程是64位,您可以再次调用ShellExecuteEx来打开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 . 在64位系统上,如果您的进程是32位WOW64进程,则需要启动一个单独的64位进程,该进程又调用ShellExecuteEx来打开osk

Since you don't appear to be using a 64 bit version of Delphi, you'll need to find a 64 bit compiler. 由于您似乎没有使用64位版本的Delphi,因此您需要找到64位编译器。 You could use the 64 bit fpc, or a 64 bit C++ compiler. 您可以使用64位fpc或64位C ++编译器。 The following C++ program is enough: 以下C ++程序就足够了:

#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. 您可以使用64位C ++编译器对其进行编译,然后从32位WOW64进程中调用它。 Long winded I know, but it does have the merit of actually working! 我知道啰嗦,但它确实有实际工作的优点!

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: 另一个更简单的选择是在ShellExecute中使用SysNative,如下所示:

{  
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. 它在64位Windows 10上运行良好。我还没有尝试过其他版本,理论上说,这应该适用于所有版本的操作系统,除了64位pre-Vista版本,在这种情况下OSK不会显示,但32位编译的应用程序将在所有版本的Windows 32位和64位上运行。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM