Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 549
Steven Robbins Avatar asked May 28 '10 13:05

Steven Robbins


Video Answer


3 Answers

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);
    }
like image 109
Rudy Meijer Avatar answered Oct 17 '22 21:10

Rudy Meijer


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.

like image 29
Hans Passant Avatar answered Oct 17 '22 20:10

Hans Passant


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);
            }
like image 4
Phan Hoang Avatar answered Oct 17 '22 19:10

Phan Hoang