Search code examples
.netcomtext-services-framework

how to make use of Text Services Framework into .net application


i want to make use of Com intefaces into my .net application, but this is related to Text Services Framework and i reasearched on this and it was like it only supports COM servers can anyone help me on this can i use TSF intefaces into my .net application and if yes Please tell me how to do it plzzzzzz :) thanks


Solution

  • Hey I have the same problem, I am evaluating if i really have to use C++ for doing the job. You can of course create a COM Server in any COM compatible environment, so also in .NET. I am by far not in any usable state, but I can tell you what I did so far. First, the msctf.dll only has a header and an IDL file (in Windows SDK 7.0), which has to be modified such that you get a type library (tlb). I used the tlbimp2 from codeplex, it has an xml based rewriting mechanism (that eases the heavy use of pointers in the type library: Here is my batch file

    set sdk7=C:\Program Files\Microsoft SDKs\Windows\v7.0
    set imported=msctf
    
    rem call "%sdk7%\Bin\SetEnv.cmd"
    midl "%sdk7%\Include\%imported%.idl"
    rem i copied the tlbimp2 into the sdk bin
    tlbimp2 /keyfile:TextService.snk %imported%.tlb /config:msctf.xml
    rem tlbimp /keyfile:TextService.snk %imported%.tlb
    
    rem not sure about this
    gacutil /u %imported%
    gacutil /i %imported%.dll
    

    And here my XML rule file (to be expanded of course) http://clrinterop.codeplex.com/

    <Rules>
      <Rule Name="addlangprofile string 1" Category="Signature">
        <Condition>
          <And>
            <NativeParentFunctionName Operator="Equal" Value="AddLanguageProfile" />
            <NativeParameterIndex Operator="Equal" Value="4" />
          </And>
        </Condition>
        <Action Name="ConvertTo">
          <Parameter Key="Direction" Value="[In]" />
          <Parameter Key="ByRef" Value="False" />
          <Parameter Key="ManagedType" Value="LPArray" />
          <Parameter Key="MarshalAs" Value="(default)" />
          <Parameter Key="Attributes" Value="[SizeParamIndexOffset=+1]" />
        </Action>
      </Rule>
      <Rule Name="addlanguageprofile string2" Category="Signature">
        <Condition>
          <And>
            <NativeParentFunctionName Operator="Equal" Value="AddLanguageProfile" />
            <NativeParameterIndex Operator="Equal" Value="6" />
          </And>
         </Condition>
         <Action Name="ConvertTo">
           <Parameter Key="Direction" Value="[In]" />
           <Parameter Key="ByRef" Value="False" />
           <Parameter Key="ManagedType" Value="LPArray" />
           <Parameter Key="MarshalAs" Value="(default)" />
           <Parameter Key="Attributes" Value="[SizeParamIndexOffset=+1]" />
         </Action>
       </Rule>
       <Rule Name="GUID" Category="Type">
         <Condition>
           <And>
             <NativeName Operator="Equal" Value="GUID" />
           </And>
         </Condition>
         <Action Name="ResolveTo">
           <Parameter Key="AssemblyName" Value="mscorlib" />
           <Parameter Key="ManagedTypeFullName" Value="System.Guid" />
         </Action>
       </Rule>
    </Rules>
    

    Then I was trying to wrap the interfaces to be more .NET friendly:

    using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Globalization;

    using MSCTF;
    using System.Runtime.InteropServices;
    
    namespace TextService
    {
        public class LanguageProfiles
        {
            private ITfInputProcessorProfiles instance;
    
            public LanguageProfiles()
            {
                instance = new COMIFace<ITfInputProcessorProfiles>().CreateInstance();
            }
    
            public CultureInfo CurrentLanguage
            {
                get
                {
                    ushort plangid;
                    instance.GetCurrentLanguage(out plangid);
                    return CultureInfo.GetCultureInfo(plangid);
                }
                set
                {
                    instance.ChangeCurrentLanguage((ushort) value.LCID);
                }
            }
    
            public IEnumerable<TF_LANGUAGEPROFILE> ProfilesOfLanguage(CultureInfo culture)
            {
                IEnumTfLanguageProfiles ppenum;
                instance.EnumLanguageProfiles( (ushort) culture.LCID, out ppenum);
    
                TF_LANGUAGEPROFILE profile;
                uint fetch;
                do
                {
                    ppenum.Next(1, out profile, out fetch);
                    yield return profile;
                } while (fetch == 1 && profile.fActive != -1);
            }
    
            public void Register(ref Guid rclsid)
            {
                instance.Register(ref rclsid);
            }
    
            public void Unregister(ref Guid rclsid)
            {
                instance.Unregister(ref rclsid);
            }
    
            public void Add(ref Guid rclsid, CultureInfo info, string name, string icon)
            {
                var empty = Guid.Empty;
                instance.AddLanguageProfile(ref empty, (ushort)info.LCID, ref rclsid, name.ToUShortArray(), name.ULength(), icon.ToUShortArray(), icon.ULength(), 0);
            }
    
            public void Remove(ref Guid rclsid, CultureInfo info)
            {
                instance.RemoveLanguageProfile(ref rclsid, (ushort)info.LCID, ref rclsid);
            }
        }
    }
    

    The COMIFace class is just a helper to fetch the IIDs from registry, as I only found them in the .c file generated with the IDL compiler. Better would be to parse that file I think, but this works fine too. The given class works fine, I can register the service with following batch (check if regasm enabled in the C# project Options)

    set outtype=Debug
    set asmname=TextService
    cd bin\%outtype%
    gacutil /u %asmname%
    gacutil /i %asmname%.dll
    cd ..\..
    

    The problem I had is with all this that its a huge effort and there are many issues that are not worth the pain i think. I am not sure and wish anybody had some guidance. A point is, one always needs to follow some great site like this: TSF Aware blog but then u can write it in C++ anyways. One option might be C++/CLR, doing all the COM stuff and registration in C++ and the logic in C#, possible I guess

    A here is some test code which shows it basically works:

    var profiles = new LanguageProfiles();

    var ko_KR = CultureInfo.GetCultureInfo("ko-KR");
    
    foreach (var profile in profiles.ProfilesOfLanguage(ko_KR))
    {
        Console.WriteLine("clsid: " + profile.clsid + " lid: " + CultureInfo.GetCultureInfo(profile.langid) + " catid: " + profile.catid + " active: " + profile.fActive + " guidProf: " + profile.guidProfile);
        var id = profile.clsid;
    }