无法从 Win7 x64 上的 32 位进程启动屏幕键盘 (osk.exe)

发布于 2024-09-04 01:53:16 字数 3826 浏览 9 评论 0原文

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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

久而酒知 2024-09-11 01:53:16

在 64 位操作系统上运行的 32 位应用程序应启动 64 位版本的 osk.exe。
下面您可以看到用 C# 编写的代码片段,用于启动正确的屏幕键盘。

    private static void ShowKeyboard()
    {
        var path64 = @"C:\Windows\winsxs\amd64_microsoft-windows-osk_31bf3856ad364e35_6.1.7600.16385_none_06b1c513739fb828\osk.exe";
        var path32 = @"C:\windows\system32\osk.exe";
        var path = (Environment.Is64BitOperatingSystem) ? path64 : path32;
        Process.Start(path);
    }

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.

    private static void ShowKeyboard()
    {
        var path64 = @"C:\Windows\winsxs\amd64_microsoft-windows-osk_31bf3856ad364e35_6.1.7600.16385_none_06b1c513739fb828\osk.exe";
        var path32 = @"C:\windows\system32\osk.exe";
        var path = (Environment.Is64BitOperatingSystem) ? path64 : path32;
        Process.Start(path);
    }
东风软 2024-09-11 01:53:16

对于您收到的确切错误消息,我没有非常可靠的解释。但禁用重定向将会扰乱 .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.

子栖 2024-09-11 01:53:16

这是我的代码

            var path64 = Path.Combine(Directory.GetDirectories(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Windows), "winsxs"), "amd64_microsoft-windows-osk_*")[0], "osk.exe");
            var path32 = @"C:\windows\system32\osk.exe";
            var path = (Environment.Is64BitOperatingSystem) ? path64 : path32;
            if(File.Exists(path))
            {
                Process.Start(path);
            }

This is my code

            var path64 = Path.Combine(Directory.GetDirectories(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Windows), "winsxs"), "amd64_microsoft-windows-osk_*")[0], "osk.exe");
            var path32 = @"C:\windows\system32\osk.exe";
            var path = (Environment.Is64BitOperatingSystem) ? path64 : path32;
            if(File.Exists(path))
            {
                Process.Start(path);
            }
任性一次 2024-09-11 01:53:16

幕后发生的某些事情需要您从 MTA 线程启动 osk.exe。原因似乎是对 的调用Wow64DisableWow64FsRedirection 仅影响当前线程。但是,在某些条件下,Process.Start 将从单独的线程创建新进程,例如,当 UseShellExecute 设置为 false 时以及从 STA 线程调用时它似乎。

下面的代码检查单元状态,然后确保从 MTA 线程启动屏幕键盘:

using System;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading;

class Program
{
    [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);


    [DllImport("user32.dll", CharSet = CharSet.Auto)]
    static extern IntPtr SendMessage(IntPtr hWnd, 
        UInt32 Msg, 
        IntPtr wParam, 
        IntPtr lParam);
    private const UInt32 WM_SYSCOMMAND = 0x112;
    private const UInt32 SC_RESTORE = 0xf120;

    private const string OnScreenKeyboardExe = "osk.exe";

    [STAThread]
    static void Main(string[] args)
    {
        Process[] p = Process.GetProcessesByName(
            Path.GetFileNameWithoutExtension(OnScreenKeyboardExe));

        if (p.Length == 0)
        {
            // we must start osk from an MTA thread
            if (Thread.CurrentThread.GetApartmentState() == ApartmentState.STA)
            {
                ThreadStart start = new ThreadStart(StartOsk);
                Thread thread = new Thread(start);
                thread.SetApartmentState(ApartmentState.MTA);
                thread.Start();
                thread.Join();
            }
            else
            {
                StartOsk();
            }
        }
        else
        {
            // there might be a race condition if the process terminated 
            // meanwhile -> proper exception handling should be added
            //
            SendMessage(p[0].MainWindowHandle, 
                WM_SYSCOMMAND, new IntPtr(SC_RESTORE), new IntPtr(0));
        }
    }

    static void StartOsk()
    {
        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);
        }


        ProcessStartInfo psi = new ProcessStartInfo();
        psi.FileName = OnScreenKeyboardExe;
        // We must use ShellExecute to start osk from the current thread
        // with psi.UseShellExecute = false the CreateProcessWithLogon API 
        // would be used which handles process creation on a separate thread 
        // where the above call to Wow64DisableWow64FsRedirection would not 
        // have any effect.
        //
        psi.UseShellExecute = true;
        Process.Start(psi);

        // Re-enable directory virtualisation if it was disabled.
        if (System.Environment.Is64BitOperatingSystem)
            if (sucessfullyDisabledWow64Redirect)
                Wow64RevertWow64FsRedirection(ptr);
    }
}

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. when UseShellExecute 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:

