基于TSF框架的输入法注册流程分析

原文链接

输入法注册

基于TSF框架的输入法其实本质上就是一个COM程序,通过regsvr32.exe进行注册,注册的命令如下所示.

regsvr32.exe  sogou.ime //注册输入法
regsvr32.exe /u sogou.ime //取消注册输入法

1.调用对应的注册回调函数

这里解析一下输入法COM程序的注册流程。在注册输入法的时候,会调用对应的输入法动态库里面的DllRegisterServer()函数,在卸载输入法的时候会调用 DllUnregisterServer()

所以输入法动态库需要导出对应的接口函数

//通过RAII方式初始化
class AutoCOM
{
public:
	BOOL bInit;
	//初始化Com对应的库
     AutoCOM() {
		bInit = FALSE;    
		HRESULT hr = CoInitialize(NULL);
		if (FAILED(hr))
			return;
		bInit = TRUE;
    	}
     //卸载对应的Com库
    ~AutoCOM() {
		if (bInit)
			CoUninitialize();
    	}
};
//Com组件注册的时候会被调用
STDAPI DllRegisterServer(void)
{
	AutoCOM ac;
    if ((!RegisterTSFServer()) //注册表里面注册输入法的信息,注册之后才能调用
    || (!RegisterProfiles()) 
    || (!RegisterCategories()))
    {
        DllUnregisterServer();
        return E_FAIL;
    }
	InstallLayout();
    return S_OK;
}

//Com组件被卸载的时候调用
STDAPI DllUnregisterServer(void)
{
	AutoCOM ac;
	UnregisterProfiles();
	UnregisterCategories();
	UnregisterTSFServer();
	UninstallLayout();
	return S_OK;
}

2.注册输入法组件的服务端

RegisterTSFServer()注册输入法的时候,会将对应的输入法的CLSID值,输入法的名称以及输入法Com组件对应的文件地址和线程模型写入到注册表中,效果如下图所示。

注册表地址如下所示:

计算机\HKEY_LOCAL_MACHINE\SOFTWARE\Classes\WOW6432Node\CLSID\{1572C5E0-7FCA-43E5-856D-0870D9641436}\InProcServer32

UnregisterTSFServer();的时候会将对应的输入法的注册进行删除,从而取消输入法服务器的注册。

