HOW TO: Write a Custom Marshaler for COM Interoperation by Using Visual C# .NET (307713)



The information in this article applies to:

  • Microsoft Visual C# .NET (2002)

This article was previously published under Q307713

SUMMARY

This article describes how to create a custom marshaler that maps a specific unmanaged interface to a managed interface and vice versa. The custom marshaler that is implemented in this article permits native clients to see any managed server by exposing the unmanaged interface that native clients understand. Also, the custom marshaler permits the managed clients of a native Component Object Model (COM) server to see the interface that is exposed by the COM server as a managed interface.

Back to the top

MORE INFORMATION

This article discusses the context where the custom marshaler is required. This article provides two different scenarios.

Scenario 1

The first scenario discusses custom marshaling from a .NET client to a COM server. In this scenario, you must have:
  • A COM Server that implements the IUnmanagedServer interface.
  • An IUnmanagedServer that has a single method that is named UnmanagedServerMethod. This method requires a single parameter of type IUnmanaged as input.
  • A .NET client that has an instance of type IManaged.
  • A .NET client that passes the IManaged instance to the UnmanagedServerMethod of the COM server.
The code sample that follows is an excerpt of the IUnmanaged interface:
interface IUnmanaged : IUnknown
{
	HRESULT UnmanagedMethod([out, retval] LONG *RetVal);
};
The managed interface ( IManaged) that is semantically similar to the unmanaged interface (IUnmanaged) is:
public interface IManaged
{
	long ManagedMethod();
}

Scenario 2

The second scenario discusses custom marshaling from a COM client to a .NET server. In this scenario, you must have:
  • A .NET server that implements a single method that is named ManagedServerMethod. This method requires a single parameter of type IManaged as input.
  • A COM client that has an instance of type IUnmanaged.
  • A COM client that passes the IUnmanaged instance to the ManagedServerMethod of the .NET server.
When you try to map different types that are semantically similar, you must create a wrapper class. A wrapper class holds an instance of one type but exposes the interface for another type. The types that are mapped must be similar so that the wrapper class can maintain the semantics that are defined for the interfaces. This article describes how to use a wrapper class for the scenario where the .NET client accesses the COM server. A wrapper class that is named ManagedIUnmanaged wraps an instance of IManaged but exposes the IUnmanaged interface.

Similarly, when the COM client accesses the .NET server, a wrapper class that is named ComUnmanged wraps an instance of IUnmanaged but exposes the IManaged interface.

The problem of mapping a managed interface to an unmanaged interface has a solution. The solution is to use wrapper classes. Custom marshaler automates and hides when the wrapper is created for the type that is marshaled from the umanaged to the managed domain and vice versa.

Look at the Stream class and the IStream COM interface. You can see that conversion mechanisms are needed. The conversion mechanisms permits an unmanaged object to implement certain interface requirements that must be mapped to a wrapper object. That wrapper object then exposes a different set of interfaces. The .NET Stream class is semantically similar to the IStream COM interface. The abstractions that are provided in the Stream class and the IStream COM interface are similar. Both IStream and Stream permit reading, writing, and seeking over a buffer. A .NET class that takes an instance of the Stream class can logically be passed an instance of the COM object that implements the IStream interface. To do this, the .NET Framework must provide a mechanism where a wrapper object of the Stream class can be created. This then redirects the Stream class method that calls the embedded IStream COM object. The mechanism that permits a conversion to occur is the CustomMarshaler class that is provided in the .NET Framework.

In both scenarios, the custom marshaler is constructed depending on the direction that the marshaling is performed. If the marshaling is performed from the native client to a managed server, then you must define the custom marshaler to marshal from the native to the managed domain only. If custom marshaling is marshaled in both directions, then you must combine the definition of the custom marshaler as described in both scenarios.

Back to the top

Custom Marshaling from a .NET Client to a COM Server

Steps to Create an ATL COM Server

