扫一扫
分享文章到微信
扫一扫
关注官方公众号
至顶头条
作者:Paul DiLascia 来源:msdn中国 2007年11月20日
关键字:
ListLib.cpp 实现了两个本机函数 — AllocateList 和 FreeList,它们用于分配和释放 NativeNode 结构的列表:
以下是引用片段: // from ListLib.h struct NativeNode { int a, b; TCHAR *str; struct NativeNode *next; }; |
ListWrap.cpp 中的包装类 ManagedNode 模拟 NativeNode 的定义,不过有几个细微区别:本机 char* 被替换为托管 String,并且由于我使用 ArrayList 来实现列表结构,所以没有 next 指针。在代码中,它应该如下所示:
以下是引用片段: // managed equivalent of NativeNodepublic __gc class ManagedNode {public: int a, b; String* str;}; |
定义好 ManagedNode 之后,下一步就是编写一些代码将 NativeNode 转换成 ManagedNode。但在开始编写之前,请稍微考虑一下转换器函数应该是什么样子的,它应该有什么样的参数和返回值。一种方式是编写一个函数,将 NativeNode 的本机列表作为参数并返回 ManagedNode 的托管列表,它可能销毁进程中的本机列表。.NET 客户端应用程序会直接调用 ListLib DLL(或者您的 getmystruct)来获取本机列表,将其作为 IntPtr,然后将这个 IntPtr 传递给转换函数,如下所示:
以下是引用片段: // call DLL directly through interop IntPtr nativeList = AllocateList(7); // call wrapper to convert ArrayList amanagedList = ListWrap.Convert(nativeList); |
在大多数情况下,客户端要负责调用 DLL 来释放本机列表,或者由 Convert 函数自动完成释放过程。
另一种方法是完全隐藏 DLL,其做法是:将本机函数 AllocateList 包装在一个包装器中,由它分配列表、转换并释放原始本机列表,再将托管列表作为 ArrayList 返回。哪种方法更好呢?第一种策略的优点是您只需编写一个简单的转换函数,在任何具有本机列表的地方都可以使用它。第二种策略需要包装创建列表的每个函数。如果您有多个创建列表的函数,这种方式会有些麻烦,但是它的优点是对 .NET 客户端完全隐藏了所有本机内容。客户端不需要处理 IntPtrs,甚至不需要导入 DLL;ListWrap 将这一切都隐藏了。我采取的是这种方式,我也鼓励您在自己的应用程序中使用这种方式。它要对库进行完全包装还需要做很多工作,但结果会比较
有了 ManagedNode 之后,剩下的工作就是包装 AllocateList 了。这个过程十分简单。首先,调用 AllocateList 来分配本机列表,然后创建一个空的 ArrayList。接下来将所有 NativeNode 复制到 ManagedNode,并将它们添加到托管列表中,您在进行这些操作时会将它们删除。图 3 显示了完整的细节。托管 C++ 的好处之一就是所有代码看起来很简单、整洁,即使您处理的是混合对象。将本机 char* 复制到托管 String 只是一个简单的赋值,如以下的代码行所示:
以下是引用片段: mn->str = nn->str; // String = char*: |
不需要调用转换函数,编译器知道如何去做。CreateList 在运行时会删除本机节点。这比在最后删除节省存储空间。
通过将整个列表转换成托管对象(而不是通过 interop 和 StructLayout 将它导出),您可以为托管客户端营造一种托管环境。这叫入乡随俗!毕竟,一些程序员选择 .NET 的主要原因之一就是它具有自动垃圾回收的功能。如果您直接通过 interop 导出列表,您还必须导出 FreeList,并要求使用其他基于 .NET 语言的程序员要记得调用它。
一般情况下,如果您导出到托管环境,最好是将尽可能多的数据转换成托管对象。什么情况下例外呢?您的客户端也是采用 C++ 编写的。当然,这条规则并不总是适用。有时更好的方式是直接导出结构,并要求客户端释放它们 — 例如,如果复制对性能或内存造成的影响太大而不可接受,就需要这样做。您必须使用判断来决定是进入托管环境还是进入本机环境。
问:我正在使用 C++ 托管扩展来包装现有的 C++ 库,使基于 .NET 的语言可以访问它。在托管 C++ 中,我可以写为
以下是引用片段: String* s = new String(); s = _T("Hello, world"); |
但如何将托管 String 再次转换为本机 TCHAR*?
Matthew Brady
答:一旦您了解了神奇的 voodoo,这会变得很简单。您必须调用 PtrToStringChars 并 pin 结果。代码如下所示:
以下是引用片段: String __gc* s = S"Hello"; const wchar_t __pin* p = PtrToStringChars(s); |
不要忘记对从 PtrToStringChars 返回的指针进行 __pin 操作。Pin 是必须的,因为 PtrToStringChars 将一个托管 (__gc) 指针返回给托管内存中的 String 对象的第一个字符,而垃圾回收器随时都会回收托管内存,除非您显式对它进行 __pin。一般情况下,每次将 __gc 指针传递给本机(非托管)函数时都必须使用 __pin。
图 4 显示了一个小程序,它将
以下是引用片段: // both will work CString s1 = "hello, world"; CString s2 = L"Hello, world"; |
如果您非常迫切的想了解IT领域最新产品与技术信息,那么订阅至顶网技术邮件将是您的最佳途径之一。
现场直击|2021世界人工智能大会
直击5G创新地带,就在2021MWC上海
5G已至 转型当时——服务提供商如何把握转型的绝佳时机
寻找自己的Flag
华为开发者大会2020(Cloud)- 科技行者