BOOL RegisterTSFServer()
{
    DWORD copiedStringLen = 0;
    HKEY regKeyHandle = nullptr;
    HKEY regSubkeyHandle = nullptr;
    BOOL ret = FALSE;
    WCHAR* ime_key_path= L"CLSID\\{1572C5E0-7FCA-43E5-856D-0870D9641436}";
    WCHAR achFileName[MAX_PATH] = {'\0'};
    //CLSID值类似于GUID用来标识唯一的一个输入法
   
    if (RegCreateKeyEx(HKEY_CLASSES_ROOT, ime_key_path, 
    0, NULL, REG_OPTION_NON_VOLATILE, KEY_WRITE, NULL,
     &regKeyHandle, &copiedStringLen) == ERROR_SUCCESS)
    {
        //注册输入法的名称
        if (RegSetValueEx(regKeyHandle, NULL, 0, REG_SZ, 
        (const BYTE *)L"大飞输入法", 
        (_countof(L"大飞输入法"))*sizeof(WCHAR)) != ERROR_SUCCESS)
        {
            goto Exit;
        }

        if (RegCreateKeyEx(regKeyHandle,L"InProcServer32", 0, 
        NULL, REG_OPTION_NON_VOLATILE, KEY_WRITE, NULL, 
        &regSubkeyHandle, &copiedStringLen) == ERROR_SUCCESS)
        {
            
            copiedStringLen = GetModuleFileNameW(
            Module::dllInstanceHandle, achFileName, ARRAYSIZE(achFileName));
            copiedStringLen = (copiedStringLen >= (MAX_PATH - 1)) ? MAX_PATH : (++copiedStringLen);
            //将模块的地址写入到注册表
            if (RegSetValueEx(regSubkeyHandle, NULL, 0, REG_SZ, (const BYTE *)achFileName, 
            (copiedStringLen)*sizeof(WCHAR)) != ERROR_SUCCESS)
            {
                goto Exit;
            }
            //将线程模型写入到注册表
            if (RegSetValueEx(regSubkeyHandle,  L"ThreadingModel", 0, REG_SZ, 
            (const BYTE *)TEXTSERVICE_MODEL, 
            (_countof(TEXTSERVICE_MODEL)) * sizeof(WCHAR)) != ERROR_SUCCESS)
            {
                goto Exit;
            }

            ret = TRUE;
        }
    }

Exit:
    if (regSubkeyHandle)
    {
        RegCloseKey(regSubkeyHandle);
        regSubkeyHandle = nullptr;
    }
    if (regKeyHandle)
    {
        RegCloseKey(regKeyHandle);
        regKeyHandle = nullptr;
    }
    return ret;
void UnregisterServer()
{
    HKEY achIMEKey = L"CLSID\\{1572C5E0-7FCA-43E5-856D-0870D9641436}";
    RecurseDeleteKey(HKEY_CLASSES_ROOT, achIMEKey);
}

 3.注册输入法的说明信息

注册输入法的文本服务和概述RegisterProfiles()

注册输入法的时候用到的API

HRESULT RegisterProfile(
  REFCLSID    rclsid, //文本服务的CLSID
  LANGID      langid,  //对应的输入法的语言ID一般对应简体中文
  REFGUID     guidProfile, //输入法概述的GUID
  const WCHAR *pchDesc, //输入法名称
  ULONG       cchDesc,  //输入法名称字符串的长度
  const WCHAR *pchIconFile,//输入法对应的图标文件地址
  ULONG       cchFile, //输入法图标的文件地址
  ULONG       uIconIndex, //图标对应的资源Index
  HKL         hklsubstitute, //输入法的注册表
  DWORD       dwPreferredLayout, //没有使用设置为0
  BOOL        bEnabledByDefault,//设置为true的时输入法默认开启
  DWORD       dwFlags //输入法的作用域
);

extern const GUID profile_guid = {
		0x2b5827c0,
		0x39c6,
		0x4d90,
		{ 0xb9, 0x66, 0xbe, 0x50, 0x45, 0x5e, 0x2f, 0xd }
	};
Module::CLSID//也是对应的GUID值	
	//简体中文的输入法语言ID
 #define TEXTSERVICE_LANGID       MAKELANGID(LANG_CHINESE, SUBLANG_CHINESE_SIMPLIFIED)
 #define TEXTSERVICE_ICON_INDEX   -IDIS_IME_LOGO
HKL FindIME()
{
	HKL hKL = NULL;
	WCHAR key[9];
	HKEY hKey;
	LSTATUS ret = RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"SYSTEM\\CurrentControlSet\\Control\\Keyboard Layouts", 0, KEY_READ, &hKey);
	if (ret == ERROR_SUCCESS)
	{
		for (DWORD_PTR id = (0xE0200000 | TEXTSERVICE_LANGID); hKL == NULL && id <= (0xE0FF0000 | TEXTSERVICE_LANGID); id += 0x10000)
		{
			StringCchPrintfW(key, _countof(key), L"%08X", id);
			HKEY hSubKey;
			ret = RegOpenKeyExW(hKey, key, 0, KEY_READ, &hSubKey);
			if (ret == ERROR_SUCCESS)
			{
				WCHAR data[32];
				DWORD type;
				DWORD size = sizeof data;
				ret = RegQueryValueExW(hSubKey, L"Ime File", NULL, &type, (LPBYTE)data, &size);
				if (ret == ERROR_SUCCESS && type == REG_SZ && _wcsicmp(data, L"dafei.ime") == 0)
					hKL = (HKL)id;
			}
			RegCloseKey(hSubKey);
		}
	}
	RegCloseKey(hKey);
	return hKL;
}

