Невозможно запустить экранную клавиатуру (osk.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()
:
Не удалось найти указанную процедуру в System.Diagnostics.Process.StartWithShellExecuteEx(ProcessStartInfo startInfo)
Я попытался поместить длинные команды Thread.Sleep в строку osk.Start, просто чтобы убедиться, что это не условие гонки, но та же проблема сохраняется. Может кто-нибудь определить, где я делаю что-то не так, или предложить альтернативное решение для этого? Кажется, что он отлично работает при запуске Notepad, он не будет играть в мяч с помощью экранной клавиатуры.
9 ответов
У меня нет четкого объяснения того, какое именно сообщение об ошибке вы получаете. Но отключение перенаправления может испортить платформу.NET. По умолчанию Process.Start() P/ вызывает функцию API ShellExecuteEx() для запуска процесса. Эта функция находится в shell32.dll, DLL, которая может быть загружена, если это не было сделано ранее. Вы получите неправильный, когда отключите перенаправление.
Обходной путь для этого должен установить ProcessStartInfo.UseShellExecute в false. Вам это не нужно здесь.
Очевидно, что отключение перенаправления является рискованным подходом с побочными эффектами, которые вы не можете предсказать. Есть много DLL, которые загружаются по требованию. Очень маленький вспомогательный EXE-файл, который вы компилируете с помощью Platform Target = Любой процессор, может решить вашу проблему.
32-разрядное приложение, работающее в 64-разрядной операционной системе, должно запускать 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);
}
Это мой код
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);
}
Некоторые вещи происходят под капотом, которые требуют запуска 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 bit, и это работает.
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-битный процесс из 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
необходим для запуска Экранной клавиатуры, не дожидаясь ее закрытия.
Бегosk.exe
из реальной папки System32 все должно быть в порядке, независимо от разрядности ОС и приложения. Судя по следующему сообщению, папка System32 содержит 64-битные приложения в 64-битной Windows: https://www.ibm.com/support/pages/why-do-64-bit-dlls-go-system32-and-32-bit . -dlls-syswow64-64-бит-окна/
Я протестировал код в приложении .NET Framework 4.7.2 с конфигурацией сборки процессора x86/x64/любой в Windows 10 Pro 21H2 (64-разрядной версии).
Неуклюжий метод:
Запустите этот пакетный файл на стороне (запущен из 64-разрядного обозревателя):
: lab0 ВРЕМЯ /T 1 > нуль если есть oskstart.tmp goto lab2 Перейти к lab0:lab2 del oskstart.tmp OSK Перейти к lab0
Создайте файл oskstart.tmp, когда вам нужна клавиатура
Cambiar las propiedades de la aplicación. COMPILAR - Desmarcar проверяет предпочтения 32 бита.
Изменить свойства приложения Compila - снимите флажок "ЛУЧШИЕ 32 БИТА" (или аналогичный)
Вы можете прочитать это:
http://blog.anthonybaker.me/2012/08/spawning-windows-on-screen-keyboard-osk.html
или сделать другое приложение с этими параметрами и пообедать (запустить) из вашего основного приложения.
Для тех, кто сталкивается с вопросом "Не удалось запустить экранную клавиатуру", измените "Цель платформы" вашего проекта на "Любой процессор".