MORE INFORMATION
This article discusses the context where the custom
marshaler is required. This article provides two different scenarios.
Scenario 1The 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 2The 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 topCustom 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.
- Open Microsoft Visual Studio .NET.
- On the File menu, point to
New and then click Project.
- In the New Project dialog box, click
Visual C++ Projects under Project Types and
then click ATL Project under Templates.
- In the Name text box,
type ComServer.
- Click OK.
- In the Wizard dialog box, click the
Application Settings tab.
- Click to clear the Attributed check box
and then click Finish.
- In Solution Explorer, right-click the
ComServer (Project) node, point to Add, and
then click Add Class.
- In the Add Class dialog box, click
ATL Simple Object under Templates and then
click Open.
- In the ATL Simple Object Wizard dialog
box, type UnmanagedServer in the Short
name text box and then click Finish.
- In Solution Explorer, expand
Source Files.
- 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;
...
};
- In Class view, expand
ComServer.
- Right-click IUnmanagedServer, point to
Add, and then click Add Method.
- In the Method Name text box, type
UnmanagedServerMethod.
- Click to select the Parameter attributes
check box.
- In the Parameter type combo box, type
IUnmanaged*.
- In the Parameter text box,
type unmanaged and then click
Add.
- Click Finish.
- In Solution Explorer, double-click
UnmanagedServer.cpp.
- 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;
- On the Build menu, click Build
Solution to build the ATL COM Server application.
Back to the
topSteps 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:
- On the File menu, point to
New and then click Project.
- In the New Project dialog box, click
Visual C# Projects under Project Types and
then click Console Application under
Templates.
- Click to select the Add to Solution option
and then click OK.
- In Solution Explorer, expand
ConsoleApplication1, right-click References,
and then click Add Reference.
- In the Add Reference dialog box, click the
COM tab.
- Click ComServer 1.0 Type Library under
Component Name and then click
Select.
- Click OK.
- 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
topSteps 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:
- 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
topSteps 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.
- 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;
}
}
- 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. - 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. - On the Build menu, click Build
Solution.
- In Solution Explorer, right-click the
ConsoleApplication1 project node and then click Set as
StartUp Project.
- On the Debug menu, click
Start.
Back to the
topCustom 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:
- On the File menu, point to
New and then click Project.
- Click Visual C++ Projects under
Project Types and then click ATL Project
under Templates.
- In the Name text box, type
ComComponent and then click
OK.
- In the ATL Project Wizard dialog box,
click the Application Settings tab.
- Click to clear the Attributed check box
and then click Finish.
- In Solution Explorer, right-click
ComComponent (Project), point to Add, and
then click Add Class.
- In the Add Class dialog box, click
ATL Simple Object under Templates and then
click Open.
- In the ATL Simple Object Wizard dialog
box, type Unmanaged in the Short name
text box and then click Finish.
- In Class View, expand
ComComponent, right-click IUnmanaged, point
to Add, and then click Add Method.
- In the Add Method Wizard dialog box, type
UnmanagedMethod in the Method Name text
box.
- Select LONG* from the list under
Parameter type.
- Click to select retval under
Parameter attributes and then type
lRetVal in the Parameter name text
box.
- Click Add and then click
Finish.
- In Solution Explorer, double-click
Unmanaged.cpp to open the file.
- Replace the default code for the CUnmanaged::UnmanagedMethod with the code that follows:
*lRetVal = 300;
return S_OK;
- On the Build menu, click Build
Solution to build the COM component.
Back to the
topSteps 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:
- On the File menu, point to
New and then click Project.
- Click Visual C# Projects under
Project Types and then click Class Library
under Templates.
- Click to select the Add to Solution option
and then click OK.
- 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
topSteps 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:
- In Solution Explorer, expand
ClassLibrary1.
- In ClassLibrary1, right-click
References and then click Add
Reference.
- In the Add Reference dialog box, click the
COM tab.
- Click ComComponent 1.0 Type Library under
Component Name.
- Click Select and then click
OK.
- 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
topSteps 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.
- 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;
}
}
- 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());
}
- On the Build menu, click Build
Solution.
- On the File menu, point to
New and then click Project.
- In the New Project dialog box, click
Visual C++ Projects under Project Types and
then click Win32 Project under
Templates.
- In the Name text box, type
ComClient.
- Click to select the Add to Solution option
and then click OK.
- In the Win32 Application Wizard dialog
box, click the Application Settings tab.
- Click to select the Console Application
option under the Application type section and then click
Finish.
- 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
- 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;
}
- 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. - Copy the ClassLibrary1.tlb file and the ComComponent.tlb
file to the project directory for the ComClient application.
- On the Build menu, click Build
Solution.
- 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.
- In Solution Explorer, right-click
ComClient and then click Set as StartUp
Project.
- On the Debug menu, click
Start.
Back to the
top