『转载』微软新一代输入法框架 TSF - Text Service Framework 小小的研究
原文转载自: https://blog.csdn.net/puncha/article/details/13293665
虽说是转载的,但是其中,有很多我自己的评论,我会用红色的字标出来,参考的博文有:
TSF架构:http://blog.csdn.net/mspinyin/article/details/6137709
TSF代码实例:http://www.dotblogs.com.tw/code6421/archive/2010/09/27/17909.aspx
TSF的一个C# Wrapper库:http://social.technet.microsoft.com/Forums/office/zh-CN/002efcfc-8d21-4674-b93b-53c8424d448e/vista-api-immgetdescription?forum=2087
下面内容来自第一篇被引用的文章,TSF架构:
几个关于TSF的术语
TIP (Text Input Processor), a Text service in TSF
Cicero,TSF的开发代号,所以微软内部通常称呼TSF框架为Cicero
CUAS (Cicero Unaware Application Support),为所有应用程序和控件提供基本的TIP支持
AIMM(Active Input Method Manager),和CUAS一起工作
输入法框架
先讲一点点历史,Windows提供了两套输入法框架: Windows XP及之前,是IMM (Input Method Manager),基于纯函数API的。目前市面上非微软中文输入法基本上都是只实现IMM框架。
Windows XP开始及以后,Windows提供新的输入框架TSF,是基于COM的。实际上,到了Windows Vista,Windows 7,所有的应用程序和各种输入控件都是优先使用TSF的实现。但之所以Windows Vista,Windows 7用户还能使用各种基于IMM的输入法,是因为Windows提供了一个组件来将所有TSF的请求转为IMM的API。(PunCha:难道Win8开始就不提供了吗?!很有可能,因为Win8下很多Imm的函数都不能使用了)
按照微软的说法,TSF会最终取代IMM框架。而微软拼音基于兼容,功能和性能方面的原因,将这两个框架都实现了。(PunCha:Win8下,只有你们一家还能用Imm的API,很厚道!)
下面主要介绍TSF框架的输入法及与应用程序的交互。
TSF框架
Cicero它的目标是提供一套简单通用并易扩展的框架,用于高级文本输入和自然语言处理。一个TSF text service能够提供多语言支持和处理:键盘输入(我们通常讲的输入法),手写识别,语言识别等。
TSF的构架图
从上面构架图可以看到,TSF提供一个位于应用和输入法实现的间接层(一个Text service/TIP可以是一个输入法,或语音识别,PunCha:记住,TIP就是一个输入法提供的一个服务,比如百度输入法提供“语音、手写、键盘”输入,那就是3个Tip,但是一般我们都是用键盘输入,所以可以简单认为Tip是一种输入法。)。所以,TSF的优点在于,它是一个设备无关,语言中立,可扩展的系统;同时给用户提供一致的输入体验。任何TSF-enabled应用程序都能从任何text service接受文字输入,而不用考虑Text source的具体细节。同时,Text service也不用考虑各种不同应用的差别。譬如下面的应用场景:
上图可以看出, 应用程序收到”The boy ate the red apple”这段文字, 但是它不知道这些字是哪个TIP输入的,有可能是”Keyboard TIP”, 或是”Speech TIP”等。
与应用程序的交互
那么,这个框架是如何工作的?看看下面的组件交互图
在windows XP下,默认是CUAS关闭的,其交互如下
从上图可见,所有“edit control”(包括Notepad)都是直接调用IMM的API,最后调用IMM输入法,而4.1版本后的“RichEdit control”(包括WordPad等)是直接用TSF实现的输入法)
(PunCha:注意,是TSF依赖于Imm)
在Windows XP下如果打开CUAS,或者在Windows Vista和Windows 7下,则如下交互:
可以看到,IMM32和CUAS中多了一条交互,这意味着如果一个输入法实现了TIP,在Windows Vista和Windows 7,或CUAS打开的Windows XP下,应用程序的所有输入由TIP实现完成。
(PunCha:注意,Imm依赖于CUAS了!即可以使用用TSF实现的输入法了,应该就是高级输入)
下面内容来自第二篇被引用的文章的一条注释,使用TSF COM API获得输入法信息:
1 #include <windows.h> 2 #include <msctf.h> 3 4 int _tmain(int argc, TCHAR* argv[]) 5 { 6 CoInitialize(0); 7 HRESULT hr = S_OK; 8 9 //PunCha:创建Profiles接口被 10 ITfInputProcessorProfiles *pProfiles; 11 hr = CoCreateInstance( CLSID_TF_InputProcessorProfiles, 12 NULL, 13 CLSCTX_INPROC_SERVER, 14 IID_ITfInputProcessorProfiles, 15 (LPVOID*)&pProfiles); 16 17 if(SUCCEEDED(hr)) 18 { 19 IEnumTfLanguageProfiles* pEnumProf = 0; 20 //PunCha:枚举所有输入法咯 21 hr = pProfiles->EnumLanguageProfiles(0x804, &pEnumProf); 22 if (SUCCEEDED(hr) && pEnumProf) 23 { 24 TF_LANGUAGEPROFILE proArr[2]; 25 ULONG feOut = 0; 26 //PunCha:其实proArr这里应该写成 &proArr[0],因为里面只需要一个TF_LANGUAGEPROFILE变量!而且,proArr[1]都没用到过! 27 while (S_OK == pEnumProf->Next(1, proArr, &feOut)) 28 { 29 //PunCha:获取他的名字 30 BSTR bstrDest; 31 hr = pProfiles->GetLanguageProfileDescription(proArr[0].clsid, 0x804, proArr[0].guidProfile, &bstrDest); 32 OutputDebugString(bstrDest); 33 wprintf(bstrDest); printf("\n"); 34 35 BOOL bEnable = false; 36 hr = pProfiles->IsEnabledLanguageProfile(proArr[0].clsid, 0x804, proArr[0].guidProfile, &bEnable); 37 if (SUCCEEDED(hr)) 38 { 39 printf("Enabled %d\n", bEnable); 40 } 41 SysFreeString(bstrDest); 42 } 43 } 44 45 pProfiles->Release(); 46 } 47 48 CoUninitialize(); 49 return 0; 50 }
以下 C# -TSF 的 COM 组件代码 转载自: https://dotblogs.com.tw/code6421/archive/2010/09/27/17909.aspx
1 /////////////////////////////////////////////////////////////////////////////////////////////// 2 // Microsoft Text Service Framework Declaration 3 // from C++ header file 4 // 5 ////////////////////////////////////////////////////////////////////////////////////////////// 6 using System; 7 using System.ComponentModel; 8 using System.Collections.Generic; 9 using System.Text; 10 using System.Runtime.InteropServices; 11 using System.Security; 12 13 namespace TSF 14 { 15 [StructLayout(LayoutKind.Sequential)] 16 internal struct TF_LANGUAGEPROFILE 17 { 18 internal Guid clsid; 19 internal short langid; 20 internal Guid catid; 21 [MarshalAs(UnmanagedType.Bool)] 22 internal bool fActive; 23 internal Guid guidProfile; 24 } 25 26 [ComImport, SecurityCritical, SuppressUnmanagedCodeSecurity, 27 Guid("1F02B6C5-7842-4EE6-8A0B-9A24183A95CA"), 28 InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] 29 internal interface ITfInputProcessorProfiles 30 { 31 [SecurityCritical] 32 void Register(); //non-implement!! may be is wrong declaration. 33 [SecurityCritical] 34 void Unregister(); //non-implement!! may be is wrong declaration. 35 [SecurityCritical] 36 void AddLanguageProfile(); //non-implement!! may be is wrong declaration. 37 [SecurityCritical] 38 void RemoveLanguageProfile(); //non-implement!! may be is wrong declaration. 39 [SecurityCritical] 40 void EnumInputProcessorInfo(); //non-implement!! may be is wrong declaration. 41 [SecurityCritical] 42 int GetDefaultLanguageProfile(short langid,ref Guid catid,out Guid clsid,out Guid profile); 43 [SecurityCritical] 44 void SetDefaultLanguageProfile(); //non-implement!! may be is wrong declaration. 45 [SecurityCritical] 46 int ActivateLanguageProfile(ref Guid clsid, short langid, ref Guid guidProfile); 47 [PreserveSig, SecurityCritical] 48 int GetActiveLanguageProfile(ref Guid clsid, out short langid, out Guid profile); 49 [SecurityCritical] 50 int GetLanguageProfileDescription(ref Guid clsid,short langid,ref Guid profile,out IntPtr desc); 51 [SecurityCritical] 52 void GetCurrentLanguage(out short langid); //non-implement!! may be is wrong declaration. 53 [PreserveSig, SecurityCritical] 54 int ChangeCurrentLanguage(short langid); //non-implement!! may be is wrong declaration. 55 [PreserveSig, SecurityCritical] 56 int GetLanguageList(out IntPtr langids, out int count); 57 [SecurityCritical] 58 int EnumLanguageProfiles(short langid, out IEnumTfLanguageProfiles enumIPP); 59 [SecurityCritical] 60 int EnableLanguageProfile(); 61 [SecurityCritical] 62 int IsEnabledLanguageProfile(ref Guid clsid, short langid, ref Guid profile, out bool enabled); 63 [SecurityCritical] 64 void EnableLanguageProfileByDefault(); //non-implement!! may be is wrong declaration. 65 [SecurityCritical] 66 void SubstituteKeyboardLayout(); //non-implement!! may be is wrong declaration. 67 } 68 69 [ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown), 70 Guid("3d61bf11-ac5f-42c8-a4cb-931bcc28c744")] 71 internal interface IEnumTfLanguageProfiles 72 { 73 void Clone(out IEnumTfLanguageProfiles enumIPP); 74 [PreserveSig] 75 int Next(int count, [Out, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 2)] 76 TF_LANGUAGEPROFILE[] profiles, out int fetched); 77 void Reset(); 78 void Skip(int count); 79 } 80 81 internal static class TSF_NativeAPI 82 { 83 public static readonly Guid GUID_TFCAT_TIP_KEYBOARD; 84 85 static TSF_NativeAPI() 86 { 87 GUID_TFCAT_TIP_KEYBOARD = new Guid(0x34745c63, 0xb2f0, 88 0x4784, 0x8b, 0x67, 0x5e, 0x12, 200, 0x70, 0x1a, 0x31); 89 } 90 91 [SecurityCritical, SuppressUnmanagedCodeSecurity, DllImport("msctf.dll")] 92 public static extern int TF_CreateInputProcessorProfiles(out ITfInputProcessorProfiles profiles); 93 } 94 }
1 public static short[] GetLangIDs() 2 { 3 List<short> langIDs = new List<short>(); 4 ITfInputProcessorProfiles profiles; 5 if (TSF_NativeAPI.TF_CreateInputProcessorProfiles(out profiles) == 0) 6 { 7 IntPtr langPtrs; 8 int fetchCount = 0; 9 if (profiles.GetLanguageList(out langPtrs, out fetchCount) == 0) 10 { 11 for (int i = 0; i < fetchCount; i++) 12 { 13 short id = Marshal.ReadInt16(langPtrs, sizeof(short) * i); 14 langIDs.Add(id); 15 } 16 } 17 Marshal.ReleaseComObject(profiles); 18 } 19 return langIDs.ToArray(); 20 } 21 22 public static string[] GetInputMethodList(short langID) 23 { 24 List<string> imeList = new List<string>(); 25 ITfInputProcessorProfiles profiles; 26 if (TSF_NativeAPI.TF_CreateInputProcessorProfiles(out profiles) == 0) 27 { 28 try 29 { 30 IEnumTfLanguageProfiles enumerator = null; 31 if (profiles.EnumLanguageProfiles(langID, out enumerator) == 0) 32 { 33 if (enumerator != null) 34 { 35 TF_LANGUAGEPROFILE[] langProfile = new TF_LANGUAGEPROFILE[1]; 36 int fetchCount = 0; 37 while (enumerator.Next(1, langProfile, out fetchCount) == 0) 38 { 39 IntPtr ptr; 40 if (profiles.GetLanguageProfileDescription(ref langProfile[0].clsid, 41 langProfile[0].langid, ref langProfile[0].guidProfile, out ptr) == 0) 42 { 43 bool enabled; 44 if (profiles.IsEnabledLanguageProfile(ref langProfile[0].clsid, 45 langProfile[0].langid, ref langProfile[0].guidProfile, out enabled) == 0) 46 { 47 if (enabled) 48 imeList.Add(Marshal.PtrToStringBSTR(ptr)); 49 } 50 } 51 Marshal.FreeBSTR(ptr); 52 } 53 } 54 } 55 } 56 finally 57 { 58 Marshal.ReleaseComObject(profiles); 59 } 60 } 61 return imeList.ToArray(); 62 }
1 private short[] langIDs; 2 ……… 3 private void button1_Click(object sender, EventArgs e) 4 { 5 langIDs = TSFWrapper.GetLangIDs(); 6 if (langIDs.Length > 0) 7 { 8 string[] list = TSFWrapper.GetInputMethodList(langIDs[0]); 9 foreach (string desc in list) 10 listBox1.Items.Add(desc); 11 } 12 }
1 public static bool ActiveInputMethodWithDesc(short langID, string desc) 2 { 3 ITfInputProcessorProfiles profiles; 4 if (TSF_NativeAPI.TF_CreateInputProcessorProfiles(out profiles) == 0) 5 { 6 try 7 { 8 IEnumTfLanguageProfiles enumerator = null; 9 if (profiles.EnumLanguageProfiles(langID, out enumerator) == 0) 10 { 11 if (enumerator != null) 12 { 13 TF_LANGUAGEPROFILE[] langProfile = new TF_LANGUAGEPROFILE[1]; 14 int fetchCount = 0; 15 while (enumerator.Next(1, langProfile, out fetchCount) == 0) 16 { 17 IntPtr ptr; 18 if (profiles.GetLanguageProfileDescription(ref langProfile[0].clsid, 19 langProfile[0].langid, ref langProfile[0].guidProfile, out ptr) == 0) 20 { 21 bool enabled; 22 if (profiles.IsEnabledLanguageProfile(ref langProfile[0].clsid, 23 langProfile[0].langid, ref langProfile[0].guidProfile, out enabled) == 0) 24 { 25 if (enabled) 26 { 27 string s = Marshal.PtrToStringBSTR(ptr); 28 if (s.Equals(desc)) 29 return profiles.ActivateLanguageProfile(ref langProfile[0].clsid, 30 langProfile[0].langid, ref langProfile[0].guidProfile) == 0; 31 } 32 } 33 Marshal.FreeBSTR(ptr); 34 } 35 } 36 } 37 } 38 } 39 finally 40 { 41 Marshal.ReleaseComObject(profiles); 42 } 43 } 44 return false; 45 }
1 private void button2_Click(object sender, EventArgs e) 2 { 3 if (langIDs != null) 4 { 5 if (listBox1.SelectedIndex != -1) 6 TSFWrapper.ActiveInputMethodWithDesc(langIDs[0], (string)listBox1.SelectedItem); 7 } 8 }
1 public static bool DeActiveInputMethod(short langID) 2 { 3 List<string> imeList = new List<string>(); 4 ITfInputProcessorProfiles profiles; 5 if (TSF_NativeAPI.TF_CreateInputProcessorProfiles(out profiles) == 0) 6 { 7 try 8 { 9 Guid clsid = Guid.Empty; 10 return profiles.ActivateLanguageProfile(ref clsid, langID, ref clsid) == 0; 11 } 12 finally 13 { 14 Marshal.ReleaseComObject(profiles); 15 } 16 } 17 return false; 18 }
转载的源码下载: VistaIMEHelperNet.zip
以上内容,来自网络,
感谢各位原作者为 Xp、Win7、Win8、Win10 的 IME、TSF 输入法 API技术 所 作出的努力。