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

Using The UnknownWrapper Class Part 2.

1. Introduction.

1.1 In part 1 of this series of articles, I demonstrated how to use the UnknownWrapper class to pass an IUnknown interface pointer from a COM-visible managed object to a COM method.

1.2 The COM method specifically takes a VARIANT parameter and the COM-visible managed object has to be contained inside a System.Object instance.

1.3 Furthermore, the VARIANT parameter is designated as an “in” parameter.

1.4 Here in part 2, I shall expound further on the use of the UnknownWrapper in a COM method call. However, this time, the UnknownWrapper is used in a context where the IUnknown interface pointer is to be returned to the client code.

1.5 As we shall see later on, the UnknownWrapper class is most useful where the COM method call is late-bound. Early-bound COM method calls usually do not require the use of the UnknownWrapper.

2. New Method on Example COM Server Class.

2.1 We shall continue to use the sample COM Server first developed in part 1, i.e. CTestCOMServer, which implements an ITestCOMServer interface.

2.2 However, we shall add a new method to the ITestCOMServer interface :

[id(2), helpstring("method GetObject")] HRESULT GetObject([out] IUnknown** ppUnknownReceiver);

2.3 The updated ITestCOMServer interface in the IDL is :

[
	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);
	[id(2), helpstring("method GetObject")] HRESULT GetObject([out] IUnknown** ppUnknownReceiver);
};

2.4 The reader would have noticed by now that the parameter to GetObject() is a double pointer to an IUnknown interface. That is, the returned entity is an IUnknown interface pointer. Why is the parameter not a pointer to a VARIANT ? As in :

[id(2), helpstring("method GetObject")] HRESULT GetObject([out] VARIANT* pVarReceiver);

The reason is that the former signature (which takes the double pointer to an IUnknown interface) signifies that the return value is an IUnknown interface pointer whereas the latter (which takes the VARIANT pointer parameter) indicates that the return value is a VARIANT.

The implementation for GetObject() that we intend to provide will have the following design :

  • It will internally create a new COM object with interface ITestClass.
  • It will QueryInterface() this new COM object for its IUnknown interface and will return it via the “out” IUnknown double pointer.

The ITestClass interface will be covered in more detail later.

2.5 The full source codes for the GetObject() implementation is listed below :

STDMETHODIMP CTestCOMServer::GetObject(IUnknown** ppUnknownReceiver)
{
	AFX_MANAGE_STATE(AfxGetStaticModuleState());

	// TODO: Add your implementation code here

	// Initialize receiver.
	*ppUnknownReceiver = NULL;

	CComObject<CTestClass>* pCTestClass = NULL;

	CComObject<CTestClass>::CreateInstance(&pCTestClass);

	if (pCTestClass)
	{
		pCTestClass -> QueryInterface(IID_IUnknown, (void**)ppUnknownReceiver);		
	}

	return S_OK;
}

The following are important points regarding the code above :

  • It initialized the double IUnknown interface pointer “ppUnknownReceiver” to NULL.
  • It then instantiates a C++ ATL class named CTestClass which implements the ITestClass interface.
  • The IUnknown interface is queried from the CTestClass instance and is returned via the output “ppUnknownReceiver”.

2.6 The ITestClass interface is designed as follows :

[
	object,
	uuid(D7FE1D82-45B4-4EED-96B0-D7E26998F1A1),
	dual,
	nonextensible,
	helpstring("ITestClass Interface"),
	pointer_default(unique)
]
interface ITestClass : IDispatch
{
	[id(1), helpstring("method Method01")] HRESULT Method01(void);
};

The interface has only one sole method : Method01().

2.7 The coclass that will provide the implementation for this interface will simply display a message box :

STDMETHODIMP CTestClass::Method01(void)
{
	AFX_MANAGE_STATE(AfxGetStaticModuleState());

	// TODO: Add your implementation code here
	AfxMessageBox(TEXT("CTestClass::Method01()"));

	return S_OK;
}

2.8 The purpose of returning a CTestClass instance is to enable the client to call its Method01() method, thus confirming that a call to GetObject() has succeeded.