// 注册输入法概述
BOOL RegisterProfiles()
{
    HRESULT hr = S_FALSE;
	
	WCHAR ime_icon_path[MAX_PATH] = { '\0' }; //输入法图标地址
	DWORD cchA = 0;
	cchA = GetModuleFileName(Module::dllInstanceHandle, 
	ime_icon_path, MAX_PATH);
	cchA = cchA >= MAX_PATH ? (MAX_PATH - 1) : cchA;
	ime_icon_path[cchA] = '\0';
	//win8及win10系统的注册方式
	if (IsWindows8OrGreater())
	{
		CComPtr<ITfInputProcessorProfileMgr> pITfInputProcessorProfileMgr = nullptr;
		hr = CoCreateInstance(CLSID_TF_InputProcessorProfiles, 
		NULL, CLSCTX_INPROC_SERVER,IID_ITfInputProcessorProfileMgr, 
		(void**)&pITfInputProcessorProfileMgr);
		if (FAILED(hr))
			return FALSE;

		size_t lenOfDesc = 0;
		hr = StringCchLength(L"大飞输入法", STRSAFE_MAX_CCH, &lenOfDesc);
		hr = pITfInputProcessorProfileMgr->RegisterProfile(
                    Module::CLSID,
			TEXTSERVICE_LANGID,
			profile_guid,
			L"大飞输入法",
			static_cast<ULONG>(lenOfDesc),
			ime_icon_path, 
			cchA,
			(UINT)TEXTSERVICE_ICON_INDEX, FindIME(), 0, TRUE, 0);
	}
	else
	{
		//win7和winxp的注册方式
		CComPtr<ITfInputProcessorProfiles> pInputProcessorProfiles;
		hr = pInputProcessorProfiles.CoCreateInstance(CLSID_TF_InputProcessorProfiles, NULL, CLSCTX_INPROC_SERVER);
		if (FAILED(hr))
			return FALSE;

		hr = pInputProcessorProfiles->Register(Module::CLSID);
		if (FAILED(hr)) {
			pInputProcessorProfiles = nullptr;
			return FALSE;
		}

		hr = pInputProcessorProfiles->AddLanguageProfile(
          Module::CLSID, TEXTSERVICE_LANGID, 
          profile_guid, TEXTSERVICE_DESC, (ULONG)wcslen(TEXTSERVICE_DESC), achIconFile, cchA, TEXTSERVICE_ICON_INDEX);
		if (FAILED(hr)) {
			pInputProcessorProfiles = nullptr;
			return FALSE;
		}

		hr = pInputProcessorProfiles->SubstituteKeyboardLayout(Module::CLSID, TEXTSERVICE_LANGID, profile_guid, FindIME());
		if (FAILED(hr)) {
			pInputProcessorProfiles = nullptr;
			return FALSE;
		}
		pInputProcessorProfiles = nullptr;
	}

	return TRUE;
}

4.注册对应的类型

将开发的输入法注册到对应的注册表类别中,注册过程中使用的API如下所示

HRESULT RegisterCategory(
  REFCLSID rclsid,
  REFGUID  rcatid,
  REFGUID  rguid
);
//REFCLSID rclsid 输入法服务的CLSID值
//REFGUID  rcatid指定要注册的类别的GUID值,这个类别可以是预设的,也可以是用户自定义的
// REFGUID  rguid指定标识要注册的服务的的GUID值

注册的调用流程如下图所示,其实主要作用就是把开发的输入法注册到各个类别当中去:

extern const GUID profile_guid = {
		0x2b5827c0,
		0x39c6,
		0x4d90,
		{ 0xb9, 0x66, 0xbe, 0x50, 0x45, 0x5e, 0x2f, 0xd }
	};
static const GUID SupportCategories[] = {
    GUID_TFCAT_TIP_KEYBOARD,
    GUID_TFCAT_DISPLAYATTRIBUTEPROVIDER,
    GUID_TFCAT_TIPCAP_UIELEMENTENABLED, 
    GUID_TFCAT_TIPCAP_SECUREMODE,
    GUID_TFCAT_TIPCAP_COMLESS,
    GUID_TFCAT_TIPCAP_INPUTMODECOMPARTMENT,
    GUID_TFCAT_TIPCAP_IMMERSIVESUPPORT, 
    GUID_TFCAT_TIPCAP_SYSTRAYSUPPORT,
}; 
Module::CLSID//也是对应的GUID值可以自己定义

