//
you're reading...
Programming Issues/Tips

Using The UnknownWrapper Class Part 1.

1. Introduction.

1.1 The UnknownWrapper class is a helper class designed to be exposed to COM as a VARIANT.

1.2 Furthermore, it is specifically designed to contain an IUnknown interface pointer extracted from a managed Object.

1.3 This article explains why such a wrapper is necessary and how to use it.

1.4 Part 1 will demonstrate the use of an UnknownWrapper in a call where an IUnknown interface pointer is passed to a COM method via a VARIANT as an “in” parameter.

1.5 Part 2 will focus on the use of an UnknownWrapper in a call where an IUnknown interface pointer is returned from a COM method call via a VARIANT as a “reference” parameter.

2. The Need for an UnknownWrapper.

2.1 The managed equivalent of the unmanaged VARIANT type is the System.Object.

2.2 Setting up the value for a Systerm.Object so that COM will receive the appropriate VARIANT is by and large straightforward.

2.3 By storing a managed string into an Object, the counterpart VARIANT will contain a BSTR (variant type VT_BSTR).

2.4 By storing a managed int, the VARIANT will contain an integer of 4 bytes (for a 32-bit system) (variant type I4).

2.5 Now, when it comes to IUnknown and IDispatch types, things get a little complicated. This may surprise the reader but there are no managed types that represent the COM IUnknown and the IDispatch interface types.

2.6 There are certainly functions that deal with IUnknown and IDispatch interfaces (e.g. Marshal.GetIUnknownForObject(), Marshal.GetIDispatchForObject()) but they are represented as IntPtr’s in managed code.

2.8 If the System.Object instance contains a COM object, then the counterpart VARIANT will contain an IDispatch interface pointer (if the COM object implements IDispatch) or an IUnknown interface pointer (if the COM object does not implement IDispatch).

2.9 If the System.Object instance contains a managed object, then the general direction implied by points 2.2 through 2.4 applies. However, what if you intend to pass the IUnknown interface pointer of a COM-visible managed object to a VARIANT ?

2.10 This is where the UnknownWrapper comes in handy. The next section demonstrates how this can be done.

3. Example COM Server.

3.1 In this section, we will design a COM interface and an implementation coclass that exposes a method that takes a VARIANT as an “in” parameter.

3.2 The interface is listed below :

[
	object,
	uuid(0419912B-D21A-469E-BE81-501CAB3DD426),
	dual,
	nonextensible,
	helpstring("ITestCOMServer Interface"),
	pointer_default(unique)
]
interface ITestCOMServer : IDispatch
{
	[id(1), helpstring("method SetObject")] HRESULT SetObject([in] VARIANT var);
};

The SetObject() method takes a single “in” VARIANT parameter.

3.3 The implementation for this method that we intend to provide will be designed as follows :

  • It expect this VARIANT to contain an IUnknown interface pointer.
  • With reference to point 2.9, we also expect this IUnknown interface to be derived from a managed object.
  • Every managed object is derived from the System.Object class and so it will implement the ToString() method. We intend to call this generic method from the SetObject() method.
  • Once SetObject() has received a VT_UNKNOWN VARIANT, it will QueryInterface() the IUnknown interface pointer for its IDispatch interface. 
  • Thereafter, it will use the IDispatch interface pointer to make a late-bound call to the generic ToString() method of the managed object.

3.4 The full source codes of the SetObject() method is listed below :

STDMETHODIMP CTestCOMServer::SetObject(VARIANT var)
{
	AFX_MANAGE_STATE(AfxGetStaticModuleState());

	// TODO: Add your implementation code here

	// Do not accept anything but a VARIANT
	// that contains an IUnknown interface pointer.
	if (V_VT(&var) != VT_UNKNOWN)
	{
		return E_INVALIDARG;
	}

	IUnknown* pUnknown = V_UNKNOWN(&var);

	if (pUnknown == NULL)
	{
		return E_POINTER;
	}

	// Obtain an IDispatch interface pointer from
	// the IUnknown interface pointer.	
	IDispatchPtr spIDispatch = NULL;

	pUnknown -> QueryInterface(IID_IDispatch, (void**)&spIDispatch);

	// Calling VariantClear() will call Release()
	// on the IUnknown interface pointer contained
	// inside var.
	VariantClear(&var);

	// Use the IDispatch interface pointer to make a late-bound
	// call to the object's ToString() method.
	if (spIDispatch)
	{	
	  DISPPARAMS	dp;
	  VARIANTARG	arg;
	  VARIANTARG	vr;

	  VariantInit(&vr);
	  VariantInit(&arg);

	  memset(&dp, 0, sizeof(DISPPARAMS));

	  // Obtain the ID of the ToString() method.
	  OLECHAR FAR*	rgszNames = L"ToString";
	  DISPID		dispid = 0;
	  EXCEPINFO		excepinfo;

	  spIDispatch -> GetIDsOfNames
	  ( 
		IID_NULL,                  
		&rgszNames,  
		1,          
		LOCALE_SYSTEM_DEFAULT,                   
		&dispid          
	  );

      // Finally make the call
	  UINT nErrArg;
	  HRESULT hr = spIDispatch -> Invoke
           (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_METHOD, &dp, &vr, &excepinfo, &nErrArg);			  

	  if (V_VT(&vr) == VT_BSTR)
	  {
		std::string strMessage;

		strMessage.resize(256, 0);

		sprintf (&(strMessage[0]), "%S", V_BSTR(&vr));

		AfxMessageBox((LPCTSTR)(strMessage.c_str()));
	  }
	}	

	return S_OK;
}

