欢迎来到.net学习网

欢迎联系站长一起更新本网站!QQ:879621940

您当前所在位置:首页 »  .NET本质论第一卷:公共语言运行库教程 » 正文

本教程章节列表

MarshalAs特性、RCW与CCW架构

创建时间:2013年06月08日 22:08  阅读次数:(3662)
分享到:
为了深入了解MarshalAs特性的使用,考察下面的P/Invoke方法声:
[DIIImport(“foobar.dll”)]
public static extern void DoIt(
[MarshalAs(UnmanagedType.LPWStr)] String s1,
[MarshalAs(UnmanagedType.LPStr)] String s2,
[MarshalAs(UnmanagedType.LPTStr)] String s3,
[MarshalAs(UnmanagedType.BStr)] String s4,
);

这个方法声明隐含了下面的非托管C函数声明
void _stdcall DoIt(LPCWSTR s1,LPCSTR s2,LPCTSTR s3,BSTR s4);

Blittable和Nonblittable类型

MarshalAs特性参数

注意,C函数原型使用const参数,它给出System.String的语义。这是至关重要的,因为System.String类的实例是不可变的。为此,CLR没有提供改变System.String对象内容的机制,为了理解这对P/Invoke的影响,不妨想一想System.String的内部表示形式,如图10.4所示,System.String是引用类型,因而,所有的字符串都与CLR内部对象格式兼容。此外,所有的字符串都有容量与长度字段的前缀。几乎在所有的情形下,这两个值都是相同的。最后,字符串对象以System.Char的null结束字符数组为结尾部分,它是一个16位的Unicode字符。
System.String内部

因为CLR不使用SysAllocString API调用分配System.String对象,所以,它们不是合法的BSTR,并且,将字符串作为BSTR传递,将导致P/Invoke引擎创建字符串的副本。同样,由于System.String对象包含Unicode字符,所以,当字符串作为ANSI字符串传递时,将被认为是nonblittable类型。这些字符串的临时拷贝只是在调用期间存活,并且,CLR不会只对原始的System.String对象做任何改变。然而,如果将字符串作为Unicode字符串(UnmanagedType.LPWStr)传递,P/Invoke引擎实际传递的一个指针,它指同字符串的字符数组的起始位置。这意味着在调用期间,外部DLL拥有指向字符串实际缓存的指针。由于CLR字符串是不变的,所以外部DLL对它的改变将导致随机的和不可预期的错误。为了避免这种情况,你应该将外部DLL参数声明为const wchar_t *。如果需要将字符串传递给外部DLL(用于更改),则应该使用System.Text.StringBuilder类型。例如,考察下面的Win32 API函数:
BOOL __stdcall GetModuleFileName(HMODULE hmod,LPTSTR psz,DWORD nSize);

这个函数所需要的P/Invoke原形如下所示:
[DLLImport(“kernel32.dll”,CharSet=CharSet.Auto,SetLastError=true)]
static extern boll GetModuleFileName(IntPtr hmod,StringBuilder psz,uint nSize);

为了使用这个函数,你需要使用StringBuilder类预分配字符串的缓存,如下所示:
static string GetTheName(IntPtr hmod){
//分配1024个字符缓冲
StringBuilder sb=new StringBuilder(1024);
//调用P/Invoke子例程
if(!GetModuleFileName(hmod,sb,1024))
 throw new MyException();
//获得最终字符串
return sb.ToString();
}

注意.StringBuilder对象持有一个私有的字符串对象,用于底层字符缓冲区。调用ToString方法将返回对这个私有字符串对象的引用。任何StringBuilder对象的使用都会触发这个字符串的新拷贝,并且StringBuilder对象在后续操作中进行修改的,正是这个字符串拷贝。这
样就保证了从ToString返回的字符串是相对独立的。

DllImport特性允许你在方法范围的基础上指定默认的字符串格式,而不必对每一个参数都使用MarShalAs特性。你能够对使用CharSot参数的方法设置Unicode/ANSI策略。Dllimport特性的CharSet参数可以选择使用Unicode( CharSet.Unicode)或者ANSI(CharSet.ansi)。这与手工地将每一个字符串参数逐个标记为Marshal(UnmanagedType.LPWStr)或者Marshal(UnmanagedType.LPStr)是等价的。

DllImport特性支持第三个设置:CharSeL.Auto。它标明了底层平台(WindowsNT/2000/xP对Windows 9x/ME)规定字符串爹数的外部格式。使用CharSet.Auto类似于用TCHAR类型编写Win32/C的代码,除了在加载时(而不是编译时、CLR确定实际的字符类型和API,它还可以让一段二进制代码在任何版本的Windows上都合适而高效地运行。

当你传递的对象引用不是System.string和System.Object时,默认的封送行为是CLR对象引用和COM对象引用之间的转换。如图10.5所示,当你越过P/Invoke边界,将一个引用封送到CLR对象上时.CLR将创建一个COM可调用包装(COM-callable wrapper,ccw),并将其充当CLR对象的代理(proxy)。同样,当你通过P/Invoke边界,将引用封送到COM对象,CLR将创建运行库可调用包装(runtime-callable wrapper,RCW),充当到COM对象的代理。对于这两种情况.代理都将实现底层对象的所有接口。此外,代理将映射COM与CLR之间的术语,例如,IDispatch对象的持久性,以及在其他技术中所对应构件的事件。
RCW和CCW架构

值得关注的是.ccw或者RCW(或者两者)的出现减少了CLR和COM生存期管理中存在的破坏性。例如.RCW持有指向底层COM对象的Addref接口指针。在RCW终结前.CLR不会释放这些接口指针。此外,CCW持有对底层CLR对象的根引用,并且只要有一个未完成的COM接口指针,它就会阻止对象被垃圾回收。这就意味着,当对象存活期中包含CCW或者RCW时,需要某种机制来打破这个周期。你可以通过调用Marshal.ReleaseComObject静态方法抢占式地释放RCW接口指针,也可以通过调用Marsnal.ChangeWrapperHandlestrength方法,将CCW内部的根引用转换为弱引用。
来源:.net学习网
说明:所有来源为 .net学习网的文章均为原创,如有转载,请在转载处标注本页地址,谢谢!
【编辑:Wyf】

打赏

取消

感谢您的支持,我会做的更好!

扫码支持
扫码打赏,您说多少就多少

打开支付宝扫一扫,即可进行扫码打赏哦

最新评论

共有评论0条
  • 暂无任何评论,请留下您对本文章的看法,共同参入讨论!
发表评论:
留言人:
内  容:
请输入问题 25+35=? 的结果(结果是:60)
结  果: