首页
社区
课程
招聘
[原创][原创]利用输入法在文本框输入文本
发表于: 2013-5-9 11:30 14894

[原创][原创]利用输入法在文本框输入文本

2013-5-9 11:30
14894

一般我们想用程序在某个编辑框里输入文本,通常都会采用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了,不出意外,游戏中的对话框应该有出现字符串了


[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

收藏
免费 6
支持
分享
最新回复 (12)
雪    币: 137
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
学习了 不错
2013-5-9 13:12
0
雪    币: 22
活跃值: (453)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
能提供下完整代码就好了。。有点没看懂 。。
2013-5-9 13:35
0
雪    币: 66
活跃值: (203)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
4
好主意,和 ime inject 一样,合法入侵
2013-5-9 22:21
0
雪    币: 218
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
同求源码
2013-5-12 03:00
0
雪    币: 639
活跃值: (1192)
能力值: ( LV2,RANK:15 )
在线值:
发帖
回帖
粉丝
6
同求源码
2013-5-17 19:09
0
雪    币: 2882
活跃值: (1279)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
yjd
7
支持,用来用来对付自绘软件的窗口应该不错。
2013-5-17 19:30
0
雪    币: 34
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
8
有点没看懂的说
2013-6-4 10:55
0
雪    币: 244
活跃值: (63)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
9
MARKing  看看
2013-7-4 13:28
0
雪    币: 326
活跃值: (41)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
10
perfect
2013-7-5 10:27
0
雪    币: 1042
活跃值: (500)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
11
MAKE~~~~~
2013-7-7 10:15
0
雪    币: 40
活跃值: (25)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
12
对QQ框什么的 不管用的·
2013-7-12 09:55
0
雪    币: 1085
活跃值: (114)
能力值: ( LV8,RANK:120 )
在线值:
发帖
回帖
粉丝
13
思路不错~~~~~
2013-7-16 12:20
0
游客
登录 | 注册 方可回帖
返回
//