The following are pertinent points about the code above :

  • It is implemented by an MFC class named CTestCOMServer.
  • It accepts nothing short of a VARIANT that contains an IUnknown interface pointer.
  • Once an IUnknown interface pointer is confirmed, an IDispatch interface pointer is extracted from it using QueryInterface().
  • When the IDispatch interface pointer is successfully retrieved, the IDispatch interface pointer is used to make a late-bound call to the object’s ToString() method.

4. C# Client Code and the Use of UnknownWrapper.

4.1 In this section, the client code that uses the SetObject() method that we implemented in the last section.

4.2 The SetObject() method, when referenced in a C# project will have the following function signature :

[DispId(1)]
public virtual void SetObject(object var);

Note that the VARIANT parameter is interpreted as a System.Object in managed code. However, as will be seen shortly in the client code, we can actually use an instance of the UnknownWrapper class as the parameter to SetObject().

4.3 The managed object that we intend to pass to SetObject() is a string instance which has been initialized. The SetObject() method will thus display the string value that is contained inside the string instance.

4.4 The code is listed below :

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using TestMFCCOMServer;
using System.Runtime.InteropServices;

namespace CSConsoleClient01
{
    class Program
    {
        static void DoTest_WithManagedString()
        {
            TestCOMServerClass com_server = new TestCOMServerClass();
            string objString = "Hello World";
            UnknownWrapper unknown_wrapper = new UnknownWrapper((Object)objString);

            com_server.SetObject((object)unknown_wrapper);
        }

        static void Main(string[] args)
        {
            DoTest_WithManagedString();
        }
    }
}

The DoTest_WithManagedString() calls the SetObject() method. At runtime, when this function is called, the following message box will appear :

4.5 Note that if we had not used the UnknownWrapper class, then the VARIANT that SetObject() will receive will not contain an IUnknown interface pointer but a BSTR instead.

5. Custom Managed Class.

5.1 Here, I present another set of client code that uses SetObject() and this time, a custom COM-visible managed class is developed and an instance of it passed to SetObject.

5.2 The managed class is listed below :

[ComVisible(true)]
public class MyClass
{
    public override string ToString() 
    {
        return "MyClass instance";
    }
}

The following are pertinent points about the class above :

  • It is decorated with the ComVisibleAttribute.
  • Although all managed classes are COM-visible by default, this attribute is nevertheless required for our example because without it, its instances will not have any IDispatch implementation.
  • Note also that this class overrides the default Object.ToString() method.
  • When the SetObject() later invokes the ToString() method, this overridden ToString() version will be called.

5.3 The client code that uses MyClass in a call to SetObject() is listed below :

static void DoTest_WithMyClass()
{
    TestCOMServerClass com_server = new TestCOMServerClass();
    MyClass obj = new MyClass();
    UnknownWrapper unknown_wrapper = new UnknownWrapper((Object)obj);

    com_server.SetObject(unknown_wrapper);
}

When this code is run, the following message box will be displayed :

6. Using the UnknownWrapper Class in a Late-Bound Call Via Type.Invoke().

6.1 The UnknownWrapper is most useful in late-bound method calls using Type.InvokeMember(). This is because all parameters involved in Type.InvokeMember() must be System.Object instances.

6.2 If you intend to pass the IUnknown interface pointer of a managed class to a COM method call via a VARIANT, you would definitely need the UnknownWrapper.

6.3 The next example code demonstrates this using a MyClass instance in a call to SetObject() using Type.InvokeMember() :

static void DoTest_WithMyClass_LateBound()
{
    TestCOMServerClass com_server = new TestCOMServerClass();
    MyClass obj = new MyClass();
    UnknownWrapper unknown_wrapper = new UnknownWrapper((Object)obj);
    Object[] args = new Object[] { unknown_wrapper };

    Object objRet = com_server.GetType().InvokeMember
        (
          "SetObject",
          BindingFlags.InvokeMethod,
          null,
          com_server,
          args
        );
}

7. In Conclusion.

7.1 There are many other wrappers that are used in COM-interoperation.

7.2 These include DispatchWrapper, BStrWrapper, CurrencyWrapper, etc.

7.3 I hope to cover these other wrappers in future articles.

7.4 In Part 2 of this series of articles, I shall cover the use of the UnknownWrapper in a call to a COM method where an IUnknown interface pointer is returned via a VARIANT.

 

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: