Editing An Interop Assembly
The Type Library Importer (Tlbimp.exe) converts most COM method signatures into managed signatures. However, several types require additional information that you can specify by editing the interop assembly. This topic identifies several such cases and explains precisely how to correct them.
To specify marshaling changes in Microsoft intermediate language (MSIL)
Generate the initial interop assembly using Tlbimp.exe. For example, to produce an assembly called
New.dll
fromNew.
tlb, type the following command at the command prompt:tlbimp New.tlb /out:New.dll
At the command prompt, type the following command to produce Microsoft intermediate language (MSIL) for the assembly:
ildasm New.dll /out:new.il
Edit the MSIL as required.
At the command prompt, type the following command to produce a new
New.dll
defining the proper syntax:ilasm New.il
The following sections provide a selected set of changes that you can make to address some specific problems with the output of the import process:
- Conformant C-Style Arrays
- In/Out C-Style Arrays
- Multidimensional C-Style Arrays
- Nonzero-Bound SAFEARRAY
- Preserve Signature
- Passing Null Instead Of Reference To Value Type
These sections do not represent every case for editing an interop assembly. For example, you can also edit an interop assembly to enhance its ease of use. The only way to determine which customizations are necessary is to actually write code using the interop assembly.
Marshaling is affected when the client and server are in incompatible apartments. In the following examples, most of the marshaled parameters are not Automation compatible and require one of the following actions:
- Confirm that both client and server are in compatible apartments (and thus, there is no COM marshaling involved).
- Register the proxy and stub generated from Interface Definition Language (IDL). Registering the type library does not help in these cases because much of the information needed for marshaling is not propagated from IDL to the type library.
Conformant C-Style Arrays
The following IDL declaration shows a C-style array.
HRESULT ConformantArray([in] int cElems, [in, size_is(cElems)] int
aConf[]);
Because this type is not Automation compatible, information about the size of the array, such as the link between the first and the second parameter, cannot be expressed in the type library. The Type Library Importer (Tlbimp.exe) imports the second parameter as a reference to the integer and not as a managed array. You can adjust the parameter by editing the MSIL.
Search MSIL for
method public hidebysig newslot virtual
instance void ConformantArray([in] int32 cElems,
[in] int32& aConf) runtime managed internalcall
Replace with
method public hidebysig newslot virtual
instance void ConformantArray([in] int32 cElems,
[in] int32[] marshal([]) aConf) runtime managed internalcall
To call from managed code
int[] param1 = { 11, 22, 33 };
tstArrays.ConformantArray( 3, param1 );
In/Out C-Style Arrays
The following IDL declaration shows an In/Out C-style array.
HRESULT InOutArray([in, out] int* pcElems, [in, out, size_is(*pcElems)]
int** ppInOut);
In this case, the array can be resized and the new size can be passed back. Because this type is not Automation compatible, information about the size of the array, such as the link between the first and the second parameter, cannot be expressed in the type library. Tlbimp.exe imports the second parameter as an IntPtr. Although you can still call this method from managed code, to resize the array you must edit the MSIL and use methods from the Marshal class to manually handle the allocation and deallocation of memory.
Search MSIL for
.method public hidebysig newslot virtual
instance void InOutArray([in][out] int32& pcElems,
[in][out] native int ppInOut) runtime managed internalcall
Replace with
.method public hidebysig newslot virtual
instance void InOutArray([in][out] int32& pcElems,
[in][out] native int& ppInOut) runtime managed internalcall
To call from managed code
int[] inArray = { 11, 22, 33 };
int arraySize = inArray.Length;
IntPtr buffer = Marshal.AllocCoTaskMem( Marshal.SizeOf( typeof( int )) * inArray.Length );
Marshal.Copy( inArray, 0, buffer, inArray.Length );
tstArrays.InOutArray( ref arraySize, ref buffer );
if( arraySize > 0 )
{
int[] arrayRes = new int[ arraySize ];
Marshal.Copy( buffer, arrayRes, 0, arraySize );
Marshal.FreeCoTaskMem( buffer );
}
Multidimensional C-Style Arrays
The following IDL declaration shows a two-dimensional, C-style array.
HRESULT TwoDimArray([in] int cDim, [in, size_is(cDim)] int aMatrix[][3]);
Because this type is not Automation compatible, information about the size and number of dimensions of the array, such as the link between the first and the second parameter, cannot be expressed in the type library. Tlbimp.exe imports the second parameter as an IntPtr type and not as a managed multidimensional array. You can adjust the parameter by editing the MSIL.
Search MSIL for
.method public hidebysig newslot virtual
instance void TwoDimArray([in] int32 cDim,
[in] native int aMatrix) runtime managed internalcall
Replace with
.method public hidebysig newslot virtual
instance void TwoDimArray([in] int32 cDim,
[in] int32[,] marshal([]) aMatrix) runtime managed internalcall
To call from managed code
int[,] param = {{ 11, 12, 13 }, { 21, 22, 23 }, { 31, 32, 33 }};
tstArrays.TwoDimArray( 3, param );
Nonzero-Bound SAFEARRAY
The following IDL declaration shows a SAFEARRAY parameter.
HRESULT InSArray([in] SAFEARRAY(int) *ppsa);
Consider that this SAFEARRAY is nonzero bound. In managed code, such arrays are represented by the System.Array type. However, by default, the importer converts all SAFEARRAY parameters to references to managed arrays. You have two options for changing the default behavior:
Import all the arrays in a type library as System.Array types by using Tlbimp.exe with the /sysarray switch.
Import a few parameters as System.Array types by manually editing the MSIL, as the following example shows.
Search MSIL for
.method public hidebysig newslot virtual instance void InSArray([in] int32[]& marshal( safearray int) ppsa) runtime managed internalcall
Replace with
.method public hidebysig newslot virtual instance void InSArray(class [mscorlib]System.Array& marshal( safearray) ppsa) runtime managed internalcall
Call from managed code
int[] lengthsArray = new int[1] { 3 }; int[] boundsArray = new int[1] { -1 }; Array param2 = Array.CreateInstance( typeof(int), lengthsArray, boundsArray ); for( int i = param2.GetLowerBound( 0 ); i <= param2.GetUpperBound( 0 ); i++ ) param2.SetValue( i * 10, i ); sum = tstArrays.InSArray( ref param2 );
Preserve Signature
The following IDL declaration shows a COM method signature.
HRESULT TestPreserveSig2([in] int inParam, [out,retval] int* outParam);
Tlbimp.exe changes the signatures of COM methods. Parameters marked with [out, retval] in IDL become return values of managed methods. All HRESULT values that indicate failure are transformed to managed exceptions. It is sometimes necessary to preserve the original COM method signature, such as when the method returns something other than success HRESULTs. The following managed representation shows an example of a signature that you can modify.
Managed representation in MSIL
.method public hidebysig newslot virtual
instance int32 TestPreserveSig2([in] int32 inParam) runtime managed internalcall
{
Replace with
.method public hidebysig newslot virtual
instance int32 TestPreserveSig2([in] int32 inParam, [out] int32& outParam) runtime managed internalcall preservesig
To see which HRESULT is returned
int hr = tst.TestPreserveSig2( -3, out retValue );
Console.WriteLine( "Return value is {0}", retValue );
if( hr == 0 )
Console.WriteLine( "HRESULT = S_OK" );
else if ( hr == 1 )
Console.WriteLine( "HRESULT = S_FALSE" );
else
Console.WriteLine( "HRESULT = {0}", hr );
Passing Null Instead of a Reference to a Value Type
The following IDL declaration shows an IDL pointer to a structure.
HRESULT TestPassingNull([in, unique] Point* refParam);
Tlbimp.exe imports the parameter as a reference to the value type Point
. In C# and Visual Basic .NET, a null reference (Nothing in Visual Basic) cannot be passed as a parameter when a reference to a value type is expected. If the COM function requires a null (Nothing) parameter, you can alter the signature by editing the MSIL.
Search MSIL for
.method public hidebysig newslot virtual
instance void TestPassingNull(
[in] valuetype MiscSrv.tagPoint& refParam)
runtime managed internalcall
Replace with
.method public hidebysig newslot virtual
instance void TestPassingNull([in] native int) runtime managed internalcall
The altered signature enables you to pass a null value. However, when you need to pass some real values, you must use the methods of the Marshal class, as the following example shows.
tagPoint p = new tagPoint();
p.x = 3;
p.y = 9;
IntPtr buffer = Marshal.AllocCoTaskMem( Marshal.SizeOf( p ));
Marshal.StructureToPtr( p, buffer, false );
tst.TestPassingNull( buffer );
Marshal.FreeCoTaskMem( buffer );
tst.TestPassingNull( IntPtr.Zero );
See Also
Customizing Runtime Callable Wrappers | Creating a Wrapper Manually | COM Data Types | Customizing COM Callable Wrappers