Recently, I needed to write some C# code to dynamically modify an object inside an RTF document. I had done this in C++ before, so I knew it was possible, and started searching the internet for a boost (CP first!)
My hunt not only turned up fruitless, but there was outright misinformation out there. Somewhere, one poster asked how to do this and got the response that it wasn't possible. Now, that just got me all the more interested. The die-hard, problem-solving coder in me just wouldn't let that one rest without a fight.
So, here's the solution (and some interesting tidbits about the journey).
Basically, we want to be able to fill a
RichTextBox with any RTF document, and then manipulate the objects within that document by using their COM interfaces. If I were using MFC, I'd call the GetIRichEditOle function and start using the returned interface. .NET has plenty of features which are already part of the RichTextBox class, but it doesn't provide a way to do that task.
Interop to the Rescue
There are plenty of articles on CodeProject about Interop for the .NET platform, so I won't go into much detail about that here. However, this solution requires both platform invoke and COM Interop.
First, we have to fill in the gap missing in the RichTextBox control. To accomplish this, we need to use the SendMessage Windows API call. We'll use the EM_GETOLEINTERFACE message, which is specific to the RichEdit control. Once we have the appropriate interop code setup (see the downloadable project), this can be done with the following code. (Note that we need to be careful to handle the
LPARAM parameter correctly.)
IntPtr ptr = Marshal.AllocCoTaskMem(Marshal.SizeOf(typeof(IntPtr)));
if (0 != API.SendMessage(RichTextBox.Handle,
Messages.EM_GETOLEINTERFACE, IntPtr.Zero, ptr))
IntPtr pRichEdit = Marshal.ReadIntPtr(ptr);
throw new Exception("EM_GETOLEINTERFACE failed.");
Now that we have an
IntPtr that is the
IRichEditOle interface, we can do something with it. This is where the COM Interop stuff really kicks in.
Wrapping the IRichEditOle interface
To use the
IRichEditOle interface we just obtained, we need some way for C# to call the methods on that interface. Normally, we would find the dll from which that interface is defined, add it as a reference to the VS.NET project, and just start using the generated library. That is either very difficult or not really possible with the information I've found, BUT we can accomplish the same task manually.
The following code is the C# representation of the
IRichEditOle interface. The
InterfaceType, and Guid attributes are needed in order for the interface to work, so don't leave them out if you're wrapping another interface in a similar fashion. Browsing the C++ source code that is supplied with VS.NET is the best way to get this information. (Note that I didn't test all of these methods, and didn't even bother trying to use the last few. I'm focusing almost solely on the GetObject method.)
public interface IRichEditOle
int GetClientSite(IntPtr lplpolesite);
int GetObject(int iob, REOBJECT lpreobject,
int InsertObject(REOBJECT lpreobject);
int ConvertObject(int iob, CLSID rclsidNew, string lpstrUserTypeNew);
int ActivateAs(CLSID rclsid, CLSID rclsidAs);
int SetHostNames(string lpstrContainerApp, string lpstrContainerObj);
int SetLinkAvailable(int iob, int fAvailable);
int SetDvaspect(int iob, uint dvaspect);
int HandsOffStorage(int iob);
int SaveCompleted(int iob, IntPtr lpstg);
int ContextSensitiveHelp(int fEnterMode);
Now, we have an interface! But then what? Well, since my application only needs the
GetObject method, we've got to handle that
REOBJECT class. This is done with the following C# class (it could have been a structure, but then you'd need to pass it as a "ref" parameter to the
GetObject function in
public class REOBJECT
public int cbStruct = Marshal.SizeOf(typeof(REOBJECT));
public int cp = 0;
public CLSID clsid = new CLSID();
public IntPtr poleobj = IntPtr.Zero;
public IntPtr pstg = IntPtr.Zero;
public IntPtr polesite = IntPtr.Zero;
public SIZEL sizel = new SIZEL();
public uint dvaspect = 0;
public uint dwFlags = 0;
public uint dwUser = 0;
The CLSID member could be handled better, but I provided a structure with the necessary byte size to fill that gap. We already know the CLSID, anyway.
Putting it all together
So, we have a way to get the
IRichEditOle pointer value into an
IntPtr variable and we have a C# interface that wraps
IRichEditOle. Now, we can fill in the gap in the first code section of this article. Using GetTypedObjectForIUnknown, we can take that
IntPtr and wrap it in our interface! But first, we need to make sure that we have the honest-to-goodness
IRichEditOle interface pointer - not a pointer to another interface in that same COM object. To do that, we'll use QueryInterface, supplying the GUID that we already used when wrapping the
IRichEditOle interface above.
Guid guid = new Guid("00020D00-0000-0000-c000-000000000046");
Marshal.QueryInterface(pRichEdit, ref guid, out this.IRichEditOlePtr);
this.IRichEditOleValue = (IRichEditOle)Marshal.GetTypedObjectForIUnknown(
if (this.IRichEditOleValue == null)
throw new Exception("Failed to get the object wrapper for the interface.");
Putting that all together into one function, we've got a nifty way to get the
IRichEditOle pointer from a RichText Box.
To make it all nice and easy, I've derived a
RichTextBoxPlus class from the
RichTextBox class. It is part of the RichTextBoxPlus.dll project, which also has the needed code for wrapping and using the
IRichEditOle interface. Add that dll as a reference to any project, then use
RichTextBoxPlus in place of RichTextBox any time you need the
The demo project
There is a (lovely) demo application in the download, which loads an RTF document containing a Windows Media Player object. (I tried to find an object that everyone would have access to.) It then uses the
IRichEditOle object to get an interface to WMPPlayer, and sets the URL to a MIDI file in the executable's folder. The "Play song" menu lets you use the
IRichEditOle interface to start the song, then switches to "Stop song" so you can call the
stop function of the WMPPlayer object. (If you want to use the buttons on Windows Media Player itself, you may need to double-click the object first. This has something to do with the RTF control itself.)
If you have any questions, comments, or improvements, don't hesitate to contact me.