分享
 
 
 

删除托管对象,如何果包装一个库?及其它......

王朝vc·作者佚名  2006-01-17
窄屏简体版  字體: |||超大  

C++ Q&A 专栏...

删除托管对象,如何果包装一个库?及其它......

原著:Paul DiLascia

翻译:Northtibet

原代码下载:CQA0412.exe (235KB)

原文出处:MSDN Magazine December 2004 (C++ Q&A)

删除托管对象

如何果包装一个库?

如何将托管 String 转换回本地的 TCHAR*?

如何改变标签控件的背景颜色?

在托管 C++ 中,请告诉我使用 delete

操作符销毁托管对象是否安全?

Bernie Sanders

是的,在托管 C++ 中,你可以删除( delete

)托管对象,只要你理解删除只不过是调用对象的析构函数,但析构函数必须显示定义。调用 delete

不会释放对象的存储区。只有垃圾收集器才行。Figure 1 展示了一个简单的程序,该程序定义了一个带析构函数的托管类,当它运行的时候会显示一条信息。TESTDTOR

分配两个 ManagedClass 实例。它显式删除第一个实例,但第二个则不然。如果运行 TESTDTOR,你会得到象下面这样的结果:Begin main

ManagedClass(04A712D4)::ctor

ManagedClass(04A712D4)::dtor

ManagedClass(04A712E0)::ctor

End main

ManagedClass(04A712E0)::dtor

它说明了当 delete 语句执行时,第一个对象的析构函数立即执行;而第二个对象(at 04A712E0)则没有被销毁,直到控制离开

main 并且系统终止代码调用垃圾收集器释放逗留对象。

Figure 2 Testdtor 的精彩输出

不管什么时候,如果你不能确定 .NET 环境中发生了什么,你总是可以编写一些代码,编译它并检查微软中间语言(MSIL)产生的东西。正如

Figure 2 所展示的,定义析构函数导致编译器产生两个方法:一个是 Finalize 方法,它包含你的实现(这里是调用 printf),一个是

__dtor 方法,它调用 System.GC::SuppressFinalize,然后再调用

Finalize。当你删除对象时,编译器产生一个对此 __dtor 方法的掉用。如果你用 /FAs 编译 TESTDTOR

来产生有源码的程序集清单,你将看到 delete 语句以如下的方式编译:

; delete pmc;

ldloc.0 ; _pmc$

call ??1ManagedClass@@$$FQ$AAM@XZ

奇怪神秘的符号是被修饰过的析构函数(__dtor)。

老练的 C++ 程序员可能会弄不明白,如果调用 delete 都无法释放对象,那调用它有干什么?好问题。调用 delete

的唯一理由是收回任何你的类所使用的非托管资源。例如,如果你的对象打开数个文件或创建了数据库连接,你可以写一个关闭其资源的析构函数,然后在用完该对象时使用

delete 释放它。在托管类中释放资源的一个更好的方法是通过实现 Dispose 模式,IDisposable——如果你在写托管 C++

代码——由 auto_dispose 来调用它。(更多的信息参见 Tomas Restrepo 在 MSDN 杂志 2002 二月刊上的文章:“Tips and Tricks to Bolster Your Managed C++ Code in Visual Studio .NET”)。

如果你实现 dispose 模式,其他的 .NET

使用者也可以使用它。如果你自己在析构函数中进行清理,其它语言便没有办法显式调用你的清理代码。因为在 C# 和 Visual Basic 中没有

delete 操作符。

所以结果是你能调用 delete 来触发你的析构函数,但是将清理代码放在析构函数中可能不是一个好主意。最好是实现 IDisposable,这样所有人都能使用。注意,在

Visual C++ 2005 中,这个行为有所改变。更多信息参见 Andy Rich 对这个问题的讨论:“Deterministic Finalization IV - Benefits, part II”,以及当前的 C++/CLI 语言规范标准:“C++/CLI Language Specification Standard

我有一个返回链表的非托管函数,其中有 char* 字符串:

struct blah {

int a, b;

char *a, *b;

struct blah *next;

};

struct blah *getmystruct();

因为 getmystruct() 分配内存,当用完之后,我需要调用 freemystruct(struct blah

*b)。我尝试做一个包装器,用它来将之转换成托管类型的集合,但我不知道当需要释放所有这些指针的时候,该如何来处理。你能否赐教?

Cory Nelson

为什么,的确。你不能用 dllimport

语句将你的本地链表转换成托管类型集合。interop

服务固然不错,但处理此问题也不是那么好!你需要编写一个包装器,显式地将你的链表转换为托管集合,象 ArrayList。我写了一个带有三个模块的程序,ListWrap,它示范了具体做法。第一个模块,ListLib.cpp,实现一个本地

C++ 库(DLL),其中有两个函数,AllocateList 和 FreeList。分别用来分配和释放本地 C++ 结构链表。它们模仿你程序中的 getmystruct

和 freemystruct 函数。第二个模块是一个托管 C++ 文件,ListWrap.cpp,它实现托管类 ManagedNode,该类包装本地

