//
you're reading...
.NET/COM/ActiveX Interop, HTML

Loading Parameters for a Managed COM Server in HTML.

1. Introduction.

1.1 ActiveX and COM objects may be loaded onto a HTML page via the <object> tag.

1.2 Additionally, parameters may be passed to the ActiveX/COM objects using the <param> tag (see HTML <param> Tag).

1.3 But what if the COM object is instantiated from a managed class exported by COM interop ? In this blog, I shall demonstrate how to receive <param> tags and values from inside a managed class instantiated from inside a HTML page.

2. How Parameters are Read by an Object in HTML.

2.1 Let’s say we have the following <object> tag inside a HTML file being run by the Internet Explorer (IE) :

<object classid="clsid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" id="TestObject">
   <param name="SomeProperty1" value="Value1" />
   <param name="SomeProperty2" value="Value2" />
   <param name="SomeProperty3" value="Value3" />
 </object>

2.2 At runtime, IE will internally instantiate an object that implements the IPropertyBag interface. This IPropertyBag object stores properties referred to by name. Each property value is returned via a VARIANT. See IPropertyBag Interface for more details.

2.3 The internal IPropertyBag object created by IE will be initialized to contain the 3 properties listed in the HTML code above : “SomeProperty1”, “SomeProperty2” and “SomeProperty3” with values as indicated in their respective <param> tag “value” attributes.

2.4 Right after instantiating the COM object with the specified CLSID, IE will query this object for an IPersistPropertyBag interface. See IPersistPropertyBag Interface for more details.

2.5 If this interface cannot be found, no parameter loading (for the object) will take place.

2.6 If this interface is found, IE will call its Load() method. As parameter for the Load() method, IE will pass the IPropertyBag object mentioned in point 2.2 above.

2.7 The object then calls the IPropertyBag::Read() method to obtain the values of required named parameters :

HRESULT Read
(
    LPCOLESTR pszPropName,
    VARIANT *pVar,
    IErrorLog *pErrorLog
);

The name of the parameter is supplied in the first parameter (pszPropName) and the value is returned in the second “out” parameter (pVar). IE will return all values as BSTRs. Strings that are meant to be taken as numerical in nature must be converted by the object.

3. Implementing the IPersistPropertyBag Interface for a Managed Class.

3.1 Hence in order for a managed class to provide instances which is able to receive runtime parameters from a HTML page through IE, it needs to implement the IPersistPropertyBag interface.

3.2 It will also have to be able to interact with an object which implements the IPropertyBag interface.

3.3 Towards this end, we need to provide proper declarations for these 2 interfaces in managed code. This will be shown in the example code below.

3.4 The following is a full code listing for a class library project that exports a managed C# class CSTestCOMServer that implements IPersistPropertyBag :

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices;

namespace CSTestCOMServer
{
    [ComImport()]
    [Guid("0000010c-0000-0000-C000-000000000046")]
    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    public interface IPersist
    {
        void GetClassID(out Guid pClassID);
    };

    [ComImport()]
    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    [Guid("37D84F60-42CB-11CE-8135-00AA004BB851")]
    public interface IPersistPropertyBag : IPersist
    {
        new void GetClassID(out Guid pClassID);

        void InitNew();

        void Load
        (
            [In, MarshalAs(UnmanagedType.IUnknown)] object pPropBag, // IPropertyBag*
            [In, MarshalAs(UnmanagedType.IUnknown)] object pErrorLog // IErrorLog*
        );

        int Save
        (
            /* [in] */ ref object pPropBag, // IPropertyBag*
            /* [in] */ bool fClearDirty,
            /* [in] */ bool fSaveAllProperties
        );
    };

    [ComImport()]
    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    [Guid("55272A00-42CB-11CE-8135-00AA004BB851")]
    public interface IPropertyBag
    {
        void Read
        (
            [In, MarshalAs( UnmanagedType.BStr )] String bstrPropName,
            [In, Out] ref object pVar, // VARIANT __RPC_FAR *pVar
            [In] object pErrorLog // IErrorLog* pErrorLog
        );

        void Write
        (
            /* [in] */ string pszPropName,
            /* [in] */ ref object pVar // VARIANT* pVar
        );
    };