using System;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading;

class Program
{
    [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);


    [DllImport("user32.dll", CharSet = CharSet.Auto)]
    static extern IntPtr SendMessage(IntPtr hWnd, 
        UInt32 Msg, 
        IntPtr wParam, 
        IntPtr lParam);
    private const UInt32 WM_SYSCOMMAND = 0x112;
    private const UInt32 SC_RESTORE = 0xf120;

    private const string OnScreenKeyboardExe = "osk.exe";

    [STAThread]
    static void Main(string[] args)
    {
        Process[] p = Process.GetProcessesByName(
            Path.GetFileNameWithoutExtension(OnScreenKeyboardExe));

        if (p.Length == 0)
        {
            // we must start osk from an MTA thread
            if (Thread.CurrentThread.GetApartmentState() == ApartmentState.STA)
            {
                ThreadStart start = new ThreadStart(StartOsk);
                Thread thread = new Thread(start);
                thread.SetApartmentState(ApartmentState.MTA);
                thread.Start();
                thread.Join();
            }
            else
            {
                StartOsk();
            }
        }
        else
        {
            // there might be a race condition if the process terminated 
            // meanwhile -> proper exception handling should be added
            //
            SendMessage(p[0].MainWindowHandle, 
                WM_SYSCOMMAND, new IntPtr(SC_RESTORE), new IntPtr(0));
        }
    }

    static void StartOsk()
    {
        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);
        }


        ProcessStartInfo psi = new ProcessStartInfo();
        psi.FileName = OnScreenKeyboardExe;
        // We must use ShellExecute to start osk from the current thread
        // with psi.UseShellExecute = false the CreateProcessWithLogon API 
        // would be used which handles process creation on a separate thread 
        // where the above call to Wow64DisableWow64FsRedirection would not 
        // have any effect.
        //
        psi.UseShellExecute = true;
        Process.Start(psi);

        // Re-enable directory virtualisation if it was disabled.
        if (System.Environment.Is64BitOperatingSystem)
            if (sucessfullyDisabledWow64Redirect)
                Wow64RevertWow64FsRedirection(ptr);
    }
}
瀞厅☆埖开 2024-09-11 01:53:16

笨拙的方法:

在侧面运行这个批处理文件(从64位资源管理器启动):

:lab0
TIMEOUT /T 1 >nul
if exist oskstart.tmp goto lab2
goto lab0
:lab2
del oskstart.tmp
osk
goto lab0

当您需要键盘时创建文件oskstart.tmp

Clumsy method:

Run this batch file on the side (started from 64 bit explorer) :

:lab0
TIMEOUT /T 1 >nul
if exist oskstart.tmp goto lab2
goto lab0
:lab2
del oskstart.tmp
osk
goto lab0

Create file oskstart.tmp when you need the keyboard

香草可樂 2024-09-11 01:53:16

这是基于以下内容的最新答案:
如何启动 64 位32位进程的进程

代码如下:

string system32 = Environment.Is64BitOperatingSystem && !Environment.Is64BitProcess ? "Sysnative" : "System32";
string windir = Environment.ExpandEnvironmentVariables("%windir%");
Process proc = Process.Start(new ProcessStartInfo
{
    WindowStyle = ProcessWindowStyle.Hidden,
    FileName = Path.Combine(windir, system32, "cmd.exe"),
    Arguments = "/c start osk.exe"
});
proc.WaitForExit();

需要添加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:

string system32 = Environment.Is64BitOperatingSystem && !Environment.Is64BitProcess ? "Sysnative" : "System32";
string windir = Environment.ExpandEnvironmentVariables("%windir%");
Process proc = Process.Start(new ProcessStartInfo
{
    WindowStyle = ProcessWindowStyle.Hidden,
    FileName = Path.Combine(windir, system32, "cmd.exe"),
    Arguments = "/c start osk.exe"
});
proc.WaitForExit();

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).

夜灵血窟げ 2024-09-11 01:53:16

对于遇到“无法启动屏幕键盘”的用户,请将项目的平台目标更改为任何 CPU。

For those who are facing "Could not start On-Screen Keyboard.", change your project's Platform Target to Any CPU.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文