输入法注册
基于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,
®KeyHandle, &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,
®SubkeyHandle, &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;
}