封送处理更改

下面几部分提供了可以在互操作程序集中进行的一组选定的更改,这些更改可以解决导入过程的输出可能出现的一些特定问题:

  • 符合 C 样式的数组

  • 入/出 C 样式数组

  • 多维 C 样式数组

  • 非零绑定 SAFEARRAY

  • 保留签名

  • 传递 null 引用,而不是传递对值类型的引用

这些章节并不代表编辑 interop 程序集的每一种情况。 例如,还可以编辑 interop 程序集以提高其易用性。 确定必要的自定义的唯一方法是使用 interop 程序集实际编写代码。 有关编辑互操作程序集的说明,请参见如何:编辑互操作程序集

当客户端和服务器位于不兼容的单元时,封送处理将受到影响。 在下面的示例中,大多数被封送的参数不是自动化兼容的,并需要进行下列操作之一:

  • 确认客户端和服务器两者都在兼容的单元中(因此,不涉及 COM 封送处理)。

  • 注册从接口定义语言 (IDL) 中生成的代理和存根 (stub)。 在这些情形下,注册类型库没有用,原因是封送处理所需的大部分信息不会从 IDL 传播到类型库。

符合 C 样式的数组

下面的 IDL 声明显示一个 C 样式数组。

HRESULT ConformantArray([in] int cElems, [in, size_is(cElems)] int 
aConf[]);

由于此类型不是自动化兼容的,因此有关该数组大小的信息(如第一个和第二个参数之间的链接)无法在类型库中表示。 类型库导入程序 (Tlbimp.exe) 将第二个参数导入为对整数的引用,而不是导入为托管数组。 可以通过编辑 MSIL 来调整该参数。

在 MSIL 中搜索

method public hidebysig newslot virtual 
instance void  ConformantArray([in] int32 cElems,
[in] int32& aConf) runtime managed internalcall

替换为

method public hidebysig newslot virtual 
instance void  ConformantArray([in] int32 cElems,
[in] int32[] marshal([]) aConf) runtime managed internalcall

从托管代码中调用

int[] param1 = { 11, 22, 33 };
tstArrays.ConformantArray( 3, param1 );

入/出 C 样式数组

下面的 IDL 声明显示一个入/出 C 样式数组。

HRESULT InOutArray([in, out] int* pcElems, [in, out, size_is(,*pcElems)] 
int** ppInOut);

在这种情况下,可以调整该数组的大小,并且可以传回新的大小。 由于此类型不是自动化兼容的,因此有关该数组大小的信息(如第一个和第二个参数之间的链接)无法在类型库中表示。 Tlbimp.exe 将第二个参数作为 IntPtr 导入。 尽管仍然可以从托管代码中调用此方法,但若要调整数组的大小,就必须编辑 MSIL,并使用 Marshal 类中的方法手动处理内存的分配和解除分配。

在 MSIL 中搜索

.method public hidebysig newslot virtual 
instance void  InOutArray([in][out] int32& pcElems,
[in][out] native int ppInOut) runtime managed internalcall

替换为

.method public hidebysig newslot virtual 
instance void  InOutArray([in][out] int32& pcElems,
[in][out] native int& ppInOut) runtime managed internalcall

从托管代码中调用

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 );
}

多维 C 样式数组

下面的 IDL 声明显示一个二维 C 样式数组。

HRESULT TwoDimArray([in] int cDim, [in, size_is(cDim)] int aMatrix[][3]);

由于此类型不是自动化兼容的,因此有关该数组的大小和维数的信息(如第一个和第二个参数之间的链接)无法在类型库中表示。 Tlbimp.exe 将第二个参数导入为 IntPtr 类型,而不是导入为托管多维数组。 可以通过编辑 MSIL 来调整该参数。

在 MSIL 中搜索

.method public hidebysig newslot virtual 
instance void  TwoDimArray([in] int32 cDim,
[in] native int aMatrix) runtime managed internalcall

替换为

.method public hidebysig newslot virtual 
instance void  TwoDimArray([in] int32 cDim,
[in] int32[,] marshal([]) aMatrix) runtime managed internalcall

从托管代码中调用

int[,] param = {{ 11, 12, 13 }, { 21, 22, 23 }, { 31, 32, 33 }};
tstArrays.TwoDimArray( 3, param );

非零绑定 SAFEARRAY

下面的 IDL 声明显示一个 SAFEARRAY 参数。

HRESULT InSArray([in] SAFEARRAY(int) *ppsa);

假定此 SAFEARRAY 是非零绑定。 在托管代码中,这样的数组由 System.Array 类型表示。 但是,默认情况下,导入程序将所有 SAFEARRAY 参数转换为对托管数组的引用。 有两个用来更改默认行为的选项:

  • 通过使用带有 /sysarray 开关的 Tlbimp.exe 将类型库中的所有数组导入为 System.Array 类型。

  • 通过手动编辑 MSIL 将一些参数导入为 System.Array 类型,如下面的示例所示。

    在 MSIL 中搜索

    .method public hidebysig newslot virtual 
    instance void  InSArray([in] int32[]&  marshal( safearray int) ppsa) runtime managed internalcall
    

    替换为

    .method public hidebysig newslot virtual 
    instance void  InSArray(class [mscorlib]System.Array& marshal( safearray) 
    ppsa) runtime managed internalcall
    

    从托管代码中调用

    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 );
    

保留签名

下面的 IDL 声明显示一个 COM 方法签名。

HRESULT TestPreserveSig2([in] int inParam, [out,retval] int* outParam);

Tlbimp.exe 更改 COM 方法的签名。 在 IDL 中用 [out, retval] 标记的参数成为托管方法的返回值。 所有指示失败的 HRESULT 值都被转换为托管异常。 有时需要保留原始的 COM 方法签名,例如当方法返回除成功 HRESULT 以外的内容时。 下面的托管表示形式显示可以修改的签名的示例。

MSIL 中的托管表示形式

.method public hidebysig newslot virtual 
instance int32 TestPreserveSig2([in] int32 inParam) runtime managed internalcall
{

替换为

.method public hidebysig newslot virtual 
instance int32 TestPreserveSig2([in] int32 inParam, [out] int32& outParam) runtime managed internalcall preservesig

查看返回了哪个 HRESULT

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 );

传递 null 而不是对值类型的引用

下面的 IDL 声明显示一个指向结构的 IDL 指针。

HRESULT TestPassingNull([in, unique] Point* refParam);

Tlbimp.exe 将参数导入为对值类型 Point 的引用。 在 C# 和 Visual Basic 2005 中,当预期的是对值类型的引用时,不能将 null 引用(在 Visual Basic 中为 Nothing)作为参数传递。 如果 COM 函数需要 null (Nothing) 参数,可以通过编辑 MSIL 来改变签名。

在 MSIL 中搜索

.method public hidebysig newslot virtual 
instance void  TestPassingNull(
[in] valuetype MiscSrv.tagPoint& refParam) 
runtime managed internalcall

替换为

.method public hidebysig newslot virtual 
instance void  TestPassingNull([in] native int) runtime managed internalcall

改变后的签名使您能够传递 null 值。 但是,如果需要传递一些实际值,则必须使用 Marshal 类的方法,如下面的示例所示。

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 );

请参见

任务

如何:编辑互操作程序集

如何:手动创建包装

参考

Tlbimp.exe(类型库导入程序)

概念

自定义运行时可调用包装

COM 数据类型

自定义 COM 可调用包装