Using Managed Controls as ActiveX Controls

Can you use a managed usercontrol in an Office document in the same way that you can use a native ActiveX control – all without using VSTO? Some time ago, I posted about how to use native ActiveX controls within a doc-level VSTO solution, by wrapping them in managed usercontrols. A reader (Casey) asked the question, “what about going the other way?” The answer is “maybe”. Or, to be more precise, “up to a point, but the technique is unsupported and probably won't work in most scenarios”. If you still want to play with this a bit, here’s how to start…

First, create a managed usercontrol project – either a Windows Forms class library or control library project. Use the usercontrol designer to design your custom usercontrol the way you want it (using any standard controls you like).

Second, in the project properties, on the Build tab, select the “Register for COM interop” option. This will register any COM-visible classes in the project when you build it, and will also build a COM typelib and register it.

Third, attribute your usercontrol class to make it COM-visible. Also, specify a GUID, to avoid getting a fresh one on each build; and specify that you want the compiler to generate a dispatch class interface for the control.

[ComVisible(true)]

[Guid("0F2A2E9D-C79E-4b1b-9AF7-2D1487F29041")]

[ClassInterface(ClassInterfaceType.AutoDispatch)]

public partial class ManagedOcx : UserControl

{

Next, we need to provide some additional registry entries: Control, MiscStatus, TypeLib and Version. You can do this with a .REG script, but it’s generally better to write functions that will be called on registration/unregistration (attributed with ComRegisterFunction/ComUnregisterFunction).

Control is an empty subkey. TypeLib is mapped to the GUID of the TypeLib (this is the assembly-level GUID in the assemblyinfo.cs). Version is the major and minor version numbers from the assembly version. The only mildly interesting subkey is MiscStatus. This needs to be set to a value composed of the (bitwise) values in the OLEMISC enumeration, documented here. To make this enum available, add a reference to Microsoft.VisualStudio.OLE.Interop (and a suitable ‘using’ statement for the namespace).

[ComRegisterFunction]

static void ComRegister(Type t)

{

    string keyName = @"CLSID\" + t.GUID.ToString("B");

    using (RegistryKey key =

        Registry.ClassesRoot.OpenSubKey(keyName, true))

    {

        key.CreateSubKey("Control").Close();

        using (RegistryKey subkey = key.CreateSubKey("MiscStatus"))

        {

            // 131456 decimal == 0x20180.

            long val = (long)

                ( OLEMISC.OLEMISC_INSIDEOUT

                | OLEMISC.OLEMISC_ACTIVATEWHENVISIBLE

                | OLEMISC.OLEMISC_SETCLIENTSITEFIRST);

            subkey.SetValue("", val);

        }

        using (RegistryKey subkey = key.CreateSubKey("TypeLib"))

        {

            Guid libid =

                Marshal.GetTypeLibGuidForAssembly(t.Assembly);

            subkey.SetValue("", libid.ToString("B"));

        }

        using (RegistryKey subkey = key.CreateSubKey("Version"))

        {

            Version ver = t.Assembly.GetName().Version;

            string version =

              string.Format("{0}.{1}", ver.Major, ver.Minor);

            subkey.SetValue("", version);

        }

    }

}

[ComUnregisterFunction]

static void ComUnregister(Type t)

{

    // Delete the entire CLSID\{clsid} subtree for this component.

    string keyName = @"CLSID\" + t.GUID.ToString("B");

    Registry.ClassesRoot.DeleteSubKeyTree(keyName);

}

Build the project, then run Excel. From the Developer tab, go to the Controls group, and click the Insert button. This drops down a little gallery of available controls. The one in the bottom right-hand corner pops up the More Controls dialog, which offers a list of all suitably-registered ActiveX controls. You should find your custom control in this list.

Note: this seems to work OK for Excel (with the very limited testing I've done), partly works with PowerPoint, but fails miserably with Word. Possibly, some more of the OLEMISC values might improve this; possibly there are some messages we need to hook; possibly there are some more interfaces we need to implement – I haven’t tried. Of course, the VSTO runtime has a nice set of hosting controls that enable this behavior for Excel and Word, but these are not usable outside the context of a VSTO solution. As I said, this is an entertaining avenue to explore, but it remains unsupported. The fact that I’ve only barely got it to work in a very limited way should tell you that this is probably not a technique you want to use in any serious way.