做网站放广告收益邢台哪里做网站
2026/1/3 7:04:00 网站建设 项目流程
做网站放广告收益,邢台哪里做网站,建设银行官网首页网站招聘,2015做啥网站能致富TSF输入法框架开发全指南#xff1a;从COM组件到拼音输入法落地#xff08;C/VS2022#xff09; 引言 TSF#xff08;Text Services Framework#xff09;是微软从Windows XP开始推出的现代文本输入服务框架#xff0c;旨在替代传统IMM框架#xff0c;通过COM组件化设…TSF输入法框架开发全指南从COM组件到拼音输入法落地C/VS2022引言TSFText Services Framework是微软从Windows XP开始推出的现代文本输入服务框架旨在替代传统IMM框架通过COM组件化设计实现应用程序与文本服务的解耦支持键盘、手写、语音等多源输入是开发Windows平台输入法的首选方案。本文基于Windows SDK官方规范实际开发实践以“基础组件→核心接口→业务逻辑→UI实现→测试落地”为脉络拆解TSF输入法开发的每一个关键函数提供可直接复用的C代码框架、避坑要点和调试步骤适合有C/COM基础想入门输入法开发的开发者。开发环境VS2022 Windows 10/11 SDK语言CUnicode字符集一、前置准备工程初始化必做1. 工程配置创建“Win32 DLL”空项目完成以下配置包含头文件#include tsf.h、#include tspub.h链接库工程属性→链接器→输入→附加依赖项添加tsf.lib、ole32.libCOM依赖字符集工程属性→配置属性→常规→字符集→选择“使用Unicode字符集”TSF接口均为Unicode2. 自定义GUID避免冲突TSF服务需通过唯一GUID标识使用VS生成自定义GUID工具→创建GUID→选择“GUID_STR”格式替换以下代码中的示例值// 文本服务CLSID自定义替换为自己生成的GUIDconstCLSID CLSID_MyTSFInputMethod{0x12345678,0x1234,0x1234,{0x12,0x34,0x56,0x78,0x90,0xab,0xcd,0xef}};// 文本输入处理器IID复用微软官方定义无需修改constIID IID_ITfTextInputProcessor{0x529A9E6B,0x6587,0x4F23,{0xAB,0x9E,0x17,0x9D,0x41,0x36,0x2F,0xB0}};二、阶段1COM组件核心函数系统加载DLL的基础TSF文本服务本质是COM DLL需先实现以下5个核心函数否则系统无法识别和加载。1. DLL入口DllMain核心作用初始化/释放COM环境禁用不必要的线程通知确保TSF多线程模型兼容。BOOL APIENTRYDllMain(HMODULE hModule,DWORD ul_reason_for_call,LPVOID lpReserved){switch(ul_reason_for_call){caseDLL_PROCESS_ATTACH:// TSF必须使用多线程COM模型CoInitializeEx(NULL,COINIT_MULTITHREADED);// 禁用线程通知提升性能DisableThreadLibraryCalls(hModule);break;caseDLL_PROCESS_DETACH:// 释放COM资源CoUninitialize();break;}returnTRUE;}避坑要点不可省略COINIT_MULTITHREADED否则会导致TSF接口调用失败。2. 服务注册DllRegisterServer核心作用向注册表写入COM标识、TSF服务信息和支持语言让系统识别输入法。STDAPIDllRegisterServer(void){WCHAR szCLSID[40]{0};StringFromGUID2(CLSID_MyTSFInputMethod,szCLSID,_countof(szCLSID));WCHAR szKey[256]{0};// 1. 注册COM CLSID系统识别组件的基础wsprintf(szKey,LCLSID\\%s,szCLSID);HKEY hKeyNULL;RegCreateKeyEx(HKEY_CLASSES_ROOT,szKey,0,NULL,REG_OPTION_NON_VOLATILE,KEY_WRITE,NULL,hKey,NULL);RegSetValueEx(hKey,NULL,0,REG_SZ,(BYTE*)LMy TSF Input Method,sizeof(LMy TSF Input Method));RegCloseKey(hKey);// 2. 注册TSF文本输入处理器TSF管理器识别关键wsprintf(szKey,LCLSID\\%s\\TextServices,szCLSID);RegCreateKeyEx(HKEY_CLASSES_ROOT,szKey,0,NULL,REG_OPTION_NON_VOLATILE,KEY_WRITE,NULL,hKey,NULL);RegCloseKey(hKey);// 3. 注册支持语言0804简体中文0409英文参考微软语言代码wsprintf(szKey,LCLSID\\%s\\Language,szCLSID);RegCreateKeyEx(HKEY_CLASSES_ROOT,szKey,0,NULL,REG_OPTION_NON_VOLATILE,KEY_WRITE,NULL,hKey,NULL);RegSetValueEx(hKey,NULL,0,REG_SZ,(BYTE*)L0804,sizeof(L0804));RegCloseKey(hKey);returnS_OK;}使用方式管理员权限打开命令行输入regsvr32 你的DLL路径如regsvr32 D:\TSFInput\Debug\MyTSFInput.dll完成注册。避坑要点必须包含TextServices和Language子键否则TSF管理器无法识别。4. 服务注销DllUnregisterServer核心作用删除注册表中输入法的所有项避免残留。STDAPIDllUnregisterServer(void){WCHAR szCLSID[40]{0};StringFromGUID2(CLSID_MyTSFInputMethod,szCLSID,_countof(szCLSID));WCHAR szKey[256]{0};wsprintf(szKey,LCLSID\\%s,szCLSID);// 递归删除整个CLSID子键包含所有子项SHDeleteKey(HKEY_CLASSES_ROOT,szKey);returnS_OK;}避坑要点使用SHDeleteKey而非RegDeleteKey确保子键完全删除。4. 类工厂IClassFactory::CreateInstance核心作用COM类工厂的核心方法为TSF管理器创建文本服务实例。先定义类工厂类classCMyTSFClassFactory:publicIClassFactory{public:// 创建文本服务实例STDMETHOD(CreateInstance)(IUnknown*pUnkOuter,REFIID riid,void**ppvObj)override{// TSF不支持组件聚合禁止pUnkOuter非空if(pUnkOuter!NULL)returnCLASS_E_NOAGGREGATION;// 创建文本服务核心类实例后续定义CMyTSFInputMethod*pIMnewCMyTSFInputMethod();if(pIMNULL)returnE_OUTOFMEMORY;// 接口查询返回请求的接口returnpIM-QueryInterface(riid,ppvObj);}// 简化实现锁定组件无需复杂处理STDMETHOD(LockServer)(BOOL fLock)override{returnS_OK;}// COM基础方法QueryInterface接口路由STDMETHOD(QueryInterface)(REFIID riid,void**ppvObj)override{if(riidIID_IUnknown||riidIID_IClassFactory){*ppvObj(IClassFactory*)this;AddRef();returnS_OK;}*ppvObjNULL;returnE_NOINTERFACE;}// COM引用计数ULONGAddRef()override{returnInterlockedIncrement(m_cRef);}ULONGRelease()override{ULONG cRefInterlockedDecrement(m_cRef);if(cRef0)deletethis;returncRef;}private:LONG m_cRef1;// 引用计数初始化为1};5. 暴露类工厂DllGetClassObject核心作用让系统获取类工厂实例进而创建文本服务对象。STDAPIDllGetClassObject(REFCLSID rclsid,REFIID riid,void**ppvObj){// 仅响应自定义CLSID的请求if(rclsid!CLSID_MyTSFInputMethod)returnCLASS_E_CLASSNOTAVAILABLE;CMyTSFClassFactory*pFactorynewCMyTSFClassFactory();if(pFactoryNULL)returnE_OUTOFMEMORY;returnpFactory-QueryInterface(riid,ppvObj);}三、阶段2定义TSF文本服务类核心业务载体文本服务类是所有TSF接口的实现容器整合输入逻辑、上下文管理、业务资源需先定义类结构再实现接口方法。1. 类结构定义CMyTSFInputMethodclassCMyTSFInputMethod:publicITfTextInputProcessor,// TSF入口接口必须继承publicITfThreadMgrEventSink,// 上下文事件监听接口publicITfEditSession,// 文本编辑会话接口publicITfContextOwnerCompositionSink// 组合输入事件接口{public:// 构造/析构函数CMyTSFInputMethod():m_cRef(1),m_pThreadMgr(NULL),m_pCurrentContext(NULL),m_dwEventSinkCookie(0){}~CMyTSFInputMethod(){// 析构时释放资源if(m_pThreadMgr)m_pThreadMgr-Release();if(m_pCurrentContext)m_pCurrentContext-Release();}// -------------------------- COM基础方法必须实现--------------------------STDMETHOD(QueryInterface)(REFIID riid,void**ppvObj)override;STDMETHOD_(ULONG,AddRef)()override{returnInterlockedIncrement(m_cRef);}STDMETHOD_(ULONG,Release)()override;// -------------------------- ITfTextInputProcessor接口TSF入口--------------------------STDMETHOD(Activate)(ITfThreadMgr*pThreadMgr,TfClientId tid,DWORD dwFlags)override;STDMETHOD(Deactivate)()override;STDMETHOD(GetInfo)(TF_TEXTINPUTPROCESSORINFO*pInfo)override;STDMETHOD(GetStatus)(DWORD*pdwFlags)override{*pdwFlags0;returnS_OK;}// 简化实现// -------------------------- ITfThreadMgrEventSink接口上下文事件--------------------------STDMETHOD(OnContextCreated)(ITfThreadMgr*pThreadMgr,ITfContext*pContext)override;STDMETHOD(OnContextDestroyed)(ITfThreadMgr*pThreadMgr,ITfContext*pContext)override;STDMETHOD(OnSetFocus)(ITfThreadMgr*pThreadMgr,ITfContext*pContextFocus,ITfContext*pContextPrevFocus)override;// 其他默认实现无需修改STDMETHOD(OnPushContext)(ITfThreadMgr*pThreadMgr,ITfContext*pContext)override{returnS_OK;}STDMETHOD(OnPopContext)(ITfThreadMgr*pThreadMgr,ITfContext*pContext)override{returnS_OK;}// -------------------------- ITfEditSession接口文本编辑--------------------------STDMETHOD(DoEditSession)(ITfContext*pContext,TfEditCookie ec)override;// -------------------------- ITfContextOwnerCompositionSink接口组合输入--------------------------STDMETHOD(OnCompositionTerminated)(TfEditCookie ecWrite,ITfComposition*pComposition)override{returnS_OK;}// -------------------------- 自定义业务方法后续实现--------------------------voidInitPinyinDict();// 初始化拼音词库std::vectorCStringWPinyinParser(constWCHAR*pchPinyin);// 拼音解析voidCommitText(constWCHAR*pchText);// 提交文本HRESULTStartComposition();// 开始组合输入HRESULTEndComposition(ITfComposition*pComposition);// 结束组合输入private:// 成员变量核心状态管理LONG m_cRef;// COM引用计数ITfThreadMgr*m_pThreadMgr;// TSF线程管理器核心交互对象ITfContext*m_pCurrentContext;// 当前活跃的编辑上下文用户正在输入的区域TfClientId m_tid;// TSF客户端ID标识当前文本服务DWORD m_dwEventSinkCookie;// 事件接收器Cookie用于注销监听std::mapCStringW,std::vectorCStringWm_mapPinyinToHanzi;// 拼音-汉字映射表业务数据};核心说明必须继承ITfTextInputProcessorTSF管理器交互入口成员变量m_pThreadMgr和m_pCurrentContext是后续所有文本操作的基础。2. COM基础方法QueryInterface核心作用COM组件的“接口路由”让外部通过IID获取当前类实现的接口。STDMETHODIMPCMyTSFInputMethod::QueryInterface(REFIID riid,void**ppvObj){*ppvObjNULL;// 匹配所有继承的接口IIDif(riidIID_IUnknown)*ppvObj(IUnknown*)this;elseif(riidIID_ITfTextInputProcessor)*ppvObj(ITfTextInputProcessor*)this;elseif(riidIID_ITfThreadMgrEventSink)*ppvObj(ITfThreadMgrEventSink*)this;elseif(riidIID_ITfEditSession)*ppvObj(ITfEditSession*)this;elseif(riidIID_ITfContextOwnerCompositionSink)*ppvObj(ITfContextOwnerCompositionSink*)this;elsereturnE_NOINTERFACE;// 不支持的接口((IUnknown*)*ppvObj)-AddRef();// 接口返回前必须增加引用计数returnS_OK;}避坑要点必须包含所有继承的接口IID遗漏会导致外部无法调用对应接口方法。3. COM基础方法Release核心作用引用计数为0时删除对象并释放所有资源避免内存泄漏。STDMETHODIMP_(ULONG)CMyTSFInputMethod::Release(){ULONG cRefInterlockedDecrement(m_cRef);if(cRef0){// 1. 注销事件接收器if(m_pThreadMgrm_dwEventSinkCookie!0)m_pThreadMgr-UnadviseSink(m_dwEventSinkCookie);// 2. 释放核心对象引用if(m_pThreadMgr){m_pThreadMgr-Release();m_pThreadMgrNULL;}if(m_pCurrentContext){m_pCurrentContext-Release();m_pCurrentContextNULL;}// 3. 释放业务资源m_mapPinyinToHanzi.clear();// 4. 删除对象本身deletethis;}returncRef;}四、阶段3实现TSF入口接口ITfTextInputProcessor这是TSF管理器与文本服务的核心交互入口必须优先实现否则输入法无法被系统激活。1.GetInfo返回输入法基本信息核心作用向TSF管理器返回输入法名称、CLSID等信息用于系统显示。STDMETHODIMPCMyTSFInputMethod::GetInfo(TF_TEXTINPUTPROCESSORINFO*pInfo){if(pInfoNULL)returnE_INVALIDARG;// 参数校验// 填充输入法信息pInfo-clsidCLSID_MyTSFInputMethod;// 与自定义CLSID一致pInfo-tidm_tid;// 客户端IDActivate时传入wcscpy_s(pInfo-szDescription,_countof(pInfo-szDescription),LTSF拼音输入法);// 系统显示名称pInfo-dwFlags0;returnS_OK;}说明szDescription是输入法在系统语言栏中的显示名称建议简洁明了。2.Activate激活文本服务核心核心作用输入法被启用时的初始化逻辑注册事件监听、加载业务资源。STDMETHODIMPCMyTSFInputMethod::Activate(ITfThreadMgr*pThreadMgr,TfClientId tid,DWORD dwFlags){// 1. 保存核心对象引用后续所有操作依赖m_pThreadMgrpThreadMgr;m_pThreadMgr-AddRef();// COM对象必须持有引用m_tidtid;// 2. 注册事件接收器监听上下文创建、焦点切换等事件IID iidIID_ITfThreadMgrEventSink;HRESULT hrm_pThreadMgr-AdviseSink(iid,(IUnknown*)this,m_dwEventSinkCookie);if(FAILED(hr))returnhr;// 注册失败则激活失败// 3. 初始化业务资源如拼音词库InitPinyinDict();returnS_OK;}避坑要点必须调用AdviseSink注册事件接收器否则无法感知输入焦点变化。3.Deactivate停用文本服务核心作用输入法被关闭时释放所有资源避免内存泄漏。STDMETHODIMPCMyTSFInputMethod::Deactivate(){// 1. 注销事件接收器if(m_pThreadMgrm_dwEventSinkCookie!0){m_pThreadMgr-UnadviseSink(m_dwEventSinkCookie);m_dwEventSinkCookie0;}// 2. 释放核心对象引用if(m_pThreadMgr){m_pThreadMgr-Release();m_pThreadMgrNULL;}if(m_pCurrentContext){m_pCurrentContext-Release();m_pCurrentContextNULL;}// 3. 释放业务资源m_mapPinyinToHanzi.clear();returnS_OK;}避坑要点Deactivate可能被多次调用需确保资源释放逻辑幂等多次调用不报错。五、阶段4实现上下文事件监听定位输入区域通过监听上下文事件找到用户当前正在编辑的输入框活跃上下文为文本输入做准备。1.OnContextCreated上下文创建时触发核心作用新输入框如记事本、Word文档创建时注册组合输入事件监听。STDMETHODIMPCMyTSFInputMethod::OnContextCreated(ITfThreadMgr*pThreadMgr,ITfContext*pContext){// 为新上下文注册组合输入事件接收器IID iidIID_ITfContextOwnerCompositionSink;DWORD dwCookie0;pContext-AdviseSink(m_tid,iid,(IUnknown*)this,dwCookie);returnS_OK;}说明每个新上下文都需单独注册事件否则无法在该输入框中进行组合输入如拼音候选。2.OnSetFocus焦点切换时触发核心作用输入焦点切换到新输入框时更新当前活跃上下文。STDMETHODIMPCMyTSFInputMethod::OnSetFocus(ITfThreadMgr*pThreadMgr,ITfContext*pContextFocus,ITfContext*pContextPrevFocus){// 释放之前的活跃上下文引用if(m_pCurrentContext)m_pCurrentContext-Release();// 更新为当前焦点上下文m_pCurrentContextpContextFocus;if(m_pCurrentContext)m_pCurrentContext-AddRef();// 持有新上下文引用returnS_OK;}核心说明m_pCurrentContext是后续文本输入的目标必须实时更新。3.OnContextDestroyed上下文销毁时触发核心作用输入框关闭时释放对应的上下文引用避免野指针。STDMETHODIMPCMyTSFInputMethod::OnContextDestroyed(ITfThreadMgr*pThreadMgr,ITfContext*pContext){// 如果销毁的是当前活跃上下文释放引用if(m_pCurrentContextpContext){m_pCurrentContext-Release();m_pCurrentContextNULL;}returnS_OK;}六、阶段5实现文本存储接口ITextStoreAcp文本存储是TSF文本流传递的核心负责文本的读写、插入、状态管理需单独定义类实现。1. 文本存储类定义CMyTextStoreAcpclassCMyTextStoreAcp:publicITextStoreAcp{public:// 构造函数关联到具体上下文CMyTextStoreAcp(ITfContext*pContext):m_cRef(1),m_pContext(pContext),m_pSink(NULL){m_pContext-AddRef();// 持有上下文引用m_strTextBufferL;// 初始化文本缓冲区}// 析构函数释放资源~CMyTextStoreAcp(){if(m_pContext)m_pContext-Release();if(m_pSink)m_pSink-Release();}// -------------------------- COM基础方法 --------------------------STDMETHOD(QueryInterface)(REFIID riid,void**ppvObj)override;STDMETHOD_(ULONG,AddRef)()override{returnInterlockedIncrement(m_cRef);}STDMETHOD_(ULONG,Release)()override;// -------------------------- ITextStoreAcp核心方法 --------------------------STDMETHOD(AdviseSink)(REFIID riid,IUnknown*punk,DWORD dwMask)override;STDMETHOD(UnadviseSink)(IUnknown*punk)override;STDMETHOD(RequestLock)(DWORD dwLockFlags,HRESULT*phrSession)override;STDMETHOD(GetStatus)(TS_STATUS*pdcs)override;STDMETHOD(QueryInsert)(LONG acpTestStart,LONG acpTestEnd,LONG cchNew,LONG*pacpResultStart,LONG*pacpResultEnd)override;STDMETHOD(InsertTextAt)(TfEditCookie ec,LONG acpStart,constWCHAR*pchText,LONG cch,TS_TEXTCHANGE*pChange)override;STDMETHOD(GetText)(TfEditCookie ec,LONG acpStart,LONG acpEnd,WCHAR*pchText,LONG cchReq,LONG*pcchOut)override;// -------------------------- ITextStoreAcp默认实现无需修改--------------------------STDMETHOD(GetSelection)(...)override{returnE_NOTIMPL;}STDMETHOD(SetSelection)(...)override{returnE_NOTIMPL;}STDMETHOD(GetActiveView)(...)override{returnE_NOTIMPL;}STDMETHOD(GetDocMgr)(...)override{returnE_NOTIMPL;}STDMETHOD(GetEndACP)(LONG*pacp)override{*pacpm_strTextBuffer.GetLength();returnS_OK;}STDMETHOD(GetStartACP)(LONG*pacp)override{*pacp0;returnS_OK;}STDMETHOD(QueryText)(...)override{returnE_NOTIMPL;}STDMETHOD(ReplaceTextAt)(...)override{returnE_NOTIMPL;}STDMETHOD(DeleteTextAt)(...)override{returnE_NOTIMPL;}STDMETHOD(GetFormattedText)(...)override{returnE_NOTIMPL;}private:LONG m_cRef;// COM引用计数ITfContext*m_pContext;// 关联的编辑上下文CStringW m_strTextBuffer;// 文本缓冲区存储输入文本IUnknown*m_pSink;// 文本变化事件接收器};核心说明必实现RequestLock锁机制、InsertTextAt插入文本、GetText读取文本其他方法可默认返回E_NOTIMPL。2. COM基础方法实现// QueryInterfaceSTDMETHODIMPCMyTextStoreAcp::QueryInterface(REFIID riid,void**ppvObj){*ppvObjNULL;if(riidIID_IUnknown||riidIID_ITextStoreAcp){*ppvObj(ITextStoreAcp*)this;AddRef();returnS_OK;}returnE_NOINTERFACE;}// ReleaseSTDMETHODIMP_(ULONG)CMyTextStoreAcp::Release(){ULONG cRefInterlockedDecrement(m_cRef);if(cRef0){if(m_pContext)m_pContext-Release();if(m_pSink)m_pSink-Release();deletethis;}returncRef;}3. 核心方法RequestLock申请编辑锁核心作用避免多线程同时修改文本缓冲区保证操作原子性。STDMETHODIMPCMyTextStoreAcp::RequestLock(DWORD dwLockFlags,HRESULT*phrSession){if(phrSessionNULL)returnE_INVALIDARG;*phrSessionS_OK;// 简化实现直接允许锁请求复杂场景需处理锁冲突returnS_OK;}4. 核心方法GetStatus返回文本状态核心作用向TSF管理器声明文本区域可读写。STDMETHODIMPCMyTextStoreAcp::GetStatus(TS_STATUS*pdcs){if(pdcsNULL)returnE_INVALIDARG;ZeroMemory(pdcs,sizeof(TS_STATUS));pdcs-dwDynamicFlagsTS_STATUS_READWRITE;// 标记为可读写输入法核心需求pdcs-dwStaticFlags0;returnS_OK;}避坑要点必须设置TS_STATUS_READWRITE否则无法插入文本。5. 核心方法InsertTextAt插入文本核心作用将输入法生成的文本如汉字插入到缓冲区并通知应用。STDMETHODIMPCMyTextStoreAcp::InsertTextAt(TfEditCookie ec,LONG acpStart,constWCHAR*pchText,LONG cch,TS_TEXTCHANGE*pChange){// 参数校验if(pchTextNULL||cch0)returnE_INVALIDARG;// 1. 插入文本到缓冲区m_strTextBuffer.Insert(acpStart,pchText,cch);// 2. 填充文本变化信息通知应用if(pChange!NULL){pChange-acpStartacpStart;pChange-acpOldEndacpStart;// 插入前结束索引pChange-acpNewEndacpStartcch;// 插入后结束索引}// 3. 通知事件接收器如有注册if(m_pSink){ITextStoreACPSink*pSinkNULL;m_pSink-QueryInterface(IID_ITextStoreACPSink,(void**)pSink);if(pSink){pSink-OnTextChange(ec,pChange);pSink-Release();}}returnS_OK;}6. 核心方法GetText读取文本核心作用读取缓冲区中指定范围的文本。STDMETHODIMPCMyTextStoreAcp::GetText(TfEditCookie ec,LONG acpStart,LONG acpEnd,WCHAR*pchText,LONG cchReq,LONG*pcchOut){// 参数校验if(pchTextNULL||pcchOutNULL)returnE_INVALIDARG;// 计算可读取的字符数避免越界LONG cchTextLenm_strTextBuffer.GetLength();LONG cchToReadmin(cchReq,acpEnd-acpStart);cchToReadmin(cchToRead,cchTextLen-acpStart);if(cchToRead0)cchToRead0;// 复制文本到输出缓冲区wcscpy_s(pchText,cchReq,m_strTextBuffer.Mid(acpStart,cchToRead));*pcchOutcchToRead;// 返回实际读取长度returnS_OK;}7. 事件注册AdviseSink/UnadviseSink// 注册事件接收器STDMETHODIMPCMyTextStoreAcp::AdviseSink(REFIID riid,IUnknown*punk,DWORD dwMask){if(punkNULL)returnE_INVALIDARG;if(m_pSink)m_pSink-Release();m_pSinkpunk;m_pSink-AddRef();returnS_OK;}// 注销事件接收器STDMETHODIMPCMyTextStoreAcp::UnadviseSink(IUnknown*punk){if(m_pSinkpunk){m_pSink-Release();m_pSinkNULL;}returnS_OK;}七、阶段6实现编辑会话与组合输入业务核心编辑会话确保文本操作原子性组合输入管理“拼音→候选词→确认输入”的核心流程。1.DoEditSession执行原子性文本操作核心作用所有文本修改插入/删除必须通过该方法执行避免线程冲突。STDMETHODIMPCMyTSFInputMethod::DoEditSession(ITfContext*pContext,TfEditCookie ec){if(pContextNULL)returnE_INVALIDARG;// 1. 获取文本存储对象ITextStoreAcp*pTextStoreNULL;HRESULT hrpContext-QueryInterface(IID_ITextStoreAcp,(void**)pTextStore);if(FAILED(hr))returnhr;// 2. 插入文本实际开发替换为拼音解析结果WCHAR szTargetText[]L你好;TS_TEXTCHANGE textChange{0};hrpTextStore-InsertTextAt(ec,0,szTargetText,_countof(szTargetText)-1,textChange);// 3. 释放资源pTextStore-Release();returnhr;}避坑要点不可直接调用ITextStoreAcp::InsertTextAt必须通过编辑会话执行。2. 自定义方法CommitText触发编辑会话核心作用拼音解析完成后调用该方法提交文本。voidCMyTSFInputMethod::CommitText(constWCHAR*pchText){if(m_pCurrentContextNULL)return;// 触发编辑会话异步执行不阻塞主线程HRESULT hrm_pCurrentContext-RequestEditSession(m_tid,// 客户端ID(ITfEditSession*)this,// 编辑会话对象TF_ES_ASYNCDONTCARE,// 异步标志NULL// 无需回调);}3. 组合输入StartComposition开始未确认输入核心作用用户输入拼音但未选字时启动组合输入模式。HRESULTCMyTSFInputMethod::StartComposition(){if(m_pCurrentContextNULL)returnE_INVALIDARG;ITfComposition*pCompositionNULL;// 创建组合输入对象HRESULT hrm_pCurrentContext-StartComposition(m_tid,pComposition);if(SUCCEEDED(hr)){// 设置组合文本范围TS_RANGE compositionRange{0,0};hrpComposition-SetCompositionRange(m_tid,compositionRange);// 添加拼音文本如用户输入的“nihao”hrpComposition-AddText(m_tid,Lnihao,_countof(Lnihao)-1);pComposition-Release();}returnhr;}4. 组合输入EndComposition确认输入核心作用用户选择候选词后结束组合模式并提交最终文本。HRESULTCMyTSFInputMethod::EndComposition(ITfComposition*pComposition){if(pCompositionNULL)returnE_INVALIDARG;// 1. 结束组合模式HRESULT hrpComposition-EndComposition(m_tid);// 2. 提交最终文本CommitText(L你好);returnhr;}八、阶段7UI实现语言栏候选框1. 语言栏按钮类CMyLangBarItemclassCMyLangBarItem:publicITfLangBarItemButton{public:CMyLangBarItem():m_cRef(1){ZeroMemory(m_itemInfo,sizeof(m_itemInfo));m_itemInfo.clsidServiceCLSID_MyTSFInputMethod;m_itemInfo.guidItemGUID_NULL;// 自定义GUIDm_itemInfo.dwStyleTF_LBI_STYLE_BTN_BUTTON;wcscpy_s(m_itemInfo.szDescription,LTSF拼音输入法);}// COM基础方法QueryInterface/AddRef/Release参考之前实现STDMETHOD(QueryInterface)(REFIID riid,void**ppvObj)override{*ppvObjNULL;if(riidIID_IUnknown||riidIID_ITfLangBarItemButton){*ppvObj(ITfLangBarItemButton*)this;AddRef();returnS_OK;}returnE_NOINTERFACE;}// 按钮信息STDMETHOD(GetInfo)(TF_LANGBARITEMINFO*pInfo)override{if(pInfoNULL)returnE_INVALIDARG;*pInfom_itemInfo;returnS_OK;}// 按钮提示STDMETHOD(GetTooltipString)(BSTR*pbstrToolTip)override{*pbstrToolTipSysAllocString(LTSF拼音输入法);returnS_OK;}// 点击事件切换中英文STDMETHOD(OnClick)(TfLBIClick click,POINT pt,constRECT*prcArea)override{m_bChineseMode!m_bChineseMode;returnS_OK;}STDMETHOD(Show)(BOOL fShow)override{returnS_OK;}STDMETHOD(Hide)()override{returnS_OK;}private:LONG m_cRef;TF_LANGBARITEMINFO m_itemInfo;BOOL m_bChineseModeTRUE;// 默认中文模式};2. 候选框ShowCandidateUI显示候选词voidShowCandidateUI(conststd::vectorCStringWvecCandidates,POINT ptInput){// 创建无边框候选框窗口HWND hCandidateWndCreateWindowEx(0,LSTATIC,L候选词,WS_POPUP|WS_VISIBLE,ptInput.x,ptInput.y20,200,300,NULL,NULL,GetModuleHandle(NULL),NULL);// 绘制候选词HDC hdcGetDC(hCandidateWnd);for(inti0;ivecCandidates.size();i){TextOut(hdc,10,10i*20,vecCandidates[i],vecCandidates[i].GetLength());}ReleaseDC(hCandidateWnd,hdc);}九、阶段8业务逻辑拼音解析词库1. 初始化拼音词库InitPinyinDictvoidCMyTSFInputMethod::InitPinyinDict(){// 简化硬编码拼音-汉字映射实际开发读词库文件m_mapPinyinToHanzi[Lnihao]{L你好,L泥壕,L倪浩};m_mapPinyinToHanzi[Lzhongguo]{L中国,L忠国,L钟国};}2. 拼音解析PinyinParserstd::vectorCStringWCMyTSFInputMethod::PinyinParser(constWCHAR*pchPinyin){if(pchPinyinNULL)return{};// 查找拼音对应的候选词autoitm_mapPinyinToHanzi.find(pchPinyin);if(it!m_mapPinyinToHanzi.end())returnit-second;return{};}3. 键盘事件处理InputWndProc// 全局变量文本服务实例简化实现实际需优化CMyTSFInputMethod*g_pIMNULL;// 获取输入框位置简化取鼠标位置POINTGetInputPos(){POINT pt;GetCursorPos(pt);returnpt;}// 窗口过程处理键盘输入LRESULT CALLBACKInputWndProc(HWND hWnd,UINT uMsg,WPARAM wParam,LPARAM lParam){switch(uMsg){caseWM_KEYDOWN:{// 收集拼音字符WCHAR chInput(WCHAR)wParam;staticCStringW strPinyinBuffer;strPinyinBufferchInput;// 解析拼音生成候选词std::vectorCStringWvecCandidatesg_pIM-PinyinParser(strPinyinBuffer);// 显示候选框ShowCandidateUI(vecCandidates,GetInputPos());return0;}default:returnDefWindowProc(hWnd,uMsg,wParam,lParam);}}十、阶段9测试与调试落地关键1. 注册DLL管理员权限打开命令行输入regsvr32 你的DLL路径注册成功会弹出提示失败需检查注册表写入逻辑2. 验证注册工具Windows SDK的tsfscanner.exe路径C:\Program Files (x86)\Windows Kits\10\bin\10.0.19041.0\x64操作运行tsfscanner.exe在“Text Services”中查找自定义CLSID3. 调试代码VS中设置断点Activate、DoEditSession、InsertTextAt启动记事本notepad.exeVS→调试→附加到进程→选择“notepad.exe”切换到自定义输入法输入文字触发断点4. 启用输入法Windows设置→时间和语言→输入→高级键盘设置→添加输入法选择“TSF拼音输入法”切换后在记事本中测试十一、核心避坑指南开发必看COM引用计数严格匹配每个AddRef对应一个Release遗漏会导致内存泄漏多调用会导致野指针。文本操作必须在EditSession中直接调用ITextStoreAcp方法会被TSF拒绝引发线程冲突。GUID必须自定义不可复用系统或其他输入法的GUID否则注册失败。优先测试记事本记事本是纯TSF应用无额外兼容问题调试通过后再测试Word、浏览器。管理员权限注册注册DLL必须用管理员权限否则注册表写入失败。避免阻塞主线程Activate、DoEditSession中不可执行耗时操作如词库加载需异步处理。参考资料微软官方TSF文档Text Services FrameworkWindows SDK TSF示例C:\Program Files (x86)\Windows Kits\10\Samples\winui\input\TSF如果本文对你有帮助欢迎点赞、收藏、留言交流后续会更新“TSF手写输入”“多语言支持”等进阶内容~

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询