C++ 实现(参见

Figure 3)。第三个模块是 C# 测试程序,它调用包装器来展示它如何工作。详情请下载源代码。

ListLib.cpp 实现两个本地函数,AllocateList 和 FreeList,这两个函数用来分配和释放 NativeNode

结构链表:// from ListLib.h

struct NativeNode {

int a, b;

TCHAR *str;

struct NativeNode *next;

};

ListWrap.cpp 中的包装器类 ManagedNode 模仿用 NativeNode 的定义,只是有两个小差别:本地 char*

被用托管的 String 代替,此外它没有 next 指针,因为我将用 ArrayList 实现链表结构。代码如下:

// managed equivalent of NativeNode

public __gc class ManagedNode {

public:

int a, b;

String* str;

};

有了 ManagedNode 的定义,下一步是编写代码将 NativeNodes 转换到 ManagedNodes。但在开始之前,先停下来考虑一下转换函数应该是什么样子,他应该有什么样的参数,返回什么值。一种方法是编写一个函数,参数是本地 NativeNodes

链表并返回托管的 ManagedNodes 链表,在这个过程中可能销毁本地链表。.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 函数自动完成。

一种不同的方法是通过在某个包装器中包装分配链表的本地函数 AllocateList 来完全隐藏这个 DLL,转换并在作为 ArrayList

返回托管链表之前释放原来的本地链表。哪种方法更好的呢?第一种策略的优点是你只需要编写单一的转换函数,它便可以在任何有本地链表的地方使用。第二个方法需要对每个创建链表的函数进行包装。如果有多个创建链表的函数,则工作量稍大一些。但是其优点是它向

.NET 客户端完全隐藏了所有的本地处理逻辑和细节。客户端不再需要去处理 IntPtrs 或甚至是导入此 DLL,因为 ListWrap

隐藏了一切。这是我将要采用的方法,同时也是我鼓励你在自己的程序中使用的方法。尽管对库进行完全的包装需要更多的努力,但是结果却更加可靠和彻底的封装。

有了 ManagedNode,剩下的事情便是包装 AllocateList。这个过程非常简单直白。首先,调用 AllocateList

分配本地链表,然后创建一个空的 ArrayList,接着将所有 NativeNodes 拷贝到 ManagedNodes

并将它们添加到托管链表中,最有离开时删除它们。Figure 3 展示了所有的细节。托管 C++

的优美之处在于即便是在处理混合对象时,所有的代码看起来都很简朴优雅。将本地 char* 拷贝到托管 String

用一个赋值即可,就像下面这行代码:

mn->str = nn->str; // String = char*: it just works!

不需要调用转换函数;编译器知道该怎么做。离开 CreateList 时删除本地节点。这样做比在末尾删除它们存储更有效。

通过将整个链表转换到托管对象(而不是用 interop 和 StructLayout

将它导出),使托管客户端不用离开托管世界,此谓入乡随俗也!毕竟,某些程序员选择 .NET 的一个主要理由是其自动的垃圾收集。如果你用 interop

直接导出链表,那么你也必须导出 FreeList,从而必须让使用基于 .NET 语言的其他程序员记得调用它。

一般来说,如果你要导出到托管世界,最好将数据尽可能多地转换成托管对象。否则你的客户端也必须用 C++

编写代码。当然,这个规则并不总是适用。有时直接导出结构并让客户端释放它们更好——例如,如果拷贝动作会引发核心不可接受的性能问题或内存冲突。那么你必须做出判断以决定是走托管之路还是使用本地机制。

我正在使用 C++ 托管扩展(Managed Extension

for C++)包装现存的 C++ 库,以便基于 .NET 的语言能访问它。在 托管 C++ 中,我可以写如下代码:

String* s = new String();

s = _T("Hello, world");

但我如何才能将一个托管 String 转换回本地的 TCHAR*?

Matthew Brady

一旦你知道了这个神奇的方法,它便很简单。你必须调用 PtrToStringChars

并对结果进行 pin (销连接)操作。代码可以这样你写:String __gc* s = S"Hello";

const wchar_t __pin* p = PtrToStringChars(s);

不要忘了对 PtrToStringChars 返回的指针进行 __pin 操作。销连接是必不可少的,因为 PtrToStringChars

返回指向托管内存中 String 对象第一个字符的托管(__gc)指针,垃圾收集器可能在任何高兴的时候移走托管内存,除非你显示地对之进行 __pin

操作。一般来讲,你必须在将 __gc 指针传递给某个本地(非托管)函数的任何时候使用用 __pin。

Figure 4 展示了一个简短的程序,它将托管 String 转换为宽字符和 ANSI 字符串两者。为了转换到

ANSI,要用到你宠爱的转换函数,象 wcstombs 或 ATL W2A 宏。如果你使用 MFC CString,你不必任何事情,因为

CString 具备针对 char* 和 wchar_t 的赋值操作:

// both will work

CString s1 = "hello, world";

CString s2 = L"Hello, world";

我想在自己的应用程序中改变标签控件的背景颜色,将它从灰色改成白色。我尝试建立一个 CTabCtrl

