一般我们想用程序在某个编辑框里输入文本,通常都会采用SetWindowText或者windows消息来实现,但在很多情况下,目标进程的窗口句柄无法得到,或者目标进程屏蔽了windows消息,又或者窗口是directx绘制出来的,这时候这两种手段就无力处理了。
那么有啥方法可以破解这种情况呢?
这时候输入法就派上用场了,我想应该没有程序会不支持输入法吧。。。。。
利用输入法输入文本这功能其实网上很早就有了(例如大漠插件的输入法输入),只不过一直没什么具体的文章介绍,小弟不才,这里就不说什么原理之类的废话了,直接说下具体怎么实现。
首先,我们需要自己写个输入法程序,具体怎么写不是本文的重点,各位上网搜索吧。由于我们只是要用输入法输入文本,所有我们具体要实现的接口为ImeInquire、ImeProcessKey和ImeToAsciiEx,其他的接口直接返回就可以了。
下面介绍下这三个接口具体要做些什么事。ImeInquire
这其实是个初始化函数,我们在这个函数里面,要将一些基本信息初始化
BOOL WINAPI ImeInquire(LPIMEINFO lpIMEInfo,LPTSTR lpszUIClass,LPCTSTR lpszOption){
lpIMEInfo->dwPrivateDataSize = 0;//系统根据它为INPUTCONTEXT.hPrivate分配空间
lpIMEInfo->fdwProperty = IME_PROP_KBD_CHAR_FIRST |
IME_PROP_UNICODE |
IME_PROP_IGNORE_UPKEYS |
IME_PROP_END_UNLOAD ;
lpIMEInfo->fdwConversionCaps = IME_CMODE_FULLSHAPE |
IME_CMODE_NATIVE;
lpIMEInfo->fdwSentenceCaps = IME_SMODE_NONE;
lpIMEInfo->fdwUICaps = UI_CAP_2700;
lpIMEInfo->fdwSCSCaps = 0;
lpIMEInfo->fdwSelectCaps = SELECT_CAP_CONVERSION;
TCHAR* CLSNAME_UI = TEXT("MYCLASS");
WNDCLASSEX wc;
//
// register class of UI window.
//
wc.cbSize = sizeof(WNDCLASSEX);
wc.style = CS_INPUTSTAR | CS_IME;
wc.lpfnWndProc = UIWndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 2 * sizeof(LONG);
wc.hInstance = hInstance;
wc.hCursor = LoadCursor( NULL, IDC_ARROW );
wc.hIcon = NULL;
wc.lpszMenuName = (LPTSTR)NULL;
wc.lpszClassName = CLSNAME_UI;
wc.hbrBackground = NULL;
wc.hIconSm = NULL;
if( !RegisterClassEx( (LPWNDCLASSEX)&wc ) )
return FALSE;
_tcscpy_s(lpszUIClass,MAX_PATH,CLSNAME_UI);
return TRUE;
}
如上所示,这个函数的第一个参数为输入法的基本信息的一个结构体,在这个函数里初始化这个结构体的各字段,具体字段的意思请上网搜索这里不介绍。我们这里要关注的是下半部分。这里要向系统注册一个窗口class,让后将这个class的名字复制给函数的第二个参数,这样,输入法在激活后,系统会用这个class创建一个输入法窗口,我们可以在这个窗口的回调函数(我们在class中定义的,下面会介绍)中接受和处理各种输入法消息,对于我们来说,我们可以定义一些自己的消息,来控制输入法做各种事情。ImeProcessKey
BOOL WINAPI ImeProcessKey(HIMC hIMC,UINT uKey,LPARAM lKeyData,CONST LPBYTE lpbKeyState){
return false;
}
这个函数主要是用来处理系统按键消息的,第二个参数指明了那个按键被按下或弹起,这里我们用不到,所以直接返回false,也就是告诉系统所有的按键消息我都不处理。ImeToAsciiEx
UINT WINAPI ImeToAsciiEx (UINT uVKey,UINT uScanCode,CONST LPBYTE lpbKeyState,LPDWORD lpdwTransKey,UINT fuState,HIMC hIMC){
LPINPUTCONTEXT lpIMC = (LPINPUTCONTEXT)ImmLockIMC(hIMC);
UINT uMsgCount=0;
//WM_IME_COMPOSITION消息和GCS_RESULTSTR|GCS_COMPSTR标志位告诉输入法已完成字符串的构造,现在该输出结果了
MyGenerateMessageToTransKey(lpdwTransKey,&uMsgCount,WM_IME_COMPOSITION,0,GCS_RESULTSTR|GCS_COMPSTR);
ImmUnlockIMCC(lpIMC->hCompStr);
ImmUnlockIMC(hIMC);
return uMsgCount;
} BOOL MyGenerateMessageToTransKey(LPDWORD lpdwTransKey,
UINT *uNumTranMsgs,
UINT msg,
WPARAM wParam,
LPARAM lParam)
{
LPTRANSMSG lpTransMsg;
if (((*uNumTranMsgs) + 1) >= (UINT)*lpdwTransKey)
return FALSE;
lpTransMsg = (LPTRANSMSG)(lpdwTransKey+1+(*uNumTranMsgs)*3);
lpTransMsg->message=msg;
lpTransMsg->wParam=wParam;
lpTransMsg->lParam=lParam;
(*uNumTranMsgs)++;
return TRUE;
}
这个函数是最重要的函数,输入法将按键转换为对应的文本就在这个函数实现。从一般的流程来说,这个函数式取决于上面ImeProcessKey这个函数的返回值,只有当ImeProcessKey返回TRUE时,系统接下来才会调用到它,由于我们的ImeProcessKey总是返回false,所以系统永远不会调用到它,也就是说我们的输入法激活时,不会影响我们正常使用键盘。
ImeToAsciiEx 中有个MyGenerateMessageToTransKey函数,这是我们自己定义个一个函数,主要功能就是构造输入法消息。
好了,三个输入法接口我们都实现好了,接下来就是怎么在程序里调用它输入文本了。
首先找个一个我们要输入文本的进程,随便找个游戏来试下吧。
游戏里切换到我们的输入法,然后调出对话输入框,接着程序中向输入法窗口发送我们自己的WM_XXXX消息,还记得我们第一个接口定义的窗口类吗,里面指定了一个窗口回调函数,这个回调函数当然也是我们自己实现的,我们在这个窗口回调函数里可以实现自己逻辑。
#define GETKEYINFO(lpIMC) (PKEYINFO)((DWORD)lpIMC + sizeof(INPUTCONTEXT))
typedef struct tagKEYINFO{
DWORD uKey;
BOOL isProcess;
}KEYINFO,*PKEYINFO;
LRESULT WINAPI UIWndProc(HWND hWnd, UINT message,WPARAM wParam, LPARAM lParam){
LONG lRet = 0L;
switch(message)
{
case WM_CREATE:
uiHwnd = hWnd;
SetWindowPos(hWnd, NULL, 0, 0, 0, 0, SWP_NOACTIVATE|SWP_NOZORDER);
ShowWindow(hWnd, SW_SHOWNOACTIVATE);
break;
case WM_DESTROY:
uiHwnd = 0;
break;
case WM_IME_STARTCOMPOSITION:
case WM_IME_ENDCOMPOSITION:
case WM_IME_COMPOSITION:
case WM_IME_SETCONTEXT:
case WM_IME_CONTROL:
case WM_IME_COMPOSITIONFULL:
case WM_IME_SELECT:
case WM_IME_CHAR:
case WM_IME_NOTIFY:
case WM_PAINT:
break;
case WM_XXXX:
SendSomething(hWnd);
break;
case WM_MOUSEACTIVATE:
return MA_NOACTIVATE;
default:
return DefWindowProc(hWnd,message,wParam,lParam);
}
return lRet;
}
void SendSomething(HWND hWnd){
HIMC hIMC = (HIMC)GetWindowLong(hWnd,IMMGWL_IMC);
if (!hIMC){
return;
}
LPWSTR lpData = L"Hello World!";
int cbData = wcslen(lpData );
LPINPUTCONTEXT lpIMC = (LPINPUTCONTEXT)ImmLockIMC(hIMC);
if(lpIMC){
LPCOMPOSITIONSTRING lpCompStr=(LPCOMPOSITIONSTRING)ImmLockIMCC(lpIMC->hCompStr);
LPWSTR pText = (LPWSTR)lpData;
LPWSTR pResult = (LPWSTR)((LPBYTE)(lpCompStr) + (lpCompStr)->dwResultStrOffset);
ZeroMemory(pResult,lpCompStr->dwResultStrLen*sizeof(WCHAR));
CopyMemory(pResult,pText,cbData);
lpCompStr->dwResultStrLen=wcslen(pResult);
lpCompStr->dwCompStrLen=0;
ImmUnlockIMCC(lpIMC->hCompStr);
PKEYINFO ki = GETKEYINFO(lpIMC);
ki->uKey = VK_HOME;
ki->isProcess = TRUE;
PostMessage(lpIMC->hWnd, WM_KEYDOWN,VK_PROCESSKEY,0x01470001);
ImmUnlockIMC(hIMC);
}
}
在窗口回调函数里,我们自己定义了个消息WM_XXXX,只要有程序向我们的输入法窗口发送这个消息,输入法便输入"Hello World",在具体的发送函数SendSomething中,我们将"Hello World"字符串复制到输入法上下文的结果字符串buffer中,如红色字体CopyMemory(pResult,pText,cbData)所示。然后我们要告诉系统,现在需要输入法处理我们的字符串,我们可以看到上面红色字体的ki->uKey 和ki->isProcess这两字段,其中ki->uKey 的值会当做第一个参数传给ImeToAsciiEx,这里填不填都无所谓,因为我们在ImeToAsciiEx中都没有处理第一个参数,而ki->isProcess必须填TRUE,否则系统不会调用ImeToAsciiEx,好了这里我们可能会想起ImeProcessKey这个函数,是的,正常流程中,ImeProcessKey的返回值会赋值给ki->isProcess,这里我们没有经过ImeProcessKey,而是直接让系统调用ImeToAsciiEx,所以必须手工设置这个值。最后,向系统发送PostMessage(lpIMC->hWnd, WM_KEYDOWN,VK_PROCESSKEY,0x01470001)消息,系统接受到消息后就会直接调用ImeToAsciiEx函数了,其中第二个参数会反映到ImeToAsciiEx的uScanCode参数上,WM_KEYDOWN也可以改为WM_KEYUP,但是第三个参数一定要是VK_PROCESSKEY,第四个参数我是直接从spy++抄过来,具体反映到ImeToAsciiEx的哪个参数网友们自己研究吧。
现在一切都OK了,不出意外,游戏中的对话框应该有出现字符串了
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!