Не удалось запустить экранную клавиатуру (ОСК.exe) из 32-разрядного процесса на Win7 x64

90% времени я не могу запустить osk.exe от 32бит процесс о Win7 x64. Первоначально код просто использовал:

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():

не удалось найти указанную процедуру в системе.Диагностика.Процесс.StartWithShellExecuteEx (ProcessStartInfo startInfo)

Я пробовал положить длинную нить.Команды сна вокруг osk.Линия старта, просто чтобы убедиться, что это не гонки, но та же проблема сохраняется. Может ли кто-нибудь заметить где я делаю что-то неправильно, или обеспечить альтернативное решение для этого? Это кажется чтобы работать нормально запуск Блокнота, он просто не будет играть в мяч с экранной клавиатурой.

7 ответов


У меня нет очень твердого объяснения для точного сообщения об ошибке, которое вы получаете. Но отключение перенаправления испортит .NET framework. По умолчанию процесс.Start() P/вызывает функцию API ShellExecuteEx () для запуска процесса. Эта функция живет в заshell32.dll, DLL, которая может быть загружена, если это не было сделано ранее. Вы получите неправильный, когда отключите перенаправление.

обходным путем для этого является установка ProcessStartInfo.Значение свойства useshellexecute значение false. Здесь он тебе не нужен.

очевидно, что отключение перенаправления является рискованным подходом с побочными эффектами, которые вы не можете предсказать. Есть много DLL, которые загружаются по требованию. Очень маленький помощник EXE, который вы компилируете с платформой Target = любой процессор может решить вашу проблему.


32-разрядное приложение, работающее на 64-разрядной операционной системе, должно запустить 64-разрядную версию osk.исполняемый. Ниже вы видите код, вырезанный на 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);
    }

некоторые вещи происходят под капотом, которые требуют, чтобы вы начали osk.exe из потока MTA. Причина, кажется, в том, что призыв к 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);
    }
}

этот поток здесь, похоже, содержит некоторые реальные решения: http://www.dreamincode.net/forums/topic/174949-open-on-screen-keyboard-in-c%23/

в частности, я пробовал его на Win7 x64 и WinXP 32 бит до сих пор, и он работает.

static void StartOSK()
{
  string windir = Environment.GetEnvironmentVariable("WINDIR");
  string osk = null;

  if (osk == null)
  {
    osk = Path.Combine(Path.Combine(windir, "sysnative"), "osk.exe");
    if (!File.Exists(osk))
    {
      osk = null;
    }
  }

  if (osk == null)
  {
    osk = Path.Combine(Path.Combine(windir, "system32"), "osk.exe");
    if (!File.Exists(osk))
    {
      osk = null;
    }
  }

  if (osk == null)
  {
    osk = "osk.exe";
  }

  Process.Start(osk);
}

топорный метод:

запустите этот пакетный файл сбоку (запускается из 64-битного проводника):

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

создать файл oskstart.tmp, когда вам нужна клавиатура


Cambiar Лас-сайт propiedades-де-ла aplicación. COMPILAR-Desmarcar проверить preferencia 32 бит.

изменить свойства приложения Compila-снимите флажок 'BEST 32 BITS' (или аналогичный)

вы можете прочитать это:

http://blog.anthonybaker.me/2012/08/spawning-windows-on-screen-keyboard-osk.html

или сделать другое приложение с этой опции и обед (run) из вашего основного приложения.


для тех, кто сталкивается "не удалось запустить экранную клавиатуру.", измените цель платформы вашего проекта на любой процессор.