Steven Robbins Steven Robbins - 1 month ago 21
C# Question

Unable to launch onscreen keyboard (osk.exe) from a 32-bit process on Win7 x64

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.

Answer

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.

Comments