This section shows you how to create an ATL COM server that implements the IUnmanagedServer interface.
  1. Open Microsoft Visual Studio .NET.
  2. On the File menu, point to New and then click Project.
  3. In the New Project dialog box, click Visual C++ Projects under Project Types and then click ATL Project under Templates.
  4. In the Name text box, type ComServer.
  5. Click OK.
  6. In the Wizard dialog box, click the Application Settings tab.
  7. Click to clear the Attributed check box and then click Finish.
  8. In Solution Explorer, right-click the ComServer (Project) node, point to Add, and then click Add Class.
  9. In the Add Class dialog box, click ATL Simple Object under Templates and then click Open.
  10. In the ATL Simple Object Wizard dialog box, type UnmanagedServer in the Short name text box and then click Finish.
  11. In Solution Explorer, expand Source Files.
  12. Double-click ComServer.idl to open the .idl file in an editor. Add the following interface definition to the .idl file.
    [
    	object,
    	uuid(XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX),
    	helpstring("IUnmanaged Interface"),
    	pointer_default(unique)
    ]
    interface IUnmanaged : IUnknown
    {
    	HRESULT UnmanagedMethod([out, retval] LONG *RetVal);
    };
    Replace the contents of the uuid attribute with the GUID that is generated from the guidgen tool. In the library, add the IUnmanaged interface as shown in the earlier code. This exposes the interface through the type library that is generated. Add the following code to the library statement in the .idl file:
    library ComServerLib
    {
    	importlib("stdole2.tlb");
    	//Add the following line to the idl file.
    	interface IUnmanaged;
    	...
    };
    
  13. In Class view, expand ComServer.
  14. Right-click IUnmanagedServer, point to Add, and then click Add Method.
  15. In the Method Name text box, type UnmanagedServerMethod.
  16. Click to select the Parameter attributes check box.
  17. In the Parameter type combo box, type IUnmanaged*.
  18. In the Parameter text box, type unmanaged and then click Add.
  19. Click Finish.
  20. In Solution Explorer, double-click UnmanagedServer.cpp.
  21. Replace the default code that is generated for the CUnmanagedServer::UnmanagedServerMethod method with the code that follows:
    LONG data;
    HRESULT hr;
    hr = unmanaged->UnmanagedMethod(&data);
    if(FAILED(hr))
    	return hr;
    printf("The output of the IUnmanaged::UnmanagedMethod is : %d", data);
    return S_OK;
  22. On the Build menu, click Build Solution to build the ATL COM Server application.
Back to the top

Steps to Create a .NET Client

This section shows you how to create a .NET client that accesses the COM server and then passes the UnmanagedMethod of the server with an IManaged instance:
  1. On the File menu, point to New and then click Project.
  2. In the New Project dialog box, click Visual C# Projects under Project Types and then click Console Application under Templates.
  3. Click to select the Add to Solution option and then click OK.
  4. In Solution Explorer, expand ConsoleApplication1, right-click References, and then click Add Reference.
  5. In the Add Reference dialog box, click the COM tab.
  6. Click ComServer 1.0 Type Library under Component Name and then click Select.
  7. Click OK.
  8. Replace the default code in Class1.cs with the code that follows:
    using System;
    using System.Runtime.InteropServices;
    
    public interface IManaged
    {
    	long ManagedMethod();
    }
    
    public class ManagedImplementation : IManaged
    {
    	public long ManagedMethod()
    	{
    		return 200;
    	}
    }
    
    class Class1
    {
    	static void Main(string[] args)	
    	{
    		comserverLib.IUnmanagedServer server = new comserverLib.UnmanagedServerClass();
    		IManaged managed = new ManagedImplementation();
    		server.UnmanagedServerMethod(managed);
    	}
    }
    
Back to the top

Steps to Create a Wrapper

This section shows you how to create a wrapper class that wraps an instance of the IManaged type but exposes the IUnmanaged interface.

To create a wrapper class, follow these steps:
  1. Add the code that follows to Class1.cs:
    [
    	ComImport,
    	Guid("XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"),
    	InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)
    ]
    public interface UCOMIUnmanaged
    {
    	long UnmanagedMethod();
    }
    
    public class ManagedIUnmanaged : UCOMIUnmanaged
    {
    	IManaged managedObj;
    
    	public ManagedIUnmanaged(IManaged managedObj)
    	{
    		if(managedObj != null)
    			this.managedObj = managedObj;
    		else
    			throw new ArgumentNullException("The instance of IManaged is null.");
    	}
    
    	public long UnmanagedMethod()
    	{
    		return  managedObj.ManagedMethod();
    	}
    }
    
    Replace the contents of the Guid attribute with the contents of the uuid attribute. The uuid attribute is generated for the IUnmanaged interface in the ComServer.idl file.

Back to the top

Steps to Create a Custom Marshaler

