RIME (中州韻輸入法引擎)是一個跨平臺的輸入法框架。基於這一框架,開發者们在Windows、macOS、Linux、Android等平臺上創造了不同的輸入法前端實現。

Weasel (小狼毫)是它的Windows版,从0.9.30到0.17.4版支持Windows XP SP3, 7, 8/8.1, 10, 11.

从github下载了最新版。里面有env.vs2022.bat, 2019.bat, Visual Studio 2022 (Version 17)的.sln,2015的.vcxproj.

AI说:开发IME (Input Method Editor),当前推荐使用TSF而非传统的IMM32实现‌。Text Services Framework集成于Windows XP及后续中。‌‌文本服务层‌作为COM组件实现,提供键盘输入、手写识别、语音识别等功能。

SilverLight安否?它是微软2007年推出的跨浏览器插件,用于网页端富媒体(视频、交互式应用等)的渲染和开发‌。

Weasel依然用.def文件而不是dllexport(改错:WeaselTSF目录下7266行,ctffunc.h 3225行,TSF的头文件). 它是个导出下列符号的.dll:
ImeConversionList, ImeConfigure, ImeDestroy, ImeEscape, ImeInquire, ImeProcessKey, ImeSelect...

一个.dll如何成为输入法?修改注册表。要改HKCU\Keyboard Layout\Preload和HKLM\SYSTEM\CurrentControlSet\Control\Keyboard Layouts等处。