3. C# Client Code 1 – No UnknownWrapper Involvement.

3.1 In this section, I shall present C# client code that will not require the services of an UnknownWrapper in order to obtain a returned IUnknown interface pointer. This is the case where the COM method is early-bound.

3.2 The new and improved ITestCOMServer, when referenced in C#, is defined as follows :

namespace TestMFCCOMServer
{
    [Guid("0419912B-D21A-469E-BE81-501CAB3DD426")]
    [TypeLibType(4288)]
    public interface ITestCOMServer
    {
        [DispId(2)]
        object GetObject();
        [DispId(1)]
        void SetObject(object var);
    }
}

Notice that the return value for GetObject() is a System.Object instance. With reference to point 2.5, the returned double IUnknown interface pointer will be interpreted correctly by the interop marshaler. 

A System.Object will be created to contain the underlying COM object associated with the IUnknown double interface pointer. There is no need to involve the UnknownWrapper class.

3.3 The ITestClass interface is defined as follows in C# :

namespace TestMFCCOMServer
{
    [Guid("D7FE1D82-45B4-4EED-96B0-D7E26998F1A1")]
    [TypeLibType(4288)]
    public interface ITestClass
    {
        [DispId(1)]
        void Method01();
    }
}

3.4 The C++ implementation for ITestClass, i,e, CTestClass, will be represented in C# as follows :

namespace TestMFCCOMServer
{
    [TypeLibType(2)]
    [ClassInterface(0)]
    [Guid("B328AB77-F32D-45BC-917C-D36E927C2880")]
    public class TestClassClass : ITestClass, TestClass
    {
        public TestClassClass();

        [DispId(1)]
        public virtual void Method01();
    }
}

3.5 The C# code that will call GetObject() and obtain an instance of TestClassClass is listed below :

static void DoTest_GetObject()
{
    TestCOMServerClass com_server = new TestCOMServerClass();

    object obj;
    com_server.GetObject(out obj);

    TestClassClass test_class 
	= (TestClassClass)Marshal.CreateWrapperOfType(obj, typeof(TestClassClass));

    test_class.Method01();
}

The following are important points about the code above :

  • As mentioned in point 3.2, there is no need to involve an UnknownWrapper class instance to obtain the underlying TestClassClass instance.
  • Simply use the Marshal.CreateWrapperOfType() method to convert the System.Object returned from GetObject() to a TestClassClass instance.

3.6 When Method01() is called on the returned TestClassClass instance “test_class”, the following messge box will be displayed :

3.7 In the examples presented in the next section, we shall use late-binding to call the GetObject() method. This time, I will show that the use of the UnknownWrapper class will be quite necessary.

4. C# Client Code 2 – UnknownWrapper and ParameterModifier Involvement.

4.1 The equivalent of late-bound calls in managed code is accomplished through the use of the Type.InvokeMember() call.

4.2 All late-bound COM method calls involve the use of VARIANTs as parameters and the equivalent of this in managed code is the use of System.Object’s.

4.3 To obtain anything as a return value from a late-bound call, a System.Object must be used.

4.4 To illustrate the problem with obtaining an IUnknown interface pointer from a late-bound method call, look at the code below :

static void DoTest_GetObject_LateBound_Invalid()
{
    TestCOMServerClass com_server = new TestCOMServerClass();
    Object obj = new Object();
    Object[] args = new Object[] { obj };

    com_server.GetType().InvokeMember
	("GetObject", BindingFlags.Instance | BindingFlags.InvokeMethod | BindingFlags.Public, null, com_server, args);

    TestClassClass test_class = (TestClassClass)Marshal.CreateWrapperOfType(args[0], typeof(TestClassClass));

    test_class.Method01();
}

The above code certainly looks reasonable. However, when Marshal.CreateWrapperOfType() is called on args[0], which is meant to contain the returned COM object, we get a System.ArgumentException :

4.5 Astute observers will spot the problem : we must use the ParameterModifier to aid in calls where an argument is subject to modification in a late-bound COM method call.

4.6 OK, so we use a ParameterModifier :