This section shows you how to create a custom marshaler that maps a managed interface (IManaged) to an unmanaged interface (IUnmanaged):

The custom marshaler must implement the ICustomMarshaler interface. The MarshalNativeToManaged method is called when an unmanaged interface is mapped to a managed interface. Because the current scenario marshals from the managed to the native domain, the current implementation of the MarshalNativeToManaged method returns null. The MarshalManagedToNative method wraps the native object in a wrapper object (ManagedIUnmanaged) that was defined in the previous section. The MarshalManagedToNative method is called when a managed interface is mapped to an unmanaged interface. The CleanUpNativeData method is called when the object that is associated with the native interface is released.

Similarly, the CleanUpManagedData method is called when the object that is associated with the managed interface is released. Because the managed object is garbage collected, the implementation for the CleanUpManagedData method is empty.

The GetNativeDataSize method returns -1.

Although the GetInstance method is not defined for the ICustomMarshaler interface, you must define the GetInstance static method that returns an instance of ICustomMarshaler. This static method is called by the .NET runtime to create an instance of the custom marshaler.
  1. Add the code that follows to Class1.cs:
    public class SampleMarshaler : ICustomMarshaler
    {
    	static SampleMarshaler marshaler;
    
    	public object MarshalNativeToManaged(IntPtr pNativeData)
    	{
    		return null;
    	}
    	
    	public IntPtr MarshalManagedToNative(object managedObj)
    	{
    		if(managedObj == null)
    			return IntPtr.Zero;
    		if(!(managedObj is IManaged))
    			throw new MarshalDirectiveException("This custom marshaler must be used on a IManaged derived type.");
    
    	ManagedIUnmanaged customObject = new ManagedIUnmanaged((IManaged)managedObj);
    	return Marshal.GetComInterfaceForObject(customObject, typeof(UCOMIUnmanaged));
    	}
    
    	public void CleanUpNativeData(IntPtr pNativeData)
    	{
    		Marshal.Release(pNativeData);
    	}
    
    	public void CleanUpManagedData(object managedObj)
    	{
    
    	}
    
    	public int GetNativeDataSize()
    	{
    		return -1;
    	}
    
    	public static ICustomMarshaler GetInstance(string cookie)
    	{
    		if(marshaler == null)
    			return marshaler = new SampleMarshaler();
    		return marshaler;
    	}
    }
  2. Associate the custom marshaler with the marshaling of the instance of the IManaged interface to an instance of the IUnmanaged interface. Add the code that follows to Class1.cs to associate the custom marshaler:
    [
    	ComImport,
    	Guid("XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"),
    	InterfaceTypeAttribute(ComInterfaceType.InterfaceIsDual)
    ]
    public interface UCOMIUnmanagedServer
    {
    	void UnmanagedServerMethod(
    		[MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef=typeof(SampleMarshaler)), In]
    		object unmanaged);
    }
    The Guid that you specify with the Guid attribute is the same as the uuid attribute that is specified for the IUnmanagedServer interface in the .idl for the COM server.
  3. Modify the Main method of Class1 in Class1.cs as follows:
    static void Main(string[] args)
    {
    	UCOMIUnmanagedServer server = (UCOMIUnmanagedServer)new ComServerLib.UnmanagedServerClass();
    	IManaged managed = new ManagedImplementation();
    	server.UnmanagedServerMethod(managed);
    }
    When the unmanaged client calls the unmanaged server, the unmanaged client passes an instance of a managed object to the unmanaged server. The managed object is custom marshaled to an object that exposes the unmanaged interface as expected by the COM server.
  4. On the Build menu, click Build Solution.
  5. In Solution Explorer, right-click the ConsoleApplication1 project node and then click Set as StartUp Project.
  6. On the Debug menu, click Start.
Back to the top

Custom Marshaling from a COM Client to a .NET Server

The scenario that follows shows how custom marshaling is performed from a COM client to a .NET server.

Steps to Create a COM Client

