Windows IME のサンプルを動かす

# はじめに

Windows 日本語 IME の自作にチャレンジしてみようかと思います。
リファレンスとしては以下になると思いますが、これを読んでもさっぱり作り方がわかりません。
- [Input Method Editor (IME)](https://learn.microsoft.com/ja-jp/windows/apps/design/input/input-method-editors)
- [Text Serrvices Framework (TSF)](https://learn.microsoft.com/ja-jp/windows/win32/tsf/text-services-framework)

そこで、Microsoft が提供しているサンプルから始めてみます。

# 環境

Winddows Professional (Hyper-V が実行可能)
Visual Studio 2022

# Microsoft のサンプルをビルド

[Windows-classic-sample という repo](https://github.com/microsoft/Windows-classic-samples) に IME のサンプルが入っていますので、これを使ってみます。[Sample IME](https://github.com/microsoft/Windows-classic-samples/tree/main/Samples/IME) の下だけ適当にコピーして、VS2022 で開いてみますと、意外とターゲット ToolSet の更新 (v110 => v143) だけでビルドは成功します。

# Hyper-V VM の作成

IME はインストールしてアクティブにすると、ほぼすべてのプロセスに張り付いて、Visual Studio (IDE) にすらロードされてしまい、バグがあるとキー入力からできません。したがって、普通は Virtual Machine を使って開発するものと思います。

Hyper-Vの設定は、以下にあります。(多分 Professional 以上が必要です)
設定 ⇨ オプション機能 ⇨ Windowsのその他の機能 ⇨ Hyper-V

そして、Hyper-V マネージャーから、標準の開発環境をダウンロードします
クイック設定 ⇨ Windows 11 開発環境

Visual Machineが起動するようになったら、接続のプロパティからホスト側のドライブを共有設定するのがおすすめです。こうすると、ホスト側のドライブが \\tsclient\ 以下に出てきて、データ共有が簡単になります。
接続... ⇨ ローカルリソース ⇨ 詳細 ⇨ ドライブC,D,E,F,...

# まずはマニュアルでデプロイ

ゲスト(VM)側に適当なフォルダを作って、ビルド済みの `\\\\tsclient\\{X}\\{Sample IME project}\\x64\\Debug\\SampleIME.dll` と、辞書ファイル `\\\\tsclient\\{X}\\{Sample IME project}\\Dictionary\\SampleIMESimplifiedQuanPin.txt` をコピーします。
そして、登録します。
```
regsvr32 SampleIME.dll
```
これで、IMEが登録されて、Simplified Chinese の SampleIME が使えるようになります。(注: IME On/Off は Shift キーです)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3540211/294062e2-d664-e59e-575b-818c4db5709c.png)
動作が確認できたらいったん unregister しましょう。
```
regsvr32 /u SampleIME.dll
```

# Setup プロジェクトを作る

[SampleIME の README.md](https://github.com/microsoft/Windows-classic-samples/blob/main/Samples/IME/README.md) に、InstallShield によるセットアップの作成方法のドキュメントが含まれているのですが、VS2022 では使用できないので、新しく Setup プロジェクトを作ります。
ですが、その前に、setup プロセスから Install するのに、TSF に登録する前に COM を初期化する Entry が必要なので、[Server.cpp](https://github.com/microsoft/Windows-classic-samples/blob/main/Samples/IME/cpp/SampleIME/Server.cpp) に以下を追加します。

```C++
extern "C" __declspec(dllexport) UINT STDAPICALLTYPE Install(MSIHANDLE /* hInstall */) try
{
    // This entry is called from msiexec at installation. We need to initialize COM with STA.
    HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
    if (SUCCEEDED(hr)) {
        hr = DllRegisterServer();
        if (SUCCEEDED(hr)) {
            return static_cast<UINT>(hr);
        }
        CoUninitialize();
    } else {
        return static_cast<UINT>(ERROR_INSTALL_FAILED);
    }
    return static_cast<UINT>(ERROR_SUCCESS);
} catch (...) {
    return static_cast<UINT>(ERROR_EXCEPTION_IN_SERVICE);
}

extern "C" __declspec(dllexport) UINT STDAPICALLTYPE Uninstall(MSIHANDLE /* hInstall */) try
{
    // This entry is called from msiexec at installation. We need to initialize COM with STA.
    HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
    if (SUCCEEDED(hr)) {
        hr = DllUnregisterServer();
        if (SUCCEEDED(hr)) {
            return static_cast<UINT>(hr);
        }
        CoUninitialize();
    } else {
        return static_cast<UINT>(ERROR_INSTALL_FAILED);
    }
    return static_cast<UINT>(ERROR_SUCCESS);
} catch (...) {
    return static_cast<UINT>(ERROR_EXCEPTION_IN_SERVICE);
}

```

それでは、Setup プロジェクトをソリューションに追加します。

1. ソリューションを右クリックして新規プロジェクトを追加する
1. プロジェクト作成ダイアログの検索ボックスに "Installer" と入力して検索
1. "Setup Project" を選ぶ
1. プロジェクト名 SampleIMESetup でプロジェクト作成
1. プロジェクトのプロパティでターゲットプラットフォームを x64 にする。そして、Version等、適当に設定する。
1. ファイルシステムエディタのアプリケーションから、SampleIMEの出力を追加 (SampleIME.dllに加えて、必要な vcruntime dll も含まれる…まあ素敵!)
1. ファイルシステムエディタのアプリケーションに、`SampleIME\\Dictionary\\SampleIMESimplifiedQuanPin` も追加
1. カスタムアクションのインストールとアンインストールに、SampleIMEのプライマリ出力を追加して、EntryPoint を Install, Uninstall と設定。そして Run64Bit を True に

これをビルドすると、SampleIMESetup.msi が作られます。ターゲットVMでセットアップして動作を確認してください。

# デバッグしてみる

1. ターゲットVM上で
    1. ターゲットVMで Visual Studio Remote Debugger を起動
    1. メモ帳を起動
    1. 言語を "Simplified Chinese (SampleIME)" に変更
1. ホスト上で
    1. Visual Studio から Romote Debugger に接続
    1. メモ帳にアタッチ
    1. [KeyEventSink.cpp](https://github.com/microsoft/Windows-classic-samples/blob/main/Samples/IME/cpp/SampleIME/KeyEventSink.cpp)の`CSampleIME::OnKeyDown`にブレイクポイントを張る
1. ターゲットVM上で
    1. キーを入力する

ホスト側の Visual Studio でブレイクポイントに引っかかるはずです。

# まとめ
- SampleIMEをビルドして、使えることを確認した
- SampleIMEのセットアッププロジェクトを作った
- SampleIMEをVM上で動かして、デバッグしてみた