$ find -name '*' | xargs grep RegCreate
./WeaselSetup/imesetup.cpp: ret = RegCreateKey(hKey, hkl_str, &hSubKey);
./WeaselSetup/InstallOptionsDlg.h: ret = RegCreateKeyEx(rootKey, subpath, 0, NULL, 0,

const WCHAR KEYBOARD_LAYOUTS_KEY[] =
  L"SYSTEM\\CurrentControlSet\\Control\\Keyboard Layouts";
const WCHAR PRELOAD_KEY[] = L"Keyboard Layout\\Preload";

if (register_ime) {
HKL hKL = ImmInstallIME(ime_path.c_str(), get_weasel_ime_name().c_str());
if (!hKL) {
  // manually register ime
  WCHAR hkl_str[16] = {0};
  HKEY hKey;
  LSTATUS ret = RegOpenKey(HKEY_LOCAL_MACHINE, KEYBOARD_LAYOUTS_KEY, &hKey);
  if (ret == ERROR_SUCCESS) {
    for (DWORD k = 0xE0200000 + (hant ? 0x0404 : 0x0804); k <= 0xE0FF0804;
         k += 0x10000) {
      StringCchPrintfW(hkl_str, _countof(hkl_str), L"%08X", k);
      HKEY hSubKey;
      ret = RegOpenKey(hKey, hkl_str, &hSubKey);
      if (ret == ERROR_SUCCESS) {
        WCHAR imeFile[32] = {0};
        DWORD len = sizeof(imeFile);
        DWORD type = 0;
        ret = RegQueryValueEx(hSubKey, L"Ime File", NULL, &type,
                              (LPBYTE)imeFile, &len);
        if (ret = ERROR_SUCCESS) {
          if (_wcsicmp(imeFile, L"weasel.ime") == 0) {
            hKL = (HKL)k;  // already there
          }
        }
        RegCloseKey(hSubKey);
      } else {
        // found a spare number to register
        ret = RegCreateKey(hKey, hkl_str, &hSubKey);
        if (ret == ERROR_SUCCESS) {
          const WCHAR ime_file[] = L"weasel.ime";
          RegSetValueEx(hSubKey, L"Ime File", 0, REG_SZ, (LPBYTE)ime_file,
                        sizeof(ime_file));
          const WCHAR layout_file[] = L"kbdus.dll";
          RegSetValueEx(hSubKey, L"Layout File", 0, REG_SZ,
                        (LPBYTE)layout_file, sizeof(layout_file));
          const std::wstring layout_text = get_weasel_ime_name();
          RegSetValueEx(hSubKey, L"Layout Text", 0, REG_SZ,
                        (LPBYTE)layout_text.c_str(),
                        layout_text.size() * sizeof(wchar_t));
          RegCloseKey(hSubKey);
          hKL = (HKL)k;
        }
        break;
      }
    }
    RegCloseKey(hKey);
  }
  if (hKL) {
    HKEY hPreloadKey;
    ret = RegOpenKey(HKEY_CURRENT_USER, PRELOAD_KEY, &hPreloadKey);
    if (ret == ERROR_SUCCESS) {
      for (size_t i = 1; true; ++i) {
        std::wstring number = std::to_wstring(i);
        DWORD type = 0;
        WCHAR value[32];
        DWORD len = sizeof(value);
        ret = RegQueryValueEx(hPreloadKey, number.c_str(), 0, &type,
                              (LPBYTE)value, &len);
        if (ret != ERROR_SUCCESS) {
          RegSetValueEx(hPreloadKey, number.c_str(), 0, REG_SZ,
                        (const BYTE*)hkl_str,
                        (wcslen(hkl_str) + 1) * sizeof(WCHAR));
          break;
        }
      }
      RegCloseKey(hPreloadKey);
    }
  }
}
if (!hKL) {
  DWORD dwErr = GetLastError();
  WCHAR msg[100];
  CString str;
  str.LoadStringW(IDS_STR_ERRREGIME);
  StringCchPrintfW(msg, _countof(msg), str, hKL, dwErr);
  MSG_NOT_SILENT_ID_CAP(silent, msg, IDS_STR_INSTALL_FAILED,
                        MB_ICONERROR | MB_OK);
  return 1;
}
return 0;
}
View Code

~/weasel-master/WeaselIME$ wc -l *.cpp *.h
26 dllmain.cpp
155 ime.cpp
267 KeyEvent.cpp
8 stdafx.cpp
566 WeaselIME.cpp
941 immdev.h
16 resource.h
21 stdafx.h
12 targetver.h
69 WeaselIME.h
2081 总计

/* immdev.h - Input Method Manager definitions for IME developers */
/* Copyright (c) Microsoft Corporation. All rights reserved. */

很早以前,immdev.h, immdev.lib在DDK (Device Driver Development Kit)里,还带区位等输入法的源码,后来好像挪进SDK里。

有头文件,有dll,但是没.lib. AI说:创建一个文本文件imm32.def,内容如下:
EXPORTS
ImmGetDefaultIMEWnd
ImmInstallIMEA
...
打开适用于您Visual Studio版本的Developer Command Prompt,然后执行命令:lib /def:imm32.def /out:imm32.lib /MACHINE:X64

微软网站说:You can use LIB with the /DEF option to create an import library and an export file. LINK uses the export file to build a program that contains exports (usually a dynamic-link library (DLL)), and it uses the import library to resolve references to those exports in other programs.

嗯,① DLL的导入库与静态库不同,后者包含全部代码,前者只是存根。② 过去我总说“导出给别人用的导入库”,别扭。

XP下Windows\SYSTEM32\imm32.dll一般为107KB (精简版可能不同)。

So, 您面对的就是1000行程序,而已。(改错:基本是外包层)

那些函数的文档,微软网站有:

This section describes the IMM API and explains how to use the functionality to create and manage IME windows. It includes the following sections:

  • About Input Method Manager
  • Using Input Method Manager
  • Input Method Manager Reference

IME自己没有消息循环,而是依赖宿主的。IME可以CreateWindow. WIN32 API.

GDI+通过设备上下文(DC)作为与硬件交互的桥梁,提供矢量图形绘制、图像处理、文本渲染等功能‌。gdiplus.h gdiplus.lib

MFC应用程序启动时,theApp对象首先被构造,随后程序进入AfxWinMain函数并最终调用CWinApp::Run()启动主消息循环,使用::GetMessage()从线程消息队列中获取消息。

WPF应用程序启动时,系统会自动创建一个UI主线程,并在其上运行消息循环。

~/weasel-master/WeaselUI$ wc -l *.cpp
278 DirectWriteResources.cpp
142 FullScreenLayout.cpp
361 GdiplusBlur.cpp
262 HorizontalLayout.cpp
124 Layout.cpp
395 StandardLayout.cpp
8 stdafx.cpp
239 VerticalLayout.cpp
602 VHorizontalLayout.cpp
1270 WeaselPanel.cpp
179 WeaselUI.cpp
3860 总计


看过本书,好像叫高级C/C++编译技术 Advanced C and C++ Compiling,写得很好(没说我看懂了)。

Linux下.a是为-static准备的;直接链接到.so

This package, x11proto-dev ,  provides development headers blah blah, and also provides a number of utility headers ... 没有.so

posted on 2025-10-28 15:16  华容道专家  阅读(6)  评论(0)    收藏  举报