无法从 Win7 x64 上的 32 位进程启动屏幕键盘 (osk.exe)
90% 的时间我无法从 Win7 x64
上的 32 位进程启动 osk.exe
。最初代码只是使用:
Process.Launch("osk.exe");
由于目录虚拟化,这在 x64 上不起作用。我认为这不是问题,我只需禁用虚拟化,启动应用程序,然后再次启用它,我认为这是正确的做法。我还添加了一些代码,以便在最小化键盘时恢复键盘(效果很好) - 代码(在示例 WPF 应用程序中)现在如下所示:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;using System.Diagnostics;
using System.Runtime.InteropServices;
namespace KeyboardTest
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool Wow64DisableWow64FsRedirection(ref IntPtr ptr);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern bool Wow64RevertWow64FsRedirection(IntPtr ptr);
private const UInt32 WM_SYSCOMMAND = 0x112;
private const UInt32 SC_RESTORE = 0xf120;
[DllImport("user32.dll", CharSet = CharSet.Auto)]
static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, IntPtr lParam);
private string OnScreenKeyboadApplication = "osk.exe";
public MainWindow()
{
InitializeComponent();
}
private void KeyboardButton_Click(object sender, RoutedEventArgs e)
{
// Get the name of the On screen keyboard
string processName = System.IO.Path.GetFileNameWithoutExtension(OnScreenKeyboadApplication);
// Check whether the application is not running
var query = from process in Process.GetProcesses()
where process.ProcessName == processName
select process;
var keyboardProcess = query.FirstOrDefault();
// launch it if it doesn't exist
if (keyboardProcess == null)
{
IntPtr ptr = new IntPtr(); ;
bool sucessfullyDisabledWow64Redirect = false;
// Disable x64 directory virtualization if we're on x64,
// otherwise keyboard launch will fail.
if (System.Environment.Is64BitOperatingSystem)
{
sucessfullyDisabledWow64Redirect = Wow64DisableWow64FsRedirection(ref ptr);
}
// osk.exe is in windows/system folder. So we can directky call it without path
using (Process osk = new Process())
{
osk.StartInfo.FileName = OnScreenKeyboadApplication;
osk.Start();
osk.WaitForInputIdle(2000);
}
// Re-enable directory virtualisation if it was disabled.
if (System.Environment.Is64BitOperatingSystem)
if (sucessfullyDisabledWow64Redirect)
Wow64RevertWow64FsRedirection(ptr);
}
else
{
// Bring keyboard to the front if it's already running
var windowHandle = keyboardProcess.MainWindowHandle;
SendMessage(windowHandle, WM_SYSCOMMAND, new IntPtr(SC_RESTORE), new IntPtr(0));
}
}
}
}
但是此代码在大多数情况下会引发以下异常osk.Start()
:
找不到指定的程序 在 System.Diagnostics.Process.StartWithShellExecuteEx(ProcessStartInfo startInfo)
我尝试在 osk.Start 行周围放置长 Thread.Sleep 命令,只是为了确保它不是竞争条件,但同样的问题仍然存在。任何人都可以发现我做错了什么,或者为此提供替代解决方案吗?启动记事本似乎工作正常,只是无法与屏幕键盘配合使用。
90% of the time I am unable to launch osk.exe
from a 32bit process on Win7 x64
. Originally the code was just using:
Process.Launch("osk.exe");
Which won't work on x64 because of the directory virtualization. Not a problem I thought, I'll just disable virtualization, launch the app, and enable it again, which I thought was the correct way to do things. I also added some code to bring the keyboard back up if it has been minimized (which works fine) - the code (in a sample WPF app) now looks as follows:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;using System.Diagnostics;
using System.Runtime.InteropServices;
namespace KeyboardTest
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool Wow64DisableWow64FsRedirection(ref IntPtr ptr);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern bool Wow64RevertWow64FsRedirection(IntPtr ptr);
private const UInt32 WM_SYSCOMMAND = 0x112;
private const UInt32 SC_RESTORE = 0xf120;
[DllImport("user32.dll", CharSet = CharSet.Auto)]
static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, IntPtr lParam);
private string OnScreenKeyboadApplication = "osk.exe";
public MainWindow()
{
InitializeComponent();
}
private void KeyboardButton_Click(object sender, RoutedEventArgs e)
{
// Get the name of the On screen keyboard
string processName = System.IO.Path.GetFileNameWithoutExtension(OnScreenKeyboadApplication);
// Check whether the application is not running
var query = from process in Process.GetProcesses()
where process.ProcessName == processName
select process;
var keyboardProcess = query.FirstOrDefault();
// launch it if it doesn't exist
if (keyboardProcess == null)
{
IntPtr ptr = new IntPtr(); ;
bool sucessfullyDisabledWow64Redirect = false;
// Disable x64 directory virtualization if we're on x64,
// otherwise keyboard launch will fail.
if (System.Environment.Is64BitOperatingSystem)
{
sucessfullyDisabledWow64Redirect = Wow64DisableWow64FsRedirection(ref ptr);
}
// osk.exe is in windows/system folder. So we can directky call it without path
using (Process osk = new Process())
{
osk.StartInfo.FileName = OnScreenKeyboadApplication;
osk.Start();
osk.WaitForInputIdle(2000);
}
// Re-enable directory virtualisation if it was disabled.
if (System.Environment.Is64BitOperatingSystem)
if (sucessfullyDisabledWow64Redirect)
Wow64RevertWow64FsRedirection(ptr);
}
else
{
// Bring keyboard to the front if it's already running
var windowHandle = keyboardProcess.MainWindowHandle;
SendMessage(windowHandle, WM_SYSCOMMAND, new IntPtr(SC_RESTORE), new IntPtr(0));
}
}
}
}
But this code, most of the time, throws the following exception on osk.Start()
:
The specified procedure could not be found
at System.Diagnostics.Process.StartWithShellExecuteEx(ProcessStartInfo startInfo)
I've tried putting long Thread.Sleep commands in around the osk.Start line, just to make sure it wasn't a race condition, but the same problem persists. Can anyone spot where I'm doing something wrong, or provide an alternative solution for this? It seems to work fine launching Notepad, it just won't play ball with the onscreen keyboard.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(7)
在 64 位操作系统上运行的 32 位应用程序应启动 64 位版本的 osk.exe。
下面您可以看到用 C# 编写的代码片段,用于启动正确的屏幕键盘。
A 32 bit application running on a 64 bit operating system should start the 64 bit version of osk.exe.
Below you see a code snipped written in C# to start the correct on screen keyboard.
对于您收到的确切错误消息,我没有非常可靠的解释。但禁用重定向将会扰乱 .NET 框架。默认情况下,Process.Start() P/Invoke ShellExecuteEx() API 函数来启动进程。该函数位于 shell32.dll 中,如果之前没有加载该 DLL,则可能需要加载该 DLL。当您禁用重定向时,您会得到错误的结果。
解决方法是将 ProcessStartInfo.UseShellExecute 设置为 false。你在这里不需要它。
显然,禁用重定向是一种危险的方法,其副作用是您无法真正预测的。有很多 DLL 需要按需加载。使用 Platform Target = Any CPU 编译的一个非常小的帮助程序 EXE 可以解决您的问题。
I don't have a very solid explanation for the exact error message you are getting. But disabling redirection is going to mess up the .NET framework. By default, Process.Start() P/Invokes the ShellExecuteEx() API function to start the process. This function lives in shell32.dll, a DLL that might have to be loaded if that wasn't previously done. You'll get the wrong one when you disable redirection.
A workaround for that is to set ProcessStartInfo.UseShellExecute to false. You don't need it here.
Clearly, disabling redirection is a risky approach with side-effects you cannot really predict. There are lots of DLLs that get demand-loaded. A very small helper EXE that you compile with Platform Target = Any CPU can solve your problem.
这是我的代码
This is my code
幕后发生的某些事情需要您从 MTA 线程启动 osk.exe。原因似乎是对
的调用Wow64DisableWow64FsRedirection
仅影响当前线程。但是,在某些条件下,Process.Start
将从单独的线程创建新进程,例如,当UseShellExecute
设置为 false 时以及从 STA 线程调用时它似乎。下面的代码检查单元状态,然后确保从 MTA 线程启动屏幕键盘:
Certain things are going on under the hood that require you to start osk.exe from an MTA thread. The reason seems to be that a call to
Wow64DisableWow64FsRedirection
only affects the current thread. However, under certain conditions,Process.Start
will create the new process from a separate thread, e.g. whenUseShellExecute
is set to false and also when being called from an STA thread as it seems.The code below checks the apartment state and then makes sure to start the On-Screen Keyboard from an MTA thread:
笨拙的方法:
在侧面运行这个批处理文件(从64位资源管理器启动):
当您需要键盘时创建文件oskstart.tmp
Clumsy method:
Run this batch file on the side (started from 64 bit explorer) :
Create file oskstart.tmp when you need the keyboard
这是基于以下内容的最新答案:
如何启动 64 位32位进程的进程
代码如下:
需要添加
start
来启动屏幕键盘而不等待它关闭。无论操作系统和应用程序位数如何,从实际的 System32 文件夹运行
osk.exe
应该没问题。根据以下帖子,System32 文件夹包含 64 位 Windows 上的 64 位应用程序: https://www.ibm.com/support/pages/why-do-64-bit-dlls-go- system32-and-32-bit-dlls-syswow64-64-bit-windows/我已经在 Windows 10 Pro 上使用 x86/x64/Any CPU 构建配置测试了 .NET Framework 4.7.2 应用程序内的代码21H2(64 位)。
Here is an up to date answer based on:
How to start a 64-bit process from a 32-bit process
The code is the following:
The addition of
start
is required to start On-Screen Keyboard without waiting for it to close.Running
osk.exe
from the actual System32 folder should be ok regardless of OS and app bitness. Based on the following post, System32 folder contains 64 bit applications on 64 bit Windows: https://www.ibm.com/support/pages/why-do-64-bit-dlls-go-system32-and-32-bit-dlls-syswow64-64-bit-windows/I have tested the code inside a .NET Framework 4.7.2 app with x86/x64/Any CPU build configuration, on Windows 10 Pro 21H2 (64 bit).
对于遇到“无法启动屏幕键盘”的用户,请将项目的平台目标更改为任何 CPU。
For those who are facing "Could not start On-Screen Keyboard.", change your project's Platform Target to Any CPU.