    [ComVisible(true)]
    [GuidAttribute("7D66572F-30C6-4acf-A083-FC71DC95363E")]
    [ProgId("CSTestCOMServer.CSTestCOMServer")]
    [ClassInterface(ClassInterfaceType.AutoDual)]
    public class CSTestCOMServer : IPersistPropertyBag
    {
        #region IPersistPropertyBag implementation.
        // IPersist
        public void GetClassID(out Guid pClassID)
        {
            pClassID = GetType().GUID;
        }

        // IPersistPropertyBag
        public void InitNew()
        {
        }

        public void Load
        (
            /* [in] */ object pPropBag, // IPropertyBag*
            /* [in] */ object pErrorLog // IErrorLog*
        )
        {
            IPropertyBag pIPropertyBag = (IPropertyBag)pPropBag;
            object var = null;

            pIPropertyBag.Read("StringProperty", ref var, pErrorLog);

            m_StringProperty = (string)var;

            // Don't start Iris Camera threads yet.
            pIPropertyBag.Read("IntegerProperty", ref var, pErrorLog);

            m_IntegerProperty = Convert.ToInt32(var);
        }

        public int Save
        (
            /* [in] */ ref object pPropBag, // IPropertyBag*
            /* [in] */ bool fClearDirty,
            /* [in] */ bool fSaveAllProperties
        )
        {
            return 0;
        }
        #endregion

        public void DisplayProperties()
        {
            MessageBox.Show("StringProperty : " + m_StringProperty + "\r\n IntegerProperty : " + Convert.ToString(m_IntegerProperty));
        }

        private string m_StringProperty;
        private Int32 m_IntegerProperty;
    }

}

The following is a summary of the code listing above :

  • Declarations for the IPersist, IPersistPropertyBag and IPropertyBag interfaces are listed.
  • These interfaces are all defined outside of the CLR and hence they need to be marked with the ComImportAttribute.
  • Because the CSTestCOMServer class must be instantiated in HTML and its methods executed from script (e.g. JavaScript), the methods will be invoked via IDispatch.
  • Hence it must either expose a pure dispinterface or expose a dual interface. For this reason, it must be marked with the ClassInterfaceAttribute with either ClassInterfaceType.AutoDispatch or ClassInterfaceType.AutoDual set as the argument.
  • The CSTestCOMServer class exposes only one method named DisplayProperties() that displays the values of 2 properties m_StringProperty (a string) and m_IntegerProperty (an integer) in a message box.
  • The values for the 2 properties are acquired through the IPersistPropertyBag::Load() method which is called by IE.
  • Note how the Load() method uses the input IPropertyBag object (passed as a parameter to Load()) to read the 2 values for the properties.
  • Besides the Load() method, the other methods of the IPersistPropertyBag interface are not used by IE and so they need only trivial implementations.
  • The Save() method, for instance, is not used because IE will not summon an object in script to save its properties values.

The above code may be compiled into a DLL which can then be registered to COM using REGASM.EXE.

3.5 The following is a test HTML that instantiates CSTestCOMServer and invokes its DisplayProperties() method from a button :

<html>

<object classid="clsid:7D66572F-30C6-4acf-A083-FC71DC95363E" id="CSTestCOMServer">
   <param name="StringProperty" value="Hello World" />
   <param name="IntegerProperty" value=100 />
</object>

<script type="text/javascript">
      function DisplayProperties()
      {
        CSTestCOMServer.DisplayProperties();
      }
</script>

<body>

<button type="button" onclick="DisplayProperties();" >Click Me!</button>

</body>

</html>

4. In Conclusion.

4.1 The COM Interop System of the .NET Framework is certainly robust and thorough in ensuring that COM interfaces are implementable in managed code.

4.2 Manual definition of COM interfaces in a managed language is sometimes necessary but as can be seen in this article, this is quite straight-forward and is not necessarily esoteric.

4.3 By implementing required COM interfaces, a managed class can be binarily-compatible with existing applications (e.g. HTML) and do not require special coding or recompilation.

Advertisements

About Lim Bio Liong

I've been in software development for nearly 20 years specializing in C , COM and C#. It's truly an exicting time we live in, with so much resources at our disposal to gain and share knowledge. I hope my blog will serve a small part in this global knowledge sharing network. For many years now I've been deeply involved with C development work. However since circa 2010, my current work has required me to use more and more on C# with a particular focus on COM interop. I've also written several articles for CodeProject. However, in recent years I've concentrated my time more on helping others in the MSDN forums. Please feel free to leave a comment whenever you have any constructive criticism over any of my blog posts.

Discussion

No comments yet.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: