Share via


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)

  1. Generate the initial interop assembly using Tlbimp.exe. For example, to produce an assembly called New.dll from New.tlb, type the following command at the command prompt:

    tlbimp New.tlb /out:New.dll
    
  2. At the command prompt, type the following command to produce Microsoft intermediate language (MSIL) for the assembly:

    ildasm New.dll /out:new.il
    
  3. Edit the MSIL as required.

  4. 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