Windows ARM で3rdParty IMEは使えるのか
A. バイナリによる。
挙動は以下の通り。
バイナリ | 動作 |
---|---|
x86 | 動作可 |
x64 | 動作可 |
ARM64EC | 動作可 |
ARM64 | 動作不可 |
確認方法はタスクマネージャーの詳細>アーキテクチャから
ARM64 (x64 互換)はARM64ECです。
何で動く物と動かない物がある?
A.一言で言えばアセンブラ記述が違うから。
下記記事を読めば分かるが詳しく調べてみる。
次のような要素を持つアプリケーションを作成してみる。
コード
#include <windows.h>
#include <msctf.h>
#include <imm.h>
#include <vector>
#include <string>
#include <comdef.h>
#pragma comment(lib, "ole32.lib")
#pragma comment(lib, "imm32.lib")
struct IMEProfile {
LANGID langID;
CLSID clsid;
GUID guidProfile;
std::wstring description;
};
std::vector<IMEProfile> g_imeProfiles;
ITfInputProcessorProfiles* g_pProfiles = nullptr;
const wchar_t g_szClassName[] = L"TSF_IME_GUI_Window";
// コントロールのID
#define IDC_LISTBOX 101
#define IDC_ACTIVATE_BUTTON 102
// IMEプロファイルの列挙
bool EnumerateIMEProfiles() {
LANGID currentLang = GetSystemDefaultLangID();
IEnumTfLanguageProfiles* pEnum = nullptr;
HRESULT hr = g_pProfiles->EnumLanguageProfiles(currentLang, &pEnum);
if (FAILED(hr) || !pEnum) {
return false;
}
g_imeProfiles.clear();
TF_LANGUAGEPROFILE langProfile;
ULONG fetched = 0;
while (pEnum->Next(1, &langProfile, &fetched) == S_OK) {
IMEProfile profile;
profile.langID = langProfile.langid;
profile.clsid = langProfile.clsid;
profile.guidProfile = langProfile.guidProfile;
BSTR bstrDesc = nullptr;
hr = g_pProfiles->GetLanguageProfileDescription(langProfile.clsid, langProfile.langid, langProfile.guidProfile, &bstrDesc);
if (SUCCEEDED(hr) && bstrDesc) {
profile.description = bstrDesc;
SysFreeString(bstrDesc);
}
else {
profile.description = L"(説明なし)";
}
g_imeProfiles.push_back(profile);
}
pEnum->Release();
return !g_imeProfiles.empty();
}
// エラーメッセージ取得用の関数
std::wstring GetErrorMessage(HRESULT hr) {
wchar_t* msgBuffer = nullptr;
FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
NULL,
hr,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPWSTR)&msgBuffer,
0,
NULL);
std::wstring message;
if (msgBuffer) {
message = msgBuffer;
LocalFree(msgBuffer);
}
else {
message = L"エラーコード: " + std::to_wstring(hr);
}
return message;
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
switch (msg) {
case WM_CREATE:
{
// リストボックスの作成
HWND hList = CreateWindowW(L"LISTBOX", NULL,
WS_CHILD | WS_VISIBLE | LBS_NOTIFY | WS_VSCROLL,
10, 10, 300, 150,
hwnd, (HMENU)IDC_LISTBOX, GetModuleHandle(NULL), NULL);
// ボタンの作成
HWND hButton = CreateWindowW(L"BUTTON", L"IME有効化",
WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
320, 10, 100, 30,
hwnd, (HMENU)IDC_ACTIVATE_BUTTON, GetModuleHandle(NULL), NULL);
// IMEプロファイルを列挙してリストボックスに追加
if (!EnumerateIMEProfiles()) {
MessageBox(hwnd, L"利用可能なIMEプロファイルが見つかりません。", L"エラー", MB_ICONERROR);
}
else {
for (size_t i = 0; i < g_imeProfiles.size(); ++i) {
SendMessage(hList, LB_ADDSTRING, 0, (LPARAM)g_imeProfiles[i].description.c_str());
}
}
HWND hEdit = CreateWindowW(L"EDIT", L"",
WS_CHILD | WS_VISIBLE | WS_BORDER | ES_LEFT,
10, 170, 300, 25,
hwnd, NULL, GetModuleHandle(NULL), NULL);
}
break;
case WM_COMMAND:
if (LOWORD(wParam) == IDC_ACTIVATE_BUTTON) {
HWND hList = GetDlgItem(hwnd, IDC_LISTBOX);
int sel = (int)SendMessage(hList, LB_GETCURSEL, 0, 0);
if (sel == LB_ERR) {
MessageBox(hwnd, L"IMEプロファイルが選択されていません", L"警告", MB_ICONWARNING);
}
else if (sel < 0 || sel >= (int)g_imeProfiles.size()) {
MessageBox(hwnd, L"選択番号が不正です。", L"エラー", MB_ICONERROR);
}
else {
IMEProfile chosenProfile = g_imeProfiles[sel];
HRESULT hr = g_pProfiles->ChangeCurrentLanguage(chosenProfile.langID);
if (FAILED(hr)) {
std::wstring errMsg = L"現在の言語の変更に失敗。\n理由: " + GetErrorMessage(hr);
MessageBox(hwnd, errMsg.c_str(), L"エラー", MB_ICONERROR);
break;
}
hr = g_pProfiles->ActivateLanguageProfile(chosenProfile.clsid, chosenProfile.langID, chosenProfile.guidProfile);
if (FAILED(hr)) {
std::wstring errMsg = L"IMEプロファイルの有効化に失敗。\n理由: " + GetErrorMessage(hr);
MessageBox(hwnd, errMsg.c_str(), L"エラー", MB_ICONERROR);
}
}
}
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hwnd, msg, wParam, lParam);
}
return 0;
}
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE, PWSTR, int nCmdShow) {
HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
if (FAILED(hr)) {
MessageBox(NULL, L"COMの初期化に失敗。", L"エラー", MB_ICONERROR);
return 1;
}
hr = CoCreateInstance(CLSID_TF_InputProcessorProfiles, NULL, CLSCTX_INPROC_SERVER,
IID_ITfInputProcessorProfiles, (void**)&g_pProfiles);
if (FAILED(hr) || !g_pProfiles) {
MessageBox(NULL, L"ITfInputProcessorProfilesのインスタンス作成に失敗。", L"エラー", MB_ICONERROR);
CoUninitialize();
return 1;
}
WNDCLASSW wc = { 0 };
wc.lpfnWndProc = WndProc;
wc.hInstance = hInstance;
wc.lpszClassName = g_szClassName;
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
if (!RegisterClassW(&wc)) {
MessageBox(NULL, L"ウィンドウクラスの登録に失敗", L"エラー", MB_ICONERROR);
g_pProfiles->Release();
CoUninitialize();
return 1;
}
HWND hwnd = CreateWindowW(g_szClassName, L"IME切替 GUI",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, 450, 250,
NULL, NULL, hInstance, NULL);
if (!hwnd) {
MessageBox(NULL, L"ウィンドウの作成に失敗", L"エラー", MB_ICONERROR);
g_pProfiles->Release();
CoUninitialize();
return 1;
}
ShowWindow(hwnd, nCmdShow);
UpdateWindow(hwnd);
MSG msg;
while (GetMessage(&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
g_pProfiles->Release();
CoUninitialize();
return (int)msg.wParam;
}
実行するとx64,x86,ARM64ECバイナリではMS-IMEでも予測変換が出る。
しかしARM64バイナリでは下のように予測変換が出てこない。
デバッガの出力を見ると
'IME.exe' (Win32): 'C:\Program Files\JustSystems\ATOK32T31_X64\ATOK32TIP.DLL' が読み込まれました。含める/除外するの設定でシンボルの読み込みが無効になっています。
'IME.exe' (Win32): 'C:\Program Files\JustSystems\ATOK32T31_X64\ATOK32TIP.DLL' がアンロードされました
とあるので、Text Services Frameworkが利用するText Input Processorが存在しないため入力出来ないと考えられる。
Arm 上の Windows 11 オペレーティング システム自体は、x64 アプリケーションの実行を可能にするために、Arm64EC の相互運用性に大きく依存しています。 Arm 上の Windows 11 で実行されている x64 アプリによって読み込まれたほとんどのオペレーティング システム コードは Arm64EC としてコンパイルされており、アプリケーションが関知しなくてもそのコードのネイティブ パフォーマンスが有効になります。
上記からx64,ARM64ECバイナリではWindows APIのARM64ECコードが呼ばれるため従来のx64向けIMEが動作するが、ARM64バイナリではWindows APIのARMコードが呼ばれる為、従来のx64向けIMEが動作しない。
結局3rdPary製のIMEは使える様になるのか
A.今の所無理。
現実的な解決案?として
- MS-IMEしか使わないようにする。
- ARM64のアプリケーションを使わないようにする。
- ARM64バイナリのIMEが出るのを待つ+Arm64X バイナリで配布されるのを待つ。
- ARM64バイナリのIMEが出るのを待つ+x64向けのIMEを両方利用する。
ぐらいが考えられる。
結局の所、x64と同じ使用感で扱える様にするにはIMEを作成している各社がARM64向けビルドの作成とArm64X バイナリにまとめて配布する必要がある。
まとめ?
バイナリによって従来のIMEが使えるか変わるためARM64とx64向けのバイナリのみを用意するのでは無く、ARM64EC向けのバイナリもあるとエンドユーザーはうれしいと思う。