我试图实现一个自动化工具,在拦截IME (Windows 10的默认Microsoft )输入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字符串,但只适用于某些应用程序,比如由.NET编写的64位表单。
对于其他一些应用程序,如Firefox和Microsoft,我只能看到一些WM_IME_NOTIFY消息,并且这些窗口没有收到任何WM_IME_NOTIFY消息。因此,我无法获得这些窗口的最终Unicode输入字符串。
我做错什么了吗?对于windows 10中的所有进程或窗口(如64位Edge/Chrome/Firefox),是否有可能获得这样的信息?
发布于 2022-08-02 09:26:25
我还尝试了您在这个问题中描述的方法,但是未能捕获windows IME输出Unicode string(Chinese)。
中没有WM_IME_COMPOSITION或WM_CHAR这样的消息。
但是当我使用记事本时,仍然可以通过钩子WH_CALLWNDPROC获得中文的字符串。
https://stackoverflow.com/questions/69474343
复制相似问题