//将当前的输入法注册到对应的类别中
BOOL RegisterCategories()
{
    ITfCategoryMgr* pCategoryMgr = nullptr;
    HRESULT hr = S_OK;

    hr = CoCreateInstance(CLSID_TF_CategoryMgr, 
    NULL, 
    CLSCTX_INPROC_SERVER, 
    IID_ITfCategoryMgr,
     (void**)&pCategoryMgr);
    if (FAILED(hr))
        return FALSE;

    for each(GUID guid in SupportCategories)
    {
        hr = pCategoryMgr->RegisterCategory(Module::CLSID, 
        guid, 
        Module::CLSID);
    }

    pCategoryMgr->Release();

    return (hr == S_OK);
}

//从对应的类别中卸载掉当前的输入法
void UnregisterCategories()
{
    ITfCategoryMgr* pCategoryMgr = S_OK;
    HRESULT hr = S_OK;

    hr = CoCreateInstance(CLSID_TF_CategoryMgr, NULL, CLSCTX_INPROC_SERVER, IID_ITfCategoryMgr, (void**)&pCategoryMgr);
    if (FAILED(hr))
    {
        return;
    }

    for each(GUID guid in SupportCategories)
    {
        pCategoryMgr->UnregisterCategory(Module::CLSID, 
        guid, 
        Module::CLSID);
    }
  
    pCategoryMgr->Release();

    return;
}

通过上面的三个注册流程我们就将对应的输入法注册到了系统当中,在对应的进程调用时候,就会触发框架调用开发的输入法。当然这只是注册到了输入法当中,之后还有检索候选词,智能组词等诸多逻辑。

设置默认输入法

在电脑上一般会安装多款输入法,很多时候需要切换输入法才能切到自己开发的输入法,这样很麻烦,可以将开发的输入法设置为默认输入法,这样就能默认加载对应的输入法。设置默认输入法主要调用下面的API进行设置。

//psz [in]指代输入法键盘布局对应的GUID值
//<LangID 1>:{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx};

//dwFlags [in] 对应的属性
//SDLOT_NOAPPLYTOCURRENTSESSION  0x00000001不立即生效
//SDLOT_APPLYTOCURRENTTHREAD  0x00000002立即生效
BOOL CALLBACK SetDefaultLayoutOrTip(
  _In_ LPCWSTR           psz,
  _In_ LPCWSTR psz DWORD dwFlags
);

将当前输入法设置为默认输入法的调用demo

BOOL SetIMEAsDefault()
{
	HMODULE hModule = LoadLibrary(TEXT("input.dll"));
	if (hModule == NULL) {
		return FALSE;
	}
	std::shared_ptr<HMODULE> freeModuleLater(&hModule, [](HMODULE *hModule) {
		FreeLibrary(*hModule);
	});
	(void)freeModuleLater;

	typedef HRESULT(WINAPI *PTF_SETDEFAULTLAYOUTORTIP)(LPCWSTR psz, DWORD dwFlags);
	PTF_SETDEFAULTLAYOUTORTIP pfnSetDefaultLayoutOrTip;
	pfnSetDefaultLayoutOrTip = (PTF_SETDEFAULTLAYOUTORTIP)GetProcAddress(hModule, "SetDefaultLayoutOrTip");
	if (!pfnSetDefaultLayoutOrTip) {
		return FALSE;
	}
   //0804指代中文输入法类型,后面的两个GUID值指的是对应的输入法的UUID
	TCHAR psz* = L"0804:{1572C5E0-7FCA-43E5-856D-0870D9641436}{2B5827C0-39C6-4D90-B966-BE50455E2F0D}"
	DWORD dwFlag = 0x00000002;  // SDLOT_APPLYTOCURRENTTHREAD
	BOOL bRet = pfnSetDefaultLayoutOrTip(psz, dwFlag);
	if (!bRet) {
		return FALSE;
	}
	return TRUE;
}
  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

码农飞飞

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值