This section shows you how to create an ATL COM component that implements the IManaged interface:
  1. On the File menu, point to New and then click Project.
  2. Click Visual C++ Projects under Project Types and then click ATL Project under Templates.
  3. In the Name text box, type ComComponent and then click OK.
  4. In the ATL Project Wizard dialog box, click the Application Settings tab.
  5. Click to clear the Attributed check box and then click Finish.
  6. In Solution Explorer, right-click ComComponent (Project), point to Add, and then click Add Class.
  7. In the Add Class dialog box, click ATL Simple Object under Templates and then click Open.
  8. In the ATL Simple Object Wizard dialog box, type Unmanaged in the Short name text box and then click Finish.
  9. In Class View, expand ComComponent, right-click IUnmanaged, point to Add, and then click Add Method.
  10. In the Add Method Wizard dialog box, type UnmanagedMethod in the Method Name text box.
  11. Select LONG* from the list under Parameter type.
  12. Click to select retval under Parameter attributes and then type lRetVal in the Parameter name text box.
  13. Click Add and then click Finish.
  14. In Solution Explorer, double-click Unmanaged.cpp to open the file.
  15. Replace the default code for the CUnmanaged::UnmanagedMethod with the code that follows:
    *lRetVal = 300;
    return S_OK;
    
  16. On the Build menu, click Build Solution to build the COM component.
Back to the top

Steps to Create a .NET Server

This section shows you how to create a .NET server that implements a single method that is named ManagedServerMethod. ManagedServerMethod takes an instance of the IManaged type:
  1. On the File menu, point to New and then click Project.
  2. Click Visual C# Projects under Project Types and then click Class Library under Templates.
  3. Click to select the Add to Solution option and then click OK.
  4. In the default-generated Class1.cs file, replace the default code with the code that follows:
    using System;
    using System.Runtime.InteropServices;
    
    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    public interface IManaged
    {
    	long ManagedMethod();
    }
    
    [ClassInterface(ClassInterfaceType.AutoDual)]
    public class ManagedServer
    {
    	public void ManagedServerMethod(IManaged managed)
    	{
    		Console.WriteLine("The output of IManaged.ManagedMethod is : {0}", managed.ManagedMethod());
    	}
    }
    
Back to the top

Steps to Create a Wrapper

This section shows you how to create a wrapper class that wraps an instance of the IUnmanaged type but exposes the IManaged interface:
  1. In Solution Explorer, expand ClassLibrary1.
  2. In ClassLibrary1, right-click References and then click Add Reference.
  3. In the Add Reference dialog box, click the COM tab.
  4. Click ComComponent 1.0 Type Library under Component Name.
  5. Click Select and then click OK.
  6. In the Class1.cs file, add the code that follows:
    [ComVisible(false)]
    public class ComUnmanaged : IManaged
    {
    	ComComponentLib.IUnmanaged unmanaged;
    
    	public ComUnmanaged(ComComponentLib.IUnmanaged unmanaged)
    	{
    		if(unmanaged != null)
    			this.unmanaged = unmanaged;
    		else
    			throw new ArgumentNullException("NULL");
    	}
    
    	public long ManagedMethod()
    	{
    		return unmanaged.UnmanagedMethod();
    	}
    }
    	
Back to the top

Steps to Create a Custom Marshaler

This section shows you how to create a custom marshaler that maps an unmanaged interface (IUnmanaged) to a managed interface (IManaged):

The custom marshaler must implement the ICustomMarshaler interface. The MarshalManagedToNative method is called when a managed interface is mapped to an unmanaged interface. Because the current scenario marshals from the unmanaged to the managed domain, the current implementation of the MarshalManagedToNative method returns IntPtr.Zero.

The MarshalNativeToManaged method wraps the native object in a wrapper object (ComUnmanaged) that was defined in the previous section. The MarshalNativeToManaged method is called when an unmanaged interface is mapped to a managed interface.

The CleanUpNativeData method is called when the object that is associated with the native interface is released.

Similarly, the CleanUpManagedData method is called when the object that is associated with the managed interface is released. Because the managed object is garbage collected, the implementation for the CleanUpManagedData method is empty.

The GetNativeDataSize method returns -1.