的派生类并使用其全部功能,但没有成功,你能帮我一把吗?

Mayur Patel

改变标签控件中标签的颜色十分简单,但要想让属性页充满某种颜色,这个改造涉及相当大的工作量,对于一个胆小的人来说,是不敢轻举妄动的。对于标签来说,基本思路让该控件是自绘控件,然后处理 WM_DRAWITEM

消息。如果使用 MFC,你可以改写虚拟函数 DrawItem。

在 1998 年三月坎的 Microsoft Systems Journal 中,我示范了如何实现一个标签控件类 CTabCtrlWithDisable,这个类支持标签禁用。作为禁用标签的一部份,当标签被禁用时, CTabCtrlWithDisable

将标签文本颜色改成了浅灰色,本文我借用了 CTabCtrlWithDisable 中的一些代码实现了一个新类 CColorTablCtrl,使你能改变标签的颜色。(参见

Figure 5

为了使用 CColorTablCtrl, 在你的属性页中创建一个实例:class CMyPropSheet : public CPropertySheet {

protected:

CColorTabCtrl m_tabCtrl;

};

你必须在属性页的 OnInitDialog 处理例程(这样 MFC 将使用它)中子类化标签控件,然后按照自己的意愿设置前景色和背景色:

// in CMyPropSheet::OnInitDialog()

HWND hWndTab = (HWND)SendMessage(PSM_GETTABCONTROL);

m_tabCtrl.SubclassDlgItem(::GetDlgCtrlID(hWndTab), this);

m_tabCtrl.SetColor(WHITE, RED);

这里 WHITE 和 RED 是标准的 COLORREF 值,也就是 RGB(255, 255, 255) 和

RGB(255,0,0)。一旦你实例化并初始化 CColorTabCtrl,颜色标签控件便负责其余的事情。(参见 Figure 6)

Figure 6 带颜色的标签控件

CColorTabCtrl 有一个改写的 SubclassDlgItem,它调用 ModifyStyle 将式样改变为 TCS_OWNERDRAWFIXED。比较适合这项工作的地方是 PreSubclassWindow

函数中,因为不论控件被子类化,还是用 CreateWindow 创建(但在此杂志中,我得收缩代码,所以我采用的是简版),它都要被调用。注意 SubclassDlgItem

只是简单地改写,不是虚拟函数。为了设置颜色,SetColor 保存在两个成员变量 m_clrBackground 和 m_clrForeground.中传递的颜色。

一旦 将式样设置为自绘方式,只要到了绘制该标签时,Windows 便会发送 WM_DRAWITEM 消息。MFC 捕获这个消息并调用标签控件的虚拟 DrawItem

函数,它由 CColorTabCtrl 实现,用新的颜色绘制文本:

// in CColorTabCtrl::DrawItem

dc.FillSolidRect(rc, m_clrBackground);

dc.SetBkColor(m_clrBackground);

dc.SetTextColor(m_clrForeground);

dc.DrawText(...);

就这么简单直白,具体细节请看源代码。由于你可能在页面颜色没有改变的情况下也不想改变标签颜色,所以我还实现了一个 CColorPropertyPage

类,使你能改变属性页的背景色以便和标签颜色匹配,如果 Figure 6 所示,对于属性页而言,改变背景色最容易的方法是处理 WM_ERASEBKGND:

BOOL CColorPropertyPage::OnEraseBkgnd(CDC* pDC)

{

CRect rc;

GetClientRect(&rc);

pDC->FillSolidRect(rc, m_clrBackground);

return TRUE;

}

如果你只是尝试,你会发现各种恼人的问题。首先,如果你改变页面颜色,所有控件的背景色都是错误的,所以你必须还要解决这个问题。为此,你不得不处理处理 WM_CTLCOLOR

和 WM_ERASEBKGND。具体细节请参考我在 MSJ May 1997 专栏的文章。

另外一个问题是标签控件仍然以系统3D颜色绘制出边缘和四个角。这样肯定不行,为解决这个问题,除了处理 WM_PAINT

消息别无选择,并且要自己负责整个的绘制操作。它包括绘制选中时标签之间的偏移,以便使它看起来是在前面。至此,可以说你已开始重新发明标签控件,每一个使用

Windows

的程序员都知道,改变控件的颜色最痛苦的一件事情,并且一旦你开始走上这条路,便会觉得要做完它真是遥遥无期。不久标准的颜色看起来将比起初的颜色更好,或者你将会觉得为什么不转到

.NET 框架上来,在那上面改变颜色实在简单,用如下代码即可:ctl.BackColor = Color.Aquamarine;

编程愉快!

向 Paul 提问和评论请发到 cppqa@microsoft.com.

作者简介

Paul DiLascia 是一名自由作家,顾问和 Web/UI 设计者。他是《Writing Reusable Windows Code in

C++》书(Addison-Wesley, 1992)的作者。通过 http://www.dilascia.com 可以获得更多了解。

本文出自 MSDN Magazine

December 2004 期刊,可通过当地报摊获得,或者最好是

订阅

本文由 VCKBASE MTT 翻译

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有