c - 如何在 Windows 10 中正确拦截(挂钩)IME 输入?
问题描述
我尝试实现一个自动化工具,但在拦截 IME(Windows 10 的默认 Microsoft IME)输入 Unicode 字符串(如日文/中文)时遇到问题。
我编写了一个 64 位 dll 用于注入其他进程/窗口。dll如下,
#include <windows.h>
#include <fstream>
#include <locale>
// for output only
static wchar_t* className(HWND hwnd) {
static wchar_t className[128];
::GetClassNameW(hwnd, className, 128);
return className;
}
extern "C" __declspec(dllexport) LRESULT CALLBACK ImeCallback(int code, WPARAM wParam, LPARAM lParam) {
std::wofstream out("test.log", std::ios::app);
out.imbue(std::locale("zh_CN.UTF-8"));
if (code >= 0)
{
PCWPSTRUCT msg = (PCWPSTRUCT)lParam;
if (msg->message == WM_IME_COMPOSITION) {
out << "composition: " << className(msg->hwnd) << ":";
if (msg->lParam & GCS_COMPATTR) {
out << ":GCS_COMPATTR";
}
if (msg->lParam & GCS_COMPCLAUSE) {
out << ":GCS_COMPCLAUSE";
}
if (msg->lParam & GCS_COMPREADSTR) {
out << ":GCS_COMPREADSTR";
}
if (msg->lParam & GCS_COMPREADATTR) {
out << ":GCS_COMPREADATTR";
}
if (msg->lParam & GCS_COMPREADCLAUSE) {
out << ":GCS_COMPREADCLAUSE";
}
if (msg->lParam & GCS_COMPSTR) {
out << ":GCS_COMPSTR";
}
if (msg->lParam & GCS_CURSORPOS) {
out << ":GCS_CURSORPOS";
}
if (msg->lParam & GCS_DELTASTART) {
out << ":GCS_DELTASTART";
}
if (msg->lParam & GCS_RESULTCLAUSE) {
out << ":GCS_RESULTCLAUSE";
}
if (msg->lParam & GCS_RESULTREADCLAUSE) {
out << ":GCS_RESULTREADCLAUSE";
}
if (msg->lParam & GCS_RESULTREADSTR) {
out << ":GCS_RESULTREADSTR";
}
if (msg->lParam & GCS_RESULTSTR) {
out << ":GCS_RESULTSTR";
}
out << std::endl;
if (msg->lParam & GCS_RESULTSTR) {
wchar_t data[128] = { 0 };
HIMC context = ImmGetContext(msg->hwnd);
ImmGetCompositionStringW(context, GCS_RESULTSTR, data, 255);
ImmReleaseContext(msg->hwnd, context);
out << " result data: " << data << std::endl;
}
else if (msg->lParam & GCS_COMPSTR) {
wchar_t data[128] = { 0 };
HIMC context = ImmGetContext(msg->hwnd);
ImmGetCompositionStringW(context, GCS_COMPSTR, data, 255);
ImmReleaseContext(msg->hwnd, context);
out << " composition data: " << data << std::endl;
}
}
else if (msg->message == WM_IME_STARTCOMPOSITION) {
out << "start composition" << std::endl;
}
else if (msg->message == WM_IME_ENDCOMPOSITION)
{
out << "end composition" << std::endl;
}
else if (msg->message == WM_IME_SETCONTEXT) {
out << "set context : " << className(msg->hwnd) << ":" << (wParam ? "TRUE" : "FALSE") << std::endl;
switch (msg->lParam) {
case ISC_SHOWUICOMPOSITIONWINDOW:
out << " ISC_SHOWUICOMPOSITIONWINDOW" << std::endl;
break;
case ISC_SHOWUIGUIDELINE:
out << " ISC_SHOWUIGUIDELINE" << std::endl;
break;
case ISC_SHOWUIALLCANDIDATEWINDOW:
out << " ISC_SHOWUIALLCANDIDATEWINDOW" << std::endl;
break;
case ISC_SHOWUIALL:
out << " ISC_SHOWUIALL" << std::endl;
break;
case ISC_SHOWUICANDIDATEWINDOW:
out << " ISC_SHOWUICANDIDATEWINDOW" << std::endl;
break;
case ISC_SHOWUICANDIDATEWINDOW << 1:
out << " ISC_SHOWUICANDIDATEWINDOW << 1" << std::endl;
break;
case ISC_SHOWUICANDIDATEWINDOW << 2:
out << " ISC_SHOWUICANDIDATEWINDOW << 2" << std::endl;
break;
case ISC_SHOWUICANDIDATEWINDOW << 3:
out << " ISC_SHOWUICANDIDATEWINDOW << 3" << std::endl;
break;
default:
out << " default" << std::endl;
}
}
else if (msg->message == WM_IME_NOTIFY) {
HIMC context;
wchar_t data[128] = { 0 };
out << "notify : " << className(msg->hwnd) << std::endl;
switch (msg->wParam) {
case IMN_CHANGECANDIDATE:
out << " IMN_CHANGECANDIDATE" << std::endl;
break;
case IMN_CLOSECANDIDATE:
out << " IMN_CLOSECANDIDATE" << std::endl;
break;
case IMN_CLOSESTATUSWINDOW:
out << " IMN_CLOSESTATUSWINDOW" << std::endl;
break;
case IMN_GUIDELINE:
out << " IMN_GUIDELINE" << std::endl;
break;
case IMN_OPENCANDIDATE:
out << " IMN_OPENCANDIDATE" << std::endl;
break;
case IMN_OPENSTATUSWINDOW:
out << " IMN_OPENSTATUSWINDOW" << std::endl;
break;
case IMN_SETCANDIDATEPOS:
out << " IMN_SETCANDIDATEPOS" << std::endl;
break;
case IMN_SETCOMPOSITIONFONT:
out << " IMN_SETCOMPOSITIONFONT" << std::endl;
break;
case IMN_SETCOMPOSITIONWINDOW:
out << " IMN_SETCOMPOSITIONWINDOW" << std::endl;
break;
case IMN_SETCONVERSIONMODE:
out << " IMN_SETCONVERSIONMODE" << std::endl;
break;
case IMN_SETOPENSTATUS:
out << " IMN_SETOPENSTATUS" << std::endl;
break;
case IMN_SETSENTENCEMODE:
out << " IMN_SETSENTENCEMODE" << std::endl;
break;
case IMN_SETSTATUSWINDOWPOS:
out << " IMN_SETSTATUSWINDOWPOS" << std::endl;
break;
default:
out << " default: " << msg->wParam << ":" << msg->lParam << std::endl;
context = ImmGetContext(msg->hwnd);
auto result = ImmGetCompositionStringW(context, GCS_RESULTSTR, data, 256);
ImmReleaseContext(msg->hwnd, context);
out << " data: " << data << std::endl;
}
}
else if (msg->message == WM_IME_CHAR) {
out << "char:" << msg->wParam << std::endl;
}
else if (msg->message == WM_IME_CONTROL) {
out << "control" << std::endl;
}
else if (msg->message == WM_IME_SELECT) {
out << "select : " << className(msg->hwnd) << " : " << (msg->wParam ? "TRUE" : "FALSE") << " : " << msg->lParam << std::endl;
}
else if (msg->message == WM_IME_KEYDOWN) {
out << "key down" << std::endl;
}
else if (msg->message == WM_IME_KEYUP) {
out << "key up" << std::endl;
}
else if (msg->message == WM_IME_REQUEST) {
out << "request" << std::endl;
}
}
out.close();
return ::CallNextHookEx(0, code, wParam, lParam);
}
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
break;
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
break;
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
然后我编写了一个 64 位 .NET 应用程序来注入这个 dll。
public delegate long HookProc(long code, long wParam, long lParam);
[DllImport("kernel32.dll", EntryPoint = "LoadLibraryW", SetLastError = true)]
public static extern IntPtr LoadLibrary([MarshalAs(UnmanagedType.LPWStr)] string lpFileName);
[DllImport("kernel32.dll", EntryPoint = "FreeLibrary", SetLastError = true)]
public static extern IntPtr FreeLibrary([In] IntPtr hModule);
[DllImport("kernel32.dll", BestFitMapping = false, CharSet = CharSet.Ansi, ExactSpelling = true, SetLastError = true, ThrowOnUnmappableChar = true)]
public static extern IntPtr GetProcAddress(IntPtr hModule, [MarshalAs(UnmanagedType.LPStr)] string procName);
[DllImport("user32.dll")]
public static extern int SetWindowsHookEx(int hookType, HookProc hookFn, IntPtr hMod, int threadId);
[DllImport("user32.dll")]
public static extern int UnhookWindowsHookEx(int hookId);
public int hookId;
private void button1_Click(object sender, EventArgs e)
{
IntPtr hMod = LoadLibrary("ImeHook.dll");
IntPtr callback = GetProcAddress(hMod, "ImeCallback");
// 4 represents WH_CALLWNDPROC
// Try to inject into all other 64-bits applications
hookId = SetWindowsHookEx(4, (HookProc)Marshal.GetDelegateForFunctionPointer(callback, typeof(HookProc)), hMod, 0);
}
private void button2_Click(object sender, EventArgs e)
{
UnhookWindowsHookEx(hookId);
}
该程序确实可以将 DLL 注入其他进程,因为我可以在日志文件中看到来自各个窗口的消息。
我什至可以截取 WM_IME_COMPOSITION 消息 (GCS_RESULTSTR) 中的 IME Unicode 字符串,但仅限于某些应用程序,例如 .NET 编写的 64 位表单。
对于其他一些应用程序,例如 Firefox 和 Microsoft Edge,我只能看到一些 WM_IME_NOTIFY 消息,并且这些窗口没有收到任何 WM_IME_COMPOSITION/WM_IME_STARTCOMPOSITION/WM_IME_ENDCOMPOSITION 消息。因此,我无法获得这些窗口的最终 Unicode 输入字符串。
我做错什么了吗?是否甚至可以在 Windows 10 中获取所有进程或窗口(例如 64 位 Edge/Chrome/Firefox)的此类信息?
解决方案
推荐阅读
- google-cloud-storage - 从 Kubernetes pod 直接将数据写入 Google Cloud Storage
- c# - 从存储过程中按名称读取返回参数
- oracle-jet - 如何在 rowExpander 点击时加载动态数据?
- c++ - 如何在 C++ 中将字符变量添加到字符数组
- ruby-on-rails - ruby on rails simple_format 添加
到粗体和斜体列表项 - c# - 失去焦点时WPF列表框选择不灰显
- php - 多对多关系库存发票
- sql-server - X & Y 更新场景
- iot - 如何关闭 Apache Edgent 后台任务?
- javascript - 隐藏选定的第一个孩子,第二个孩子标签