64ビットWindowsで.NET FrameworkのProcess.Startからソフトウェアキーボードが起動しない

Windows7の64ビットOSでC#のプログラムからProcess.Startでソフトウェアキーボード(osk.exe)を起動させようとしたところ、エラーメッセージが表示されてしまい、大ハマリしました。
解決策を見つけたので、覚書として残しておきます。
ScreenKeyboard
64ビットWindowsの場合、フォルダやレジストリのリダイレクトが働くため、アプリケーションが正常に動作しないことがあります。
対策として、一時的にリダイレクションを無効化し、起動してから元に戻します。
単純なアプリケーションである、メモ帳(notepad.exe)などは、対策をしなくても問題なく起動するようです。

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

(2013年7月15日修正)
参考文献のまま起動すると、osk.WaitForInputIdle(2000);の行で
「WaitForInputIdle に失敗しました。プロセスがグラフィック インターフェイスを含んでいない可能性があります。」
が発生するので、コメントアウトしています。
終了はリダイレクション関係なしにKillすれば大丈夫でした。
ほとんどコピーですが、ソースを残しておきます。

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

using System.Diagnostics;
using System.Runtime.InteropServices;

namespace ScreenKeyboardBootSample
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        //参考文献
        //Unable to launch onscreen keyboard (osk.exe) from a 32-bit process on Win7 x64
        //http://stackoverflow.com/questions/2929255/unable-to-launch-onscreen-keyboard-osk-exe-from-a-32-bit-process-on-win7-x64

         [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";

        //起動
        private void button1_Click(object sender, EventArgs 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);//起動してすぐはWaitForInputIdleで異常が発生するため、コメントアウト (異常内容:WaitForInputIdle に失敗しました。プロセスがグラフィック インターフェイスを含んでいない可能性があります。)
                }

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

        //終了
        private void button2_Click(object sender, EventArgs 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)
            {
                //Kill process.
                keyboardProcess.Kill();
                keyboardProcess.WaitForExit();
                keyboardProcess.Close();
                keyboardProcess.Dispose();
            }
        }
    }
}

ソフト

Posted by minidora