static void DoTest_GetObject_LateBound_Invalid_Too()
{
    TestCOMServerClass com_server = new TestCOMServerClass();
    Object obj = new Object();
    Object[] args = new Object[] { obj };
    // Instantiate a ParameterModifier object and initialize 
    // it with the number of arguments that the method takes.
    ParameterModifier p = new ParameterModifier(1);
    // The 1st parameter is to be passed by reference.
    p[0] = true;
    // The ParameterModifier must be passed as the single element
    // of an array.
    ParameterModifier[] parameter_modifier = { p };

    com_server.GetType().InvokeMember
	(
		"GetObject", 
		BindingFlags.Instance | BindingFlags.InvokeMethod | BindingFlags.Public, 
		null, 
		com_server, 
		args, 
		parameter_modifier, 
		null, 
		null
	);

    TestClassClass test_class = (TestClassClass)Marshal.CreateWrapperOfType
	(args[0], typeof(TestClassClass));

    test_class.Method01();
}

This time, the late-bound call goes through and so will the call to Marshal.CreateWrapperOfType(). The call to Method01() will indeed succeed.

However, notice that I have named the above function DoTest_GetObject_LateBound_Invalid_Too() which indicates that it is still not exactly correct.

This is because without using the UnknownWrapper, what gets passed to the IDispatch::Invoke() method implementation of CTestCOMServer is actually a reference pointer to an IDispatch interface. That is, a double pointer to an IDispatch interface (VT_BYREF | VT_DISPATCH).

Such is the behaviour of the interop marshaler where a System.Object is involved. In the code above, “obj” is of type System.Object and so an IDispatch interface pointer VARIANT (VT_DISPATCH) will be created for it. If the ParameterModifier gets involved, a reference IDispatch interface pointer VARIANT (VT_BYREF | VT_DISPATCH) will be created.

This is not just a purist pusuit. If the ITestCOMServer interface is a pure dispinterface and the COM server is implemented in MFC, the call to Type.InvokeMember() will throw a System.Reflection.TargetInvocationException :

4.7 Listed below is the best code to use to make a late-bound COM method call in which an IUnknown interface pointer is to be returned :

static void DoTest_GetObject_LateBound()
{
    TestCOMServerClass com_server = new TestCOMServerClass();
    Object obj = new Object();
    UnknownWrapper unknown_wrapper = new UnknownWrapper(obj);
    Object[] args = new Object[] { unknown_wrapper };
    // Instantiate a ParameterModifier object and initialize 
    // it with the number of arguments that the method takes.
    ParameterModifier p = new ParameterModifier(1);
    // The 1st parameter is to be passed by reference.
    p[0] = true;
    // The ParameterModifier must be passed as the single element
    // of an array.
    ParameterModifier[] parameter_modifier = { p };

    com_server.GetType().InvokeMember
	(
		"GetObject", 
		BindingFlags.Instance | BindingFlags.InvokeMethod | BindingFlags.Public, 
		null, 
		com_server, 
		args, 
		parameter_modifier, 
		null, 
		null
	);

    TestClassClass test_class = (TestClassClass)Marshal.CreateWrapperOfType
	(args[0], typeof(TestClassClass));

    test_class.Method01();
}

Both the UnknownWrapper and the ParameterModifier classes are used and the call to Type.InvokeMember() will go through successfully and so will the call to Method01().

5. In Conclusion.

5.1 It is unfortunate, in my opinion, that COM usage (especially late-bound method calls) has become so complicated in managed code. 

5.2 This is where the UnknownWrapper and the ParameterModifier classes come in most handy.

5.3 I hope the reader has come to appreciate these wonderful helper classes.

 

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

One thought on “Using The UnknownWrapper Class Part 2.

  1. It’s been a pleasure reading your posts in codeproject. While you acknowledge your sources (e.g. Box) I find that YOUR articles and samples have provided me with a very practical hands on useful knowledge of COM.

    I’ve worked with programmers that know about COM, but when push comes to shove who cares that you can give a nice explanation of aggregation when you can’t even implement the simplest features.

    Your articles have lots of meat.

    I will be visiting often.

    Posted by Germán | June 7, 2012, 11:40 pm

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: