【问题标题】:Unable to launch onscreen keyboard (osk.exe) from a 32-bit process on Win7 x64无法从 Win7 x64 上的 32 位进程启动屏幕键盘 (osk.exe)
【发布时间】:2011-02-25 03:13:28
【问题描述】:

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 命令,以确保它不是竞争条件,但同样的问题仍然存在。谁能发现我做错了什么,或为此提供替代解决方案? 似乎可以正常启动记事本,只是无法使用屏幕键盘。

【问题讨论】:

    标签: c# wpf exception 64-bit on-screen-keyboard


    【解决方案1】:

    在 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);
        }
    

    【讨论】:

    • 这种方法对我有用,但 amd64_microsoft.... 路径的命名对我来说有点不同。我必须使用模式匹配搜索目录:Path.Combine(Directory.GetDirectories(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Windows), "winsxs"), "amd64_microsoft-windows-osk_*")[0], "osk.exe")
    • 嗨@HotN,你的代码对我有用,谢谢。您还可以建议我是否可以使用代码更改屏幕键盘的位置?
    • @ashishbharwal001 我没有任何代码可以方便地执行此操作,但您应该能够使用 pinvoke 函数设置键盘大小和位置。 SetWindowPos (pinvoke.net/default.aspx/user32.setwindowpos) 应该可以满足您的需求。您可以使用Process.GetProcessesByName("osk.exe")[0].MainWindowHandle 获取要传入的窗口句柄。
    【解决方案2】:

    对于您收到的确切错误消息,我没有非常可靠的解释。但是禁用重定向会弄乱 .NET 框架。默认情况下,Process.Start() P/Invoke ShellExecuteEx() API 函数来启动进程。该函数位于 shell32.dll 中,如果以前没有加载,则可能必须加载该 DLL。禁用重定向时会出错。

    解决方法是将 ProcessStartInfo.UseShellExecute 设置为 false。你在这里不需要它。

    显然,禁用重定向是一种冒险的方法,其副作用是您无法真正预测的。有很多 DLL 可以按需加载。您使用 Platform Target 编译的一个非常小的帮助程序 EXE = 任何 CPU 都可以解决您的问题。

    【讨论】:

    • 将 UseShellExec 设置为 false 意味着它会崩溃,类似于“在没有 shellexec 的情况下执行需要提升”
    • 我在想另一个 exe 可能是唯一的出路,除非有人能想到另一种方法。
    • 奇怪,听起来像是另一个副作用。是的,那个助手应该可以工作。
    • 附注:Windows 10 1607older - 在运行之前检查 c:\windows\system32\osk.exe 是否存在,将返回 true,但更新版本将返回 false。所以,你最好检查一下c:\windows\sysnative\osk.exe
    【解决方案3】:

    某些事情在后台发生,需要您从 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);
        }
    }
    

    【讨论】:

    • 嗯,奇怪,我可以正常运行它而无需提升,而且我发布的代码确实工作,只是不经常。我会添加海拔看看是否有帮助。谢谢。
    • 当通过Process.Start() 或从命令提示符手动启动 osk 时,我收到一条错误消息,指出“无法启动屏幕键盘”。在这两种情况下。
    • 好吧,我猜提升只是为 osk.exe 进程提供了不同的(64 位)父进程,因此 FS 重定向不再是问题......
    【解决方案4】:

    这是我的代码

                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);
                }
    

    【讨论】:

    • 作为良好的堆栈溢出实践,请尝试解释您编写的代码或为什么您的答案比其他答案更好。
    【解决方案5】:

    笨方法:

    在旁边运行这个批处理文件(从 64 位资源管理器开始):

    :实验室0 超时/T 1 >nul 如果存在 oskstart.tmp 转到 lab2 转到实验室0 :实验室2 删除 oskstart.tmp 操作系统 转到实验室0

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

    【讨论】:

      【解决方案6】:

      对于那些面临“无法启动屏幕键盘”的用户,请将项目的 Platform Target 更改为 Any CPU。

      【讨论】:

      • 这个问题的全部意义在于尝试在 64 位操作系统上使用 32 位进程来执行此操作 - 所以仅仅切换到任何 CPU 都无济于事!
      • Microsoft 建议您不要再这样做了。 blogs.msdn.com/b/rmbyers/archive/2009/06/08/…
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-03-11
      • 2011-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多