通过写dll方式注入全局键盘钩子,截获输入法语音识别内容(新手贴)

一、需求

    最近要搞一个获取输入法语音识别内容的功能(C#程序上),比如在讯飞上,语音识别到后并输出相当于按了Ctrl+V将文字输出到光标位置,完成语音识别。最初的想法是在窗体上新建一个文本框,输入后再根据文本框的属性去获取,之后发现存在很多问题,比如光标移动了、程序不在最上层、被小化等不确定因素太多;故在了解到全局钩子后,即着手进行。

二、实现

    刚开始对钩子一窍不通,仅收到老大发来的一个参考文档:https://blog.csdn.net/itcastcpp/article/details/7645280,然后去了解并着手实现。在了解了关键的几个方法后,开始去做。

    首先,文档是C++的,因为对各种限制不了解,由于前两周刚了解过WIN32 API尝试用C#来实现。经过创建钩子,编写回调函数(对Ctrl和V键进行拦截并处理)后发现:只能在当前程序(线程)中实现拦截,即使SetWindowsHookEx第三个参数模块句柄设置为

GetModuleHandle("user32"),查找资料后得出原因:在.Net4.0和Win8之前的版本中,CLR不再模拟托管程序集中的非托管句柄,参考:https://blog.csdn.net/catshitone/article/details/77712204?locationNum=9&fps=1。说实话主要原因并不是很清楚,然后再综合其他资料得出结论:用C#写的钩子是实现不了监控全局鼠标键盘,此时已耗时一周,果断扭头考虑其他实现方式。

    想起来刚开始老大发的文档,于是在CSDN里搜各种键盘钩子的实现,了解到需要用C++写成dll(安装钩子、回调拦截、卸载钩子等的方法都写在dll里)并在自己的C#(或其他语言写的)程序里加载后才能实现。对C++基本忘了干净,又去了解C++语法,然后参考其他文档,解决了各种遇到的编译问题,主要是由于第一次在本地电脑上用VS2013写C++的项目,关于各种项目配置也算是重新学习了一遍,之后又在调试dll遇上问题,在相继解决了不能加断点、转换数据类型、打印log等上边遇到问题,总之是经历万难,算是生成了需要的dll了。

    最后进入了漫长的调试之路,加载dll、运行所需函数,发现还是只能在程序的主窗体内才能拦截,几乎快要崩溃了;柳暗花明地又去翻阅前辈们的资料,发现是在安装钩子是第一个函数要是WH_KEYBOARD_LL(13)不能是WH_KEYBOARD(2)(虽然之前也意识到这里,但是一直用的是2,能够在线程中拦截,用13根本拦截不到),而修改后仍拦截不到,是因为大多数的资料里都缺少的一部分,无限循环去获取消息,很重要的一部分却总是未出现在各种所谓的文档中,加上这一部分后,成功运行,不管C#程序处于什么状态,在哪个界面输入,总能拦截到键盘消息,此时又已是一周零两天过去了。这也是写本文的最大目的,希望之后再遇到这种问题的同学可以少走弯路。

三、源码

KeyboardHook.cpp

// KeyboardHook.cpp : 定义 DLL 应用程序的导出函数。
//

#include <afx.h>
#include "KeyboardHook.h"
//#include "windows.h"
#include "imm.h"
#include "stdio.h"
#include<fstream>
#include<string.h>
#include <sstream>
#include <iostream>
#include <stdint.h>
#include <time.h>
using namespace std;
//#include "stdafx.h"
//#include <process.h>


//#define HOOK_API __declspec(dllexport) 

HHOOK        g_hHook = NULL;        //hook句柄
HINSTANCE  g_hHinstance = NULL;        //程序句柄
HWND         LastFocusWnd = 0;//上一次句柄,必须使全局的
HWND         FocusWnd;         //当前窗口句柄,必须使全局的  
static int CtrlV[4] = { 0, 0, 0, 0 };
static int CTRLV[2] = { 0, 0};
static bool ToGet = false;
static int GetTime = 0;
char title[256];              //获得窗口名字 
char *ftemp;                //begin/end 写到文件里面
char temptitle[256] = "<<标题:";  //<<标题:窗口名字>>
char t[2] = { 0, 0 };              //捕获单个字母
#define WM_USER_MSG WM_USER + 1001
typedef struct {
	DWORD     vkCode;
	DWORD     scanCode;
	DWORD     flags;
	DWORD     time;
	ULONG_PTR dwExtraInfo;
}_KBDLLHOOKSTRUCT, *_PKBDLLHOOKSTRUCT;
char* getTime()
 {    
	time_t timep;
    time(&timep);
    char tmp[64];
    strftime(tmp, sizeof(tmp), "%Y-%m-%d %H:%M:%S", localtime(&timep));
    return tmp;
 }
void writefile(char* lpstr)
{//保存为文件

	/*ofstream file;
	file.open("D:\\hooktxt.txt");
	cout << lpstr << endl;
	file.close();*/	
	std::ofstream out("D:\\hooktxt.txt", std::ios::app);
	//string s="";
	//s = ;
	//char* c;
	//const int len = s.length();
	//c = new char[len + 1];
	//strcpy(c, s.c_str());
	//const char* c = s.data();
	out <<  lpstr << std::endl;
	out.close();		
	/*FILE* f1;
	char cmd[256];
	GetSystemDirectory(cmd, 256);
	strcat(cmd, "D:\\hooktxt.txt");
	f1 = fopen(cmd, "a+");
	fwrite(lpstr, strlen(lpstr), 1, f1);
	fclose(f1);*/
}

LRESULT CALLBACK MessageProc(int nCode, WPARAM wParam, LPARAM lParam)
{
	if (nCode >= HC_ACTION && wParam == WM_KEYDOWN) //有键按下
	{
		DWORD vk_code = ((_KBDLLHOOKSTRUCT*)lParam)->vkCode;
		switch (vk_code)      //按键信息,CTRL为162,V为86
		{
			case 162://CTRL
				//EmptyClipboard();//清空剪贴板 
				if (CTRLV[0] == 0)
				{
					//writefile("Ctrl↓");
					int CTRLV2[2] = { 162, 0 };
					memcpy(CTRLV, CTRLV2, sizeof(CTRLV2));					
					//CTRLV[0] == 162;
					ToGet = false;
				}
				else
				{
					ToGet = false;
				}
				break;
			case 86://V				
				if (CTRLV[0] == 162 && CTRLV[1] == 0)
				{
					
					//writefile("V↓");
					int CTRLV2[2] = { 162, 86 };
					//V键被按下,动作第二步					
					memcpy(CTRLV, CTRLV2, sizeof(CTRLV2));					     
					GetTime+=1;
					ClearCtrlV();
					ToGet = true;
				}
				else{
					ToGet = false;
				}
				break;			
		}		
	}
	return CallNextHookEx(g_hHook, nCode, wParam, lParam);  //将消息还给钩子链,不要影响别人
}

bool RetToGet()
{
	return ToGet;
}
int RetGetTime()
{
	return GetTime;
}
void ClearCtrlV()
{
	int CTRLV2[2] = { 0, 0 };
	memcpy(CTRLV, CTRLV2, sizeof(CTRLV2));
	//for (int i = 0; i < 4; i++)
	//	CtrlV[i] = 0;            //清空存入的值
}
void ClearCTRLV()
{
	for (int i = 0; i < 2; i++)
		CtrlV[i] = 0;            //清空存入的值
}
WINUSERAPI
HANDLE
WINAPI
GetClipboardData(
__in UINT uFormat);
char* PrintClip()
{
	/*if (::OpenClipboard(NULL) && ::IsClipboardFormatAvailable(CF_HDROP))
	{
		HDROP hDrop = (HDROP)::GetClipboardData(CF_HDROP);
		if (hDrop != NULL)
		{
			
		}
	}*/
	if (!IsClipboardFormatAvailable(CF_TEXT))
	{
		return "1";
	}
	if (!OpenClipboard(NULL))
	{
		return "2";
	}
	//TCHAR strText[256] = "";
	
	// 分配全局内存  
	//hMem = GlobalAlloc(GMEM_MOVEABLE, ((strlen(strText) + 1)*sizeof(TCHAR)));
	//获取UNICODE的数据。
	HGLOBAL hMem = GetClipboardData(CF_TEXT);
	if (hMem != NULL)
	{
		
		// 锁住内存区
		LPTSTR lpStr = (LPTSTR)GlobalLock(hMem);
		// 内存复制   
		//memcpy(lpStr, strText, ((strlen(strText))*sizeof(TCHAR)));
		// 字符结束符    
		//lpStr[strlen(strText)] = (TCHAR)0;

		if (lpStr != NULL)
		{
			writefile(lpStr);
			GlobalUnlock(hMem);
		}
	}
	//SetClipboardData(CF_TEXT, hMem);
	CloseClipboard();

	//
	//HGLOBAL  hClip;
	//char* pBuf="";	
	读取数据  
	//hClip = GetClipboardData(CF_TEXT);
	//writefile(hClip);
	//if (NULL != hClip)
	//{
	//	char* lpStr = (char*)::GlobalLock(hClip);
	//	writefile(lpStr);
	//	if (NULL != lpStr)
	//	{
	//		//MessageBox(0, lpStr, "", 0);
	//		::GlobalUnlock(hClip);
	//	}
	//}	
	//::CloseClipboard();    
	//return pBuf;
}
//HOOK_API BOOL InstallHook()
void InstallHook()
{
	GetTime = 0;
	ToGet = false;
	ClearCtrlV();
	//DWORD dwThreadId:线程标识符,该参数表示与子程相关联的线程ID。通常情况下该参数写0时为全局钩子。 //GetModuleHandle(TEXT("KeyboardHook.dll"))    GetModuleHandle(TEXT("user32.dll"))
	g_hHook = SetWindowsHookEx(WH_KEYBOARD_LL, (HOOKPROC)MessageProc, GetModuleHandle(TEXT("KeyboardHook.dll")), 0); //可以实现线程内
	//g_hHook = SetWindowsHookEx(WH_KEYBOARD_LL, (HOOKPROC)MessageProc, g_hHinstance, NULL);
	
	//char *s = "g_hHook installed";
	//writefile(s);
	//return TRUE;
}


void main()
{
	InstallHook();
	/*MSG msg;
	while (GetMessage(&msg, NULL, 0, 0))
	{
		TranslateMessage(&msg);
		DispatchMessage(&msg);
		writefile("收到消息");
	}*/
	MSG msg;
	while (1)
	{
		if (PeekMessageA(&msg, 0, 0, 0, PM_REMOVE))
		{
			TranslateMessage(&msg);
			DispatchMessageW(&msg);			
			//writefile("111");
		}
		else
			//writefile("~~");
			Sleep(0);    //避免CPU全负载运行
	}
	UnHook();
}
//HOOK_API BOOL UnHook()
BOOL UnHook()
{
	writefile("卸载");
	ClearCtrlV();
	return UnhookWindowsHookEx(g_hHook);
}

BOOL APIENTRY DllMain(HANDLE hModule,
	DWORD  ul_reason_for_call,
	LPVOID lpReserved
	)
{
	switch (ul_reason_for_call)
	{
	case DLL_PROCESS_ATTACH:
		//char szBuff[MAX_PATH] = { 0 };
		//memset(szBuff, 0, sizeof(szBuff));
		 取得当前exe的路径
		//GetModuleFileName(NULL, szBuff, sizeof(szBuff));		
		//g_hHinstance = HINSTANCE(hModule);			
		//g_hHinstance = GetModuleHandle(NULL);
		//g_hHinstance = GetModuleHandle(TEXT("KeyboardHook.dll"));		;
		break;
	case DLL_THREAD_ATTACH:		
		break;
	case DLL_THREAD_DETACH:
		break;
	case DLL_PROCESS_DETACH:
		UnHook();
		break;
	}
	return TRUE;
}

KeyboardHook.def

LIBRARY "KeyboardHook"
EXPORTS
writefile @ 1
writefile @ 2
InstallHook @ 3
UnHook @ 4
PrintClip @ 5
ClearCtrlV @ 6
RetToGet @ 7
RetGetTime @ 8
main @ 9
ClearCTRLV @ 10

KeyboardHook.h

#ifndef KEYBOARDHOOK_H//作用:防止graphics.h被重复引用
#define KEYBOARDHOOK_H
extern "C" _declspec(dllexport) void writefile(char *lpstr);
extern "C" _declspec(dllexport) void writtitle();
extern "C" _declspec(dllexport) void InstallHook();
extern "C" _declspec(dllexport) BOOL UnHook();
extern "C" _declspec(dllexport) char* PrintClip();
extern "C" _declspec(dllexport) void ClearCtrlV();
extern "C" _declspec(dllexport) bool RetToGet();
extern "C" _declspec(dllexport) int RetGetTime();
extern "C" _declspec(dllexport) void main();
extern "C" _declspec(dllexport) void ClearCTRLV();

//extern "C" _declspec(dllexport) LRESULT CALLBACK MessageProc(int nCode, WPARAM wParam, LPARAM lParam);
#endif

C#程序里

hook.cs

sing System;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
using System.Windows.Forms;

namespace MayaCurrent.Tools
{
    class HOOK
    {
        private delegate int KeyboardProc(int nCode, IntPtr wParam, IntPtr lParam);

        static int hKeyboardHook = 0; //如果hKeyboardHook==0,钩子安装失败
        KeyboardProc KeyboardHookProcedure;

        /// <summary>
        /// 钩子函数,需要引用空间(using System.Reflection;)
        /// 线程钩子监听键盘消息设为2,全局钩子监听键盘消息设为13
        /// 线程钩子监听鼠标消息设为7,全局钩子监听鼠标消息设为14
        /// </summary>

        public const int WH_KEYBOARD = 13;
        public const int WH_MOUSE_LL = 14;

        public struct KeyboardMSG
        {
            public int vkCode;        //keyValue
            public int scanCode;
            public int flags;
            public int time;
            public int dwExtraInfo;
            public int VK_CONTROL;
            public int VK_MENU;
            public int VK_DELETE;
        }
        
        [DllImport("KeyboardHook.dll", EntryPoint = "InstallHook")]
        public static extern void InstallHook();
        [DllImport("KeyboardHook.dll")]
        public static extern bool UnHook();
        [DllImport("KeyboardHook.dll", EntryPoint = "main")]
        public static extern bool main();
        [DllImport("KeyboardHook.dll")]
        public static extern char[] PrintClip();
        
        [DllImport("KeyboardHook.dll")]
        public static extern void writefile(char[] s);
        [DllImport("KeyboardHook.dll")]
        public static extern void writtitle();

        [DllImport("KeyboardHook.dll")]
        public static extern bool RetToGet();
        [DllImport("KeyboardHook.dll")]
        public static extern int RetGetTime();
        [DllImport("kernel32.dll")]
        static extern IntPtr LoadLibrary(string lpFileName);
        /// 原型是 : FARPROC GetProcAddress(HMODULE hModule, LPCWSTR lpProcName);
        /// <param name="hModule"> 包含需调用函数的函数库模块的句柄 </param>
        /// <param name="lpProcName"> 调用函数的名称 </param>
        /// <returns> 函数指针 </returns>
        [DllImport("kernel32.dll")]
        static extern IntPtr GetProcAddress(IntPtr hModule, string lpProcName);
        /// 原型是 : BOOL FreeLibrary(HMODULE hModule);
        /// <param name="hModule"> 需释放的函数库模块的句柄 </param>
        /// <returns> 是否已释放指定的 Dll</returns>
        [DllImport("kernel32", EntryPoint = "FreeLibrary", SetLastError = true)]
        static extern bool FreeLibrary(IntPtr hModule);
        /// Loadlibrary 返回的函数库模块的句柄
        private static IntPtr hModule = IntPtr.Zero;
        /// GetProcAddress 返回的函数指针
        private static IntPtr farProc = IntPtr.Zero;        
        ///2018-7-3 14:51:11
        
        /// <summary>
        /// 装载 Dll
        /// </summary>
        /// <param name="lpFileName">DLL 文件名 </param>
        public void LoadDll(string lpFileName)
        {
            hModule = LoadLibrary(lpFileName);
            if (hModule == IntPtr.Zero)
                throw (new Exception(" 没有找到 :" + lpFileName + "."));
        }



        /// 获得函数指针
        /// <param name="lpProcName"> 调用函数的名称 </param>
        public void LoadFun(string lpProcName)
        { // 若函数库模块的句柄为空,则抛出异常
            if (hModule == IntPtr.Zero)
                throw (new Exception(" 函数库模块的句柄为空 , 请确保已进行 LoadDll 操作 !"));
            // 取得函数指针
            farProc = GetProcAddress(hModule, lpProcName);
            // 若函数指针,则抛出异常
            if (farProc == IntPtr.Zero)
                throw (new Exception(" 没有找到 :" + lpProcName + " 这个函数的入口点 "));
        }
        /// <summary>
        /// 获得函数指针
        /// </summary>
        /// <param name="lpFileName"> 包含需调用函数的 DLL 文件名 </param>
        /// <param name="lpProcName"> 调用函数的名称 </param>
        public void LoadFun(string lpFileName, string lpProcName)
        { // 取得函数库模块的句柄
            hModule = LoadLibrary(lpFileName);
            // 若函数库模块的句柄为空,则抛出异常
             if (hModule == IntPtr.Zero)
                 LOGGER.message("没有找到 :" + lpFileName);
            // 取得函数指针
            farProc = GetProcAddress(hModule, lpProcName);
            // 若函数指针,则抛出异常
            if (farProc == IntPtr.Zero)
                LOGGER.message(" 没有找到 :" + lpProcName + " 这个函数的入口点 ");
        }

        /// <summary>
        /// 卸载 Dll
        /// </summary>
        public void UnLoadDll()
        {
            LoadFun("KeyboardHook.dll", "UnHook");      //获取UnHook(入口)地址
            UnHook();
            //FreeLibrary(hModule);
            hModule = IntPtr.Zero;
            farProc = IntPtr.Zero;
        }
        
        // 安装钩子
        public void KeyboardStart()
        {
            LoadFun("KeyboardHook.dll", "main");      //获取InstallHook(入口)地址              
            main();
            

        }
        //获得返回
        public bool ToRetOrNot()
        {
            LoadFun("KeyboardHook.dll", "RetToGet");      //获取RetToGet(入口)地址              
            return RetToGet();
        }

        public int ToRetGetTime()
        {
            LoadFun("KeyboardHook.dll", "RetGetTime");      //获取RetToGet(入口)地址              
            return RetGetTime();
        }

         public string  GetClipboard()
        {
            LoadFun("KeyboardHook.dll", "PrintClip");             
            try
            {

                char[] s = new char[1024] ;
                    s=PrintClip();                
            }
            catch(Exception e)
            {
                LOGGER.info(e.ToString());
                return "";
            }
            return "OK";
        }
        // 卸载钩子
        public void KeyboardStop()
        {
            //UnHook();
        }
}

具体实现上,是开始安装钩子并无限循环获取,然后在计时器里根据状态获取剪切板内容并返回。

CAction.cs

       public static string VoiceBePrint = "";
       public static int BlockTimes = 0;
       public static bool toget = false;
       public static bool ToListen = false;
       public static void VoiceHasReg()
       {
            IDataObject iData = Clipboard.GetDataObject();
            if (iData.GetDataPresent(DataFormats.Text))
            {
                string nowtext = (string)iData.GetData(DataFormats.Text);
                CAction.VoiceBePrint +=nowtext;
                //MessageBox.Show(nowtext);
                LOGGER.message("第" + BlockTimes + "次拦截到剪切板内(语音识别)结果为:" + nowtext);
                //LOGGER.message("第" + BlockTimes + "次结果为:" + VoiceBePrint);
            }           
       }

       public static bool HasReg()
       {
           HOOK hook = new HOOK();
           toget = hook.ToRetOrNot();
           return toget;
       }
       public static int RegTime()
       {
           HOOK hook = new HOOK();
           BlockTimes = hook.ToRetGetTime();
           return BlockTimes;
       }

计时器:

程序主窗体.cs

        public static int CheckCtrlV= 0;
        public static bool CtrlvAdd = true;
        public static int OnlyOne = 0;

        private void timerVoiceBlockedDll_Tick(object sender, EventArgs e)
        {
            if (CAction.ToListen)
            {
                try
                {
                    
                    bool bl=CAction.HasReg();
                    if (OnlyOne < 1 &&bl)//监测到dll中的一个CTRL+V按完,则一直为true
                    {
                        int Int=CAction.RegTime();                        
                        if ( Int>CheckCtrlV)
                        {
                            CheckCtrlV = Int;                           
                            OnlyOne =1;
                            CAction.VoiceHasReg();
                        }
                    }
                    else if (!bl)
                    {
                        OnlyOne = 0;
                        CAction.VoiceBePrint = ""; //清空
                    }
                    else
                    {                       
                        OnlyOne = 0;
                        //CAction.VoiceBePrint = ""; //清空                     
                    }
                }
                catch(Exception){
                }                                
            }
            else
            {
                OnlyOne = 0;
                //CAction.VoiceBePrint = ""; //清空
                
            }
        }

 

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值