Although the GetInstance method is not defined for the ICustomMarshaler interface, you must define the GetInstance static method that returns an instance of ICustomMarshaler. This static method is called by the .NET runtime to create an instance of the custom marshaler.
  1. Add the code that follows to Class1.cs:
    [ComVisible(false)]
    public class SampleMarshaler : ICustomMarshaler
    {
    	static SampleMarshaler marshaler;
    
    	public object MarshalNativeToManaged(IntPtr pNativeData)
    	{
    		if(pNativeData == IntPtr.Zero)
    			return null;
    
    		object rcw = Marshal.GetObjectForIUnknown(pNativeData);
    		if(!(rcw is ComComponentLib.IUnmanaged))
    			throw new ArgumentException("The object must implement IUnmanaged");
    
    		return new ComUnmanaged((ComComponentLib.IUnmanaged) rcw);
    	}
    
    	public IntPtr MarshalManagedToNative(object managedObj)
    	{
    		return IntPtr.Zero;
    	}
    
    	public void CleanUpNativeData(IntPtr pNativeData)
    	{
    		Marshal.Release(pNativeData);
    	}
    
    	public void CleanUpManagedData(object managedObj)
    	{
    
    	}
    
    	public int GetNativeDataSize()
    	{
    		return -1;
    	}
    
    	public static ICustomMarshaler GetInstance(string cookie)
    	{
    		if(marshaler == null)
    			return marshaler = new SampleMarshaler();
    		return marshaler;
    	}
    }
  2. Add the MarshalAsAttribute to the parameter that is custom marshaled. Modify the code for the ManagedServerMethod as follows:
    public void ManagedServerMethod(
    	[MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef=typeof(SampleMarshaler)), In]
    	IManaged managed)
    {
    	Console.WriteLine("The output of IManaged.ManagedMethod is : {0}", managed.ManagedMethod());
    }
  3. On the Build menu, click Build Solution.
  4. On the File menu, point to New and then click Project.
  5. In the New Project dialog box, click Visual C++ Projects under Project Types and then click Win32 Project under Templates.
  6. In the Name text box, type ComClient.
  7. Click to select the Add to Solution option and then click OK.
  8. In the Win32 Application Wizard dialog box, click the Application Settings tab.
  9. Click to select the Console Application option under the Application type section and then click Finish.
  10. In Solution Explorer, expand ComClient and then double-click the Stdafx.h file to open that file. Add the code that follows to the file:
    #import "ComComponent.tlb" no_namespace raw_interfaces_only named_guids
    #import "ClassLibrary1.tlb" no_namespace raw_interfaces_only named_guids
  11. In Solution Explorer, expand ComClient and then double-click the ComClient.cpp file to open that file. Add the code that follows to the file:
    #include "stdafx.h"
    int _tmain(int argc, _TCHAR* argv[])
    {
    	HRESULT hr;
    	IUnmanaged *unmanagedObj;
    	_ManagedServer *managedServer;
    
    	CoInitialize(NULL);
    	hr = CoCreateInstance(CLSID_Unmanaged, NULL, CLSCTX_INPROC_SERVER, IID_IUnmanaged, (LPVOID *)&unmanagedObj);
    	if(FAILED(hr))
    		goto Error;
    
    	hr = CoCreateInstance(CLSID_ManagedServer, NULL, CLSCTX_INPROC_SERVER, IID__ManagedServer, (LPVOID *)&managedServer);
    	if(FAILED(hr))
    		goto Error;
    
    	hr = managedServer->ManagedServerMethod(unmanagedObj);
    	if(FAILED(hr))
    		goto Error;
    
    	return 0;
    
    Error:
    	CoUninitialize();
    	return hr;
    }
  12. To generate the ClassLibrary1.tlb, type the command that follows at the command prompt:

    regasm /tlb ClassLibrary1.dll

    Verify that the ClassLibrary1.dll file is in the build directory for the ClassLibrary1 project. The ClassLibrary1.dll generates the ClassLibrary1.tlb and also registers the class library.
  13. Copy the ClassLibrary1.tlb file and the ComComponent.tlb file to the project directory for the ComClient application.
  14. On the Build menu, click Build Solution.
  15. Copy the ClassLibrary1.dll file and the Interop.comclientLib.dll file from the build directory of the ClassLibrary1 project to the build directory for the ComClient console application.
  16. In Solution Explorer, right-click ComClient and then click Set as StartUp Project.
  17. On the Debug menu, click Start.
Back to the top

REFERENCES

For additional information about interoperability, see the book by Adam Natham:

.NET and COM: The Complete Interoperability Guide (SAMS publication)

For additional information about custom marshaling, visit the Microsoft Developers Network Web site:

Custom Marshaling

Back to the top

Modification Type:MinorLast Reviewed:4/24/2003
Keywords:kbProgramming kbMarshal kbinterop kbhowto KB307713 kbAudDeveloper kbAudITPRO