让人混淆的UnmanagedType.LPStruct列集指令(翻译)

原始文献地址:

http://blogs.msdn.com/adam_nathan/archive/2003/04/23/56635.aspx

 

在列集有多种非托管表现形式的托管数据类型时,MarshalAsAttribute属性可以改变列集(Marshaling)行为。一般来说,使用MarshalAsAttribute的难点是如何在它的构造函数里面选择正确的UnmanagedType枚举值。我看到过的最经常被误用的UnmanagedType枚举值应该是LPStruct了。我不责怪这些使用错误的程序员,因为它的名字就是极其误导用户。我可以肯定地说如果不是出于向后兼容的考虑的话,我们早就将它的名字改掉了。

 

作为一个为程序员提供技术支持的人,我想在这里澄清(LPStruct)这个列集指令的意思,这样你可以正确使用它,或者就是(最好)避免使用它。

 

UnmangedType.LPStruct只能够支持一个特殊的例子:将值类型System.Guid列集成非托管结构体GUID的指针。换句话说,这个指令告诉.NET的列集器,在将System.Guid从托管代码列集到非托管一端时,增加一层额外的指针封装,而在将GUID结构从非托管一端列集到托管一端时,去掉这层额外的指针封装。

 

你可以通过将下面的C#接口定义导出到类型库的时候看到上面描述的行为:

public interface IUseGuids

{

    void ByValGuid(System.Guid g);

    void ByRefGuid(ref System.Guid g);

    void ByValGuidWithLPStruct(

      [MarshalAs(UnmanagedType.LPStruct)] System.Guid g);

    void ByRefGuidWithLPStruct(

      [MarshalAs(UnmanagedType.LPStruct)] ref System.Guid g);

}

 

结果是(使用OLEVIEW.EXE查看输出的类型库文件):

interface IUseGuids : IDispatch

{

    [id(0x60020000)]

    HRESULT ByValGuid([in] GUID g);

    [id(0x60020001)]

    HRESULT ByRefGuid([in, out] GUID* g);

    [id(0x60020002)]

    HRESULT ByValGuidWithLPStruct([in, out] GUID* g);

    [id(0x60020003)]

    HRESULT ByRefGuidWithLPStruct([in, out] GUID** g);

};

 

请注意导出类型库程序(tlbexp.exe)是一个很好的工具,它知道托管代码里面的参数、类成员和返回值是如何列集的,因为它创建的(函数和接口)原型要完全匹配CLR列集器的行为的。即使是在生成P/Invoke函数声明的时候,如果你不知道如何确定参数的正确类型,你可以根据这个技巧来将函数暂时放到一个接口定义里面(删掉”static””extern”等关键字),然后运行tlbexp.exe来生成正确的函数原型。

 

在什么时候你需要用到UnmanagedType.LPStruct?当你要调用接受一个[in]GUID *的参数的非托管API,例如CoCreateInstanceEx时,(你需要用到这个列集指令),MSDNCoCreateInstanceEx的定义是:

  HRESULT CoCreateInstanceEx(

    REFCLSID rclsid,

    IUnknown* punkOuter,

    DWORD dwClsCtx,

    COSERVERINFO* pServerInfo,

    ULONG cmq,

    MULTI_QI* pResults

  );

 

而第一个参数的说明是:

  rclsid

[in] CLSID of the object to be created.

因此,使用UnmanagedType.LPStruct,你可以定义一个象下面这样的C# P/Invoke函数原型:

[DllImport("ole32.dll", PreserveSig=false)]

static extern void CoCreateInstanceEx(

    [MarshalAs(UnmanagedType.LPStruct)] Guid rclsid,

    [MarshalAs(UnmanagedType.IUnknown)] object punkOuter,

    uint dwClsCtx,

    [In] ref COSERVERINFO pServerInfo,

    uint cmq,

    [In, Out] MULTI_QI [] pResults

);

而不是得到下面这个原型:

[DllImport("ole32.dll", PreserveSig=false)]

static extern void CoCreateInstanceEx(

    ref Guid rclsid,

    [MarshalAs(UnmanagedType.IUnknown)] object punkOuter,

    uint dwClsCtx,

    [In] ref COSERVERINFO pServerInfo,

    uint cmq,

    [In, Out] MULTI_QI [] pResults

);

 

前者我们可以以这种方式调用:

  Guid clsid = ...;

  ...

  CoCreateInstanceEx(clsid, null, CLSCTX_REMOTE_SERVER, ref serverInfo, 1, results);

这种方法对于一些仁兄来说,要比使用下面的方法调用后者稍微简洁一些:

  Guid clsid = ...;

  ...

  CoCreateInstanceEx(ref clsid, null, CLSCTX_REMOTE_SERVER, ref serverInfo, 1, results);

特别是因为将Guid参数以by-ref的形式传递的方式会让调用方相信这个Guid实例有可能被改变虽然实际上不会被改变。

 

当然啦,LPStruct的技巧只能对GUID *的参数有效,这是因为在非托管一方对GUID的修改都不会反映到以按值传递方式传递过来的System.Guid实例上。而使用[in]GUID *作为非托管API的输入参数是一个非常普遍的模式,即使这些API都不会修改GUID实例,从调用方传递GUID指针的方式还是比把整个结构放到堆栈上的方式效率高的多。

 

那么上面这一个故事的精髓在哪呢?你应该避免使用UnmanagedType.LPStruct,它带来的麻烦远比带来的那么一点小方便要多得多。

posted @ 2009-03-15 17:15  donjuan  阅读(3581)  评论(0编辑  收藏  举报