A windowless rich edit control, or a text services object, which provides the functionality of a rich edit control without providing the window, is usually accessed from the native C/C++ or C++/CLI modules. This article illustrates how this can be done directly from C# assembly compiled as AnyCPU.
Windowless rich edit controls API is declared in the TextServ.h header of Windows SDK. There is a pair of interfaces,
ITextHost, and the functions for creating and destroying text services objects. A number of examples written in the native code exist that are working with this API. But a few attempts of using it from C# that I came across were apparently unsuccessful. What is the problem? The developers of these interfaces, in their wisdom, did not add
__stdcall modifiers to the methods. As a result, the methods follow default
__thiscall calling convention, and the interfaces, being quite normal
IUnknown-derived interfaces, are not friendly to .NET interop, because there is currently no way to specify calling convention for the methods of managed interfaces. However, it can be done for delegates. Thus, we can arrange a managed representation of the interface virtual tables to be marshalled from and to the unmanaged code, and use it in a kind of custom RCW. I'll provide a minimal working example, leaving its extension to the interested parties.
Using the Code
ITextHost is a callback interface that is passed from a client to the text services. In this case, an instance of the pseudo object implementing the interface should be created in the unmanaged memory. At first, we declare a formatted class containing managed methods marshaled to the pointers to unmanaged functions with the appropriate calling convention:
class ITextHostVTable : IUnknownMethods
delegate IntPtr TxGetDCDelegate(IntPtr _this);
delegate int TxReleaseDCDelegate(IntPtr _this, IntPtr hdc);
IUnknownMethods class contains three delegates for the methods of
IUnknown, which use standard calling convention.
_this is a pointer to the unmanaged instance that is passed to the methods as a first argument. Then we define a class that implements the interface. It should contain
static methods with exactly the same signatures as the delegates in
ITextHostVTable. For the purpose of this article, I use a
static class that does not implement reference count:
static class TextHost
public static int QueryInterface(IntPtr _this, ref Guid iid, out IntPtr ppv)
if (iid == IID_ITextHost || iid == IID_IUnknown)
ppv = _this;
ppv = IntPtr.Zero;
public static uint AddRef(IntPtr _this)
public static uint Release(IntPtr _this)
public static IntPtr TxGetDC(IntPtr _this)
public static int TxReleaseDC(IntPtr _this, IntPtr hdc)
TextHost's methods are assigned to the delegates in an instance of
ITextHostVTable, the latter then is marshalled to the unmanaged memory. Marshal automatically creates unmanaged wrappers for the delegates. We also need to create an unmanaged object pointing to this virtual table. Since single instance of the
static class is used, we can simply write a pointer to the virtual table in the same memory block:
vtable = new ITextHostVTable();
var ptrsize = Marshal.SizeOf(typeof(IntPtr));
Unmanaged = Marshal.AllocHGlobal(ptrsize + Marshal.SizeOf(vtable));
var pVTable = IntPtr.Add(Unmanaged, ptrsize);
Marshal.StructureToPtr(vtable, pVTable, false);
It represents an unmanaged instance of the
ITextHost implementation that can be passed to the
var hr = CreateTextServices(IntPtr.Zero, TextHost.Unmanaged, out var pUnk);
The function returns a pointer to
IUnknown that can be queried for
ITextServices interface. A managed representation of its virtual table is declared in the same way as
ITextHostVTable, but since the unmanaged table already exists at the address returned by the
QueryInterface, we just need to marshal it to the managed structure:
_this = pITextServices;
vtable = (ITextServicesVTable)Marshal.PtrToStructure(Marshal.ReadIntPtr(_this),
And that is almost all. Just wrap the
vtable with the suitable class. The sample class invokes a couple of methods to set and draw "
Hello, world!" text.
Points of Interest
I did not elaborate on marshaling the arguments of the interfaces' methods, only those that were necessary for the example were detalized. You can change the marshaling to whatever you need.
If multiple instances of
ITextHost implementations are necessary, a separate unmanaged memory block should be allocated for each of them, containing a pointer to the static virtual table and the data that can be used to convert the unmanaged
_this pointer to the managed reference.
It seems that
ShutdownTextServices function is currently not implemented, and I am not sure what is the correct way to dispose the
TextServices object and
ITextHost implementation associated with it. So I just release the
ITextService interface. It needs to be explored further.
You can also consider finding and dynamically loading necessary rich edit control implementation. Text services are available from version 2.0.