<2006年7月>
2526272829301
2345678
9101112131415
16171819202122
23242526272829
303112345

统计

  • 随笔 - 44
  • 文章 - 0
  • 评论 - 86
  • 引用 - 0

常用链接

留言簿(6)

随笔分类(31)

随笔档案(44)

Mining

最新随笔

搜索

  •  

最新评论

阅读排行榜

评论排行榜

如何禁用Tree Control 的节点

Tree Control : how to disable an item

Tree Control 不支持节点的禁用, 但是可以通过自绘实现, 主要如下:

1. 标记节点是否禁用. 可以用 SetItemData & GetItemData 来设置&获取节点数据

2. 在禁用的节点上, 要过滤一些对节点操作, 如expanding, selecting, drag & drop 等.
TVN_SELCHANGING  节点选中改变时
TVN_ITEMEXPANDING 节点展开时
TVN_BEGINDRAG  节点开始被拖拉
TVN_BEGINLABELEDIT 节点被编辑

//Preventing selection: (handle TVN_SELCHANGING)
void CYourDialog::OnSelchangingTree(NMHDR* pNMHDR, LRESULT* pResult) 
{
  NM_TREEVIEW
* pNMTreeView = (NM_TREEVIEW*) pNMHDR;
  
if(((CItemStruct *) m_tree.GetItemData(pNMTreeView->iNewItem))->m_bDisabled)
  {
    
*pResult = 1// 设置 *pResult=1 表示TVN_SELCHANGING 这个操作不能继续
    return;
  }
  
*pResult = 0;
}


3. 对禁用节点进行自画, 用图标,颜色将禁用节点和其他节点进行区分
a) 直接在 WM_PAINT 中进行自画 (或对Tree Control的绘图结果进行修改)
 可以参考: 如何在树型控件中使用背景位图

b) 相应 WM_OWERDRAW 事件
 可以参考: Outlook风格的单列使用不同的颜色显示新邮件数


参考:
MFC Tree Control: How to disable an item? 
Setting color and font attribute for individual items

posted @ 2008-01-14 19:16 泡泡牛 阅读(1929) | 评论 (1)编辑 收藏
COM 类工厂有必要存在吗

1. IClassFactory 的用途

http://www.80diy.com/home/20041120/19/3572410.html 看到几段关于COM 的类厂的话,

"""
类厂用来抽象组件的create过程,客户不需要知道组件的详细情况,也不需要知道类厂的详细情况,只要知道CoCreateInstance可以创建组件即可。而CoCreateInstance内部调用DllGetClassObject来生成该组件的类厂,由于类厂有组件的作者撰写,所以对组件类可谓知根知底,由类厂来生成组件完全行得通,这样客户和组件就进一步划分,客户只能查询该组件是否支持某借口,而对组件的其他情况一无所知,这样的划分可以使组件和客户间的耦合更小。
"""

"""
组件如果将某接口的全部方法都实现了,就称该组件支持某接口,com并没有规定组件和接口之间是虚函数继承的关系,只是在c++中以这种方法来实现最好而已。  
IClassFactory说穿了就是专门构造组件的类,这样做是为了抽象,因为客户没有必要知道组件是什么,如果由客户直接构造组件,客户势必要知道组件的信息,com就失去了它的意义了,所以,规定了一个类厂(支持IClassFactory接口),每个组件的类厂都很清楚并且也只清楚该组件的信息,而客户只需要调用com库函数CoCreateInstance就可以了。  
下面是流程图:  
  CoCreateInstance -> CoGetClassObject -> DllGetClassObject -> new ClassFactory -> IClassFactory::CreateInstance() -> new Component
"""

并且在 http://www.codeproject.com/com/comintro2.asp 也看到几段话

"""
每次实现组件对象类的时候,都要写一个旁类负责创建第一个组件对象类的实例。这个旁类就叫这个组件对象类的类工厂(class factory),其唯一目的是创建COM对象。之所以要一个类工厂,是因为语言无关的缘故。COM本身并不创建对象,因为它不是独立于语言的也不是独立于实现的。
当某个客户端想要创建一个COM对象时,COM库就从COM服务器请求类工厂。然后类工厂创建COM对象并将它返回客户端。它们的通讯机制由函数DllGetClassObject()来提供。
"""

在<COM 技术内幕> 中, 对类厂的引入也有描述.
主要是:
a. 在面向对象系统中, 对象创建是非常重要的, 因为要使用它必须先创建它. 所以尽可能灵活的创建对象(component)
b. 在CoCreateInstance 创建对象过程是: 传给一共CLSID, 然后创建成相应组件, 并返回所请求的指针. 其弊端在于无法提供给客户一种控制对象创建过程的方法. (问题关键不在初始化, 而是控制创建对象过程)
c. IClassFactory2 成批的调用接口.


2.
参考CoCreateInstance 的实现过程:
CoCreateInstance -> CoGetClassObject -[系统|组件代码]-> DllGetClassObject -> new ClassFactory -> IClassFactory::CreateInstance() -> new Component

因为 DllGetClassObject -> new ClassFactory -> IClassFactory::CreateInstance() -> new Component 都是组件所来实现的, 而系统调用 CoCreateInstance 所提供的参数, 和通过自己使用IClassFactory 来创建Component 的参数是没有变化的, 所以如果省略 ClassFactory 应该也可以.

CoCreateInstance -> CoGetClassObject -[系统|组件代码]-> DllGetClassObject -> new Component

DllGetClassObject 完全可以完成<COM 技术内幕说的> a. 灵活创建对象, b. 控制创建过程, c. IClassFactory2 , 而且这样子的实现也与语言无关. 
所以感觉没有必要一定要用到IClassFactory 这个接口


3.
因此在实现的时候, 完全可以这样子的实现组件
CCoClass : public IA, public IB, public IClassFactory
{
......
}

DllGetClassObject()
{
new CCoClass
}

而不需要额外的用一个类单独的去实现IClassFactory . 好像ATL 默认的就是这么干的, 提供了一个CComCoClass<CCoClass, &CLSID_CCoClass)  实现类.

这个是我的对COM 的IClassFactory 的理解, 感觉没必要多一个这个东西.

不知道大家是如何看待这个东西的:)


 

posted @ 2007-11-18 22:53 泡泡牛 阅读(4007) | 评论 (6)编辑 收藏
窗口的子类化与超类化

1. 子类化
改变一个已经存在的窗口实例的性质:消息处理与其他实例属性。
在SDK编程范畴内,子类化就是改变一个窗口实例的窗口函数(通过GetWindowLong()和SetWindowLong()),子类化所要做的就是为某窗口实例编写新的窗口函数。其操作是在实例级别上进行的。
在MFC中子类化的情况有所不同:所有MFC窗口有相同的窗口函数,由该窗口函数根据窗口句柄查找窗口实例,在把消息映射到该窗口类(class)得消息处理函数上。为了利用MFC的消息映射机制,不宜改变窗口函数(名),MFC也把子类化封装在函数SubclassWindow()中。但子类化的本质没有变:在实例级别影响窗口的消息及其处理。例:
Class  B :public A
{
  ……
}
A  a;
B  b;
HWND ha=a.GetSafeHwnd();
b.SubclassWindow(ha); #当然A 和B 不一定是继承关系。
注意:在被子类化的窗口销毁之前,必须执行窗口的反子类化:
b.UnSubclassWindow();


2 超类化
窗口超类化是在窗口类——WNDCLASS或WNDCLASSEX(非MFC类概念)级别进行的改变窗口类特征的
使用过程:首先获得一个已存在的窗口类,然后设置窗口类,最后注册该窗口类。
例:
WNDCLASSEX  wc;
wc.cbSize=sizeof(wc); //Windows用来进行版本检查的,与窗口特征无关
GetClassInfoEx(hinst,”XXXXXX”,&wc);
 // hinst—定义窗口类XXXXXX的模块的句柄,如为系统定义的窗口类(如:EDIT、BUTTON)则hinst=NULL.。
wc.lpszClassName = “YYYYYYY”;//必须改变窗口类的名字
wc.hbrBackGround = CreateSolidBrush(RGB(0,0.0));//改变背景刷
wc.lpfnWndProc = NewWndProc;//改变窗口函数
……
RegisterClassEx(&wc);// 注册新窗口类
//使用窗口类
……
::CreateWindow(_T(“YYYYYYYY”,……);

故超类化只能改变自己创建的窗口的特征,而不能用于由Windows创建的窗口(如对话框上的按钮就不能进行超类化) 。而子类化是实例级别上的,只要能获得窗口的实例,就可对其子类化,这是唯一的子类化对于超类化的优势。另外,凡是子类化可实现的,超类化都可实现,不过超类化用起来较麻烦。


3. 总结

(0) 子类化修改窗口过程函数,  超类化修改窗口类(新的窗口类名)
(1) 子类化是在窗口实例级别上的,超类化是在窗口类(WNDCLASS)级别上的。
(2) 超类化可以完成比子类化更复杂的功能,在SDK范畴上,可以认为子类化是超类化的子集。
(3) 子类化只能改变窗口创建后的性质,对于窗口创建期间无能为力(无法截获ON_CREATE 事件),而超类化可以实现;超类化不能用于Windows创建的窗口,子类化可以。 


4. 其他
眼见为实(2):介绍Windows的窗口、消息、子类化和超类化 这里有一个例子..
可以得出结论
a) 子类化的classname 是不会变化的, 而超类化使用新注册classname
b) 子类化 & 超类化 描述的是一个动作 和实现方法没什么关系..... 主要是子类化是SubclassWindow, SubclassDlgItem, 而超类化是RegisterClassEx(&newwindowclass)
c) 感觉具体没有必要区分这些, 实现功能就行了, 呵呵


posted @ 2007-08-24 18:58 泡泡牛 阅读(6822) | 评论 (7)编辑 收藏
Some DirectShow Samples Break in Visual Studio 2005

zt: http://blogs.msdn.com/mikewasson/archive/2005/05/23/some-directshow-samples-break-in-visual-studio-2005.aspx

DirectX 9.0 与 VS 2005 之间存在冲突, 主要因为VS 2005 的语法比VC6 & VS2003 更加严格, 所以一些DirectX 自带的代码需要更改以后才能编译通过. 本来想自己改的, 不过在网上发现了有人已经做了这个:)


[Note: This post applies to the Platform SDK for Windows Server 2003 SP1 and Server 2003 R2. These issues were fixed in the Windows SDK for Vista.]  

Some of the DirectShow samples break if you install Visual Studio 2005 Beta 2. Most of the errors that I found fall into three categories:

  • C4430: Missing type specifier. To conform with C++, undeclared types do not default to int. All types must be declared. Fix: Declare the type, or suppress the warning with the "/wd4430" flag.
  • C4996: ' xxxx' was declared deprecated. You may be including an older version of strsafe.h from the DirectX SDK or the Platform SDK. You should include the version installed with Visual Studio. (But it's probably harmless to ignore this warning.) 
  • C2065: 'xxx': undeclared identifier. To conform with C++, the scope of a variable declared inside a "for" loop is restricted to the loop. Fixes: (a) Move the declaration outside the for loop. (b) Redeclare the variable in multiple scopes, if you don't need it to persist outside the loop. (c) Set the /Zc:forScope flag. (You can find this under Project, Properties, Configuration Properties, C/C++, Language, Force Conformance In For Loop Scope. Set to "No".)

Here are the specific fixes that I made. Warning: I have not thoroughly tested these, and I only tried them under the "Windows XP 32-bit Debug" environment in Platform SDK. You should use your own judgment before making any of these fixes.

  • BaseClasses\ctlutil.h (278)  
        (LONG) operator=(LONG);
  • BaseClasses\wxdebug.cpp (564)
        static DWORD g_dwLastRefresh = 0;
  • BaseClasses\winutil.cpp (2092)
       UINT Count;
       for (Count = 0;Count < Result;Count++) {
  •  BaseClasses\outputq.cpp (635)
       long iDone = 0;
       for (iDone = 0;
  • Capture\AmCap\amcap.cpp (691)
        for(int i = 0; i < NUMELMS(gcap.rgpmAudioMenu); i++)
  • Capture\AmCap\amcap (2795)
        for(int i = 0; i < NUMELMS(gcap.rgpmAudioMenu); i++)
  • DMODemo\dsutil.cpp (686)
        DWORD i = 0;
        for( i=0; i<m_dwNumBuffers; i++ )
  • dmoimpl.h (622)   [In the Platform SDK headers]
        for (DWORD dw = 0; dw < NUMBEROFOUTPUTS; dw++) {
  • DMO\GargleDMO\MedParamBase\param.cpp (91)
        for (DWORD dwIndex = 0; dwIndex < cParams; dwIndex++)
  • DMO\GargleDMO\MedParamBase\param.cpp (309)
        CCurveItem *pCurve = NULL;
        for (pCurve = pCurveHead;
  • DMO\GargleDMO\gargle.cpp (145)
        for (DWORD i = 0; i < cOutputStreams && SUCCEEDED(hr); ++i)
  • Filters\Dump\dump.cpp (426)
        for (int Loop = 0;Loop < (DataLength % BYTES_PER_LINE);Loop++)
  • Filters\Gargle\gargle.cpp (212)
        static int m_nInstanceCount; // total instances
  • Filters\RGBFilters\RateSource\ratesource.cpp (382)
        for( int y = 0 ; y < DEFAULT_HEIGHT ; y++ )
  • Filters\RGBFilters\RateSource\ratesource.cpp (387)
        for( int y = 0 ; y < DEFAULT_WIDTH ; y++ )
  • VMR\VMRXclBasic and VMR\Ticker: LNK1181: cannot open input file 'dxguid.lib'. This was an error in the makefile. Change to read:
        DXLIB="$(DXSDK_DIR)\Lib\x86"  (currently says "x32")
  • VMR\VMRXcl and VMR\VMRMulti: C1083: Cannot open include file: 'd3dxmath.h': No such file or directory. This is an old DX header that is no longer included in DX or in Visual Studio. Unfortunately the only fix is to download an older version of the DirectX SDK.
  • VMR9\MultiVMR9\GamePlayer\character.cpp (383)
        DWORD i = 0;
        for (i = 0; i < pMeshContainer->NumInfl; ++i)
  • VMR9\MultiVMR9\DLL\MixerControl.h (28)
        static const DWORD MultiVMR9Mixer_DefaultFVF = D3DFVF_XYZ | D3DFVF_DIFFUSE | D3DFVF_TEX1;
  • VMR9\VMRAllocator: error LNK2019: unresolved external symbol "wchar_t * __stdcall _com_util::ConvertStringToBSTR(char const *)" (etc).  Add this to the makefile:
        LINK32_LIBS = \
        comsuppw.lib \
        shell32.lib \

 

posted @ 2007-06-26 11:07 泡泡牛 阅读(2281) | 评论 (2)编辑 收藏
利用IE 实现Web 页面截图

1. 目的

   在Codeproject 中看到有人做这个,  稍微做了下修改, 做成了命令行的, 使用方法是 
   iesnap.exe url filename
      用以抓取 URL 对于的Web, 并且将Web 的截图保存在Filename 中.
      url : 要截图的网址
      filename: 截图保存文件名
       
2. 思路
   
 主要使用WebBrowser Control 和 MSHTML 来完成.
 a. 创建WebBrowser control
 b. 从WebBrowser Control 获取 IWebBrowser2 接口, 用 IWebBrowser2::Navigate2 来访问URL
 c. 获得IHTMLElementRender (WebBrowser -> IHTMLDocument2 -> IHTMLElement -> IHTMLElementRender), 使用该接口的DrawToDC 来获取URL 对应的页面截图内容.
 d. 用GDI 的 Image 来保存截图到Filename

3. 
   可以看看原文以得到更好的解答   
   我看的那个文章: Capture an HTML document as an image
   另外一篇C# 的文章: Image Capture Whole Web Page using C#


4. 问题
       
 a. DocumentComplete 事件会在URL 对应页面的每个元素下载完毕的时候都会发生, 所以:( 还不知道哪个事件是整个页面下载完毕的事件:/
 b. 现在是通过MFC 来创建WebBrowser Control 的, 不知道怎么在Win32 Application 中创建这个控件.
 c. 不知道如何得到整个页面的大小, 现在只能保存页面截图的一部分:(
   这个已经知道怎么做了. 
   IHTMLElement2 的 scrollWidth & scrollHeight 加上 scrollLeft & scrollTop 来实现, 但是不知道为什么, put_scrollTop & put_scrollLeft 这些函数好像不对, 只能截获到一部分页面截图.:( 不知道为什么
   


 代码可以在这里下载: http://www.cppblog.com/Files/bigsml/iesnapshot.zip

posted @ 2007-06-10 18:55 泡泡牛 阅读(4949) | 评论 (2)编辑 收藏
YC 浏览器

这款浏览器,称之为Yang C/C++ Compiler & Internet Browser或者YC编译型浏览器..

最开始是在程序员看到相关的文章的, 开始以为是在吹..但是今天用他的程序的时候, 感觉真的很nb..

"""
记者在杨晓兵处观摩了该产品的演示,发现它是由如下五个部分组成:HTML解析器;XML解析器(目前完成一部分);javascript脚本解释器,C/C++脚本解释器;C/C++编译器;文本及二进制编辑器.其中最令人瞩目的功能,自然是他所内嵌的C/C++实时编译功能了,这样使得C/C++的运行效果看上去类似动态语言,它不仅使得HTML支持脚本化的标准C/C++语言,而且使得C/C++ 能够与象JacaScript这样的动态语言可以互相调用.杨晓兵说“这将是软件开发方式的革新.通过这个实时编译功能,每个软件可以同时分割成若干模块,相互之间可以独立运行.”该编译器,与主流的C/C++编译器相比,YC++在功能进行了一些删减和改良,比如去掉了标准C/C++的函数重载、运算符重载、模板等,而改良的方向,主要侧重增加与网页开发环境(HTML4.0、CSS2.JavaScript)的互相支持和调用.令人颇感意外的是,该产品包括编译器、HTML解析器等在内的五个模块的所有代码,都与由杨晓兵独自一个人用C语言开发完成,从未借鉴参考任何其他源代码.杨晓兵解释说主要是为了便于调试和控制.这是相当大的工作量,整整花了他六年时间.从演示的运行效果来看,这款由一人手工完成的作品,编译速度竟然比VC++还要快一些.据透露是源自其许多算法的优化,如专门为变量参数作了可供快速查找的字典表等.
"""


在用的时候, 发现整个浏览器没有使用一个Windows 控件, 所有的控件都是由用GDI 级来绘制成的... 包括菜单, 输入框, 按钮, 树形控件等等等

Orz... 一把

可以在这里下载试用, 解压缩3个压缩包就行了.
/Files/bigsml/setyc.part01.rar
/Files/bigsml/setyc.part02.rar
/Files/bigsml/setyc.part03.rar

posted @ 2007-04-25 17:07 泡泡牛 阅读(3802) | 评论 (18)编辑 收藏
如何在IE页面上显示和编辑流程图

在Web 要实现地图,流程图,涂鸦等功能的时候,可以选择VML,SVG,Javaapplet,或者Flash。百度的map 搜索的地图展示就是使用VML 的。


1、 VML
VML是微软1999年9月附带IE5.0发布的,全称是Vector Markup Language(矢量可标记语言),其实是Word和HTML结合的产物。可以将Word文档另存为HTML,其中的文本和图片可以很容易的转换,但如果是手绘制的图形在以往的IE里面就无法解释了,如果都转换成图形文件又不太现实。于是微软把Word里面的图形控件结合到IE里面,使IE也具备了绘图功能。

VML 的资料:

MS 的 How to Use VML on Web Pages
   http://msdn.microsoft.com/library/default.asp?url=/workshop/author/VML/ref/basic1.asp
Think In VML(VML基本教程)
   http://www.itlearner.com/code/vml/index.html

VML画板 
   http://www.tool.la/VMLPalette/

VML编辑器
   http://www.dynamicdrive.com/dynamicindex11/editor.htm

流程图
   http://cosoft.org.cn/projects/webflow

How to Implement Vector Markup Language (VML) on Web Pages?
   http://www.tudjarov.hit.bg/vml/vmlbt.html

VML Chart 控件
   http://dev.csdn.net/article/23/23770.shtm

Chart Demo
   http://webfx.eae.net/dhtml/chart/demo.html

 


2、SVG
SVG,全称为Scalable Vector Graphics(可伸缩矢量图形)。它是W3C制定的、用矢量描述图形的XML应用标准。它有着许多的优点,比如可扩充性(scalable),动态的,交互性强。SVG支持无极放大,对SVG图片进行任意比例的放大都不会损害图片的显示(没有太多的失真),其他诸如BMP,JPEG格式的图片都不支持无级放大。SVG有动画元素,只要在SVG文件中嵌入SVG动画元素就可以实现动画效果了。同时SVG也定义了丰富的事件,包括鼠标事件和键盘事件,只要对SVG进行相关的脚本编程就可以实现SVG文件的交互操作。


相关示例:

SVG脚本编程
http://dev.csdn.net/article/26/26676.shtm
http://www.adobe.com/svg/examples.html

SVM & SVG 相关的库和应用有
Prototype Graphic Framework
   http://prototype-graphic.xilinus.com/
Cumulate Draw 很强大的一个画图工具, 基本上实现了Visio 的功能
   http://www.cumulatelabs.com/cumulatedraw/
RichDraw - Simple VML/SVG Editor
   http://starkravingfinkle.org/blog/2006/04/richdraw-simple-vmlsvg-editor/
Mapbar 地图
   http://main.mapbar.com/
mxDraw 很强大的一个流程图画板
   http://www.mxgraph.com/demo/mxgraph/editors/diagrameditor.html

Del.icio.us
   http://del.icio.us/hedgerwang/VML


3、Flash
Flash的强大功能就不用介绍了,相信它完全可以实现任何流程图的操作功能。但是,Flash的学习成本太高了,它的ActionScript让我们这些写惯了java、js的开发人员一头雾水。而且相关的网络资源实在是太少了,当初我google了N久,才勉强找到一个通过读取xml文件显示流程图的example,功能仅仅是显示xml文件中配置的流程节点(有需要源码的朋友可以mail我)。所以,要想实现强大的流程图编辑功能,Flash只推荐高手使用。
商用的软件有
Gliff    http://www.gliffy.com/ 
DrawAnywhere  http://drawanywhere.com/

Google code有一个开源项目 http://code.google.com/p/flexvizgraphlib/ , 其代码可以在
http://groups.google.com/group/flexvizgraphlib/web/FlexVisualGraph.zip 下载到

4、Java Applet
在SVM 和 SVG 出现以前,有网络涂鸦,就是用Applet 做成的,但是现在好像用的不多了。

ref:
Tools for Creating Charts and Diagrams 一篇英文的文章, 对现有的流程图创建工具有一个很好的介绍

posted @ 2007-01-11 12:06 泡泡牛 阅读(8873) | 评论 (7)编辑 收藏
Internet Explorer 7 Final(汉化+绕正版验证安装方法)

IE7 近几天发布了, 英文版可以通过下面的链接下载.
XP w/sp2
http://download.microsoft.com/download/3/8/8/38889DC1-848C-4BF2-8335-86C573AD86D9/IE7-WindowsXP-x86-enu.exe
2003
http://download.microsoft.com/download/D/1/3/D1346F12-F3A0-4AC6-8F5C-2BEA2A184957/IE7-WindowsServer2003-x86-enu.exe
xp&2003 64bit
http://download.microsoft.com/download/1/1/4/114D5B07-4DBC-42F3-96FA-2097E207D0AF/IE7-WindowsServer2003-x64-enu.exe
2003 ia64
http://download.microsoft.com/download/4/E/3/4E3E332E-4A7B-4E85-9F45-8209472F2FD2/IE7-WindowsServer2003-ia64-enu.exe

在安装中要求正版验证. 不过在网上已经出现了破解方法, 在 http://green.crsky.com/soft/4012.html 可以下载破解过的IE7.

1.先把IE7的安装文件用WinRAR解开.
2.将破解iecustom.dll放进Update目录,覆盖原有文件.
3.执行update.exe安装,这个时候先勾选安装界面的"Do not restart now",先不要重启,如下图:
20060826_022703_414.gif

4.复制下载来的normaliz.dll到Windows的System32目录(必须执行,否则系统将故障)
5.重新启动.
6.执行Update目录下的xmllitesetup.exe更新一下就可以了

一定一定要记住安装后勾选安装界面的"Do not restart now",先不要重启!等复制normaliz.dll到Windows的System32目录下再重启!其实先复制normaliz.dll到Windows的System32目录下也是可以的!下面2个不同版本都适合上面的方法!

Internet Explorer 7 正式版(Final)版本号7.0.5730.11;Internet Explorer 7 (RC1)版本号7.0.5700.6

posted @ 2006-10-19 14:38 泡泡牛 阅读(1061) | 评论 (1)编辑 收藏
用BoundsChecker检测内存泄漏 (zz)

  BoundsChecker采用一种被称为 Code Injection的技术,来截获对分配内存和释放内存的函数的调用。简单地说,当你的程序开始运行时,BoundsChecker的DLL被自动载入进程的地址空间(这可以通过system-level的Hook实现),然后它会修改进程中对内存分配和释放的函数调用,让这些调用首先转入它的代码,然后再执行原来的代码。BoundsChecker在做这些动作的时,无须修改被调试程序的源代码或工程配置文件,这使得使用它非常的简便、直接。

  这里我们以malloc函数为例,截获其他的函数方法与此类似。

  需要被截获的函数可能在DLL中,也可能在程序的代码里。比如,如果静态连结C-Runtime Library,那么malloc函数的代码会被连结到程序里。为了截获住对这类函数的调用,BoundsChecker会动态修改这些函数的指令。

  以下两段汇编代码,一段没有BoundsChecker介入,另一段则有BoundsChecker的介入:

126: _CRTIMP void * __cdecl malloc (
127: size_t nSize
128: )
129: {

00403C10 push ebp
00403C11 mov ebp,esp
130return _nh_malloc_dbg(nSize, _newmode, _NORMAL_BLOCK, NULL, 0);
00403C13 push 
0
00403C15 push 
0
00403C17 push 
1
00403C19 mov eax,[__newmode (0042376c)]
00403C1E push eax
00403C1F mov ecx,dword ptr [nSize]
00403C22 push ecx
00403C23 call _nh_malloc_dbg (00403c80)
00403C28 add esp,14h
131: }
以下这一段代码有BoundsChecker介入:
126: _CRTIMP void * __cdecl malloc (
127: size_t nSize
128: )
129: {

00403C10 jmp 01F41EC8
00403C15 push 
0
00403C17 push 
1
00403C19 mov eax,[__newmode (0042376c)]
00403C1E push eax
00403C1F mov ecx,dword ptr [nSize]
00403C22 push ecx
00403C23 call _nh_malloc_dbg (00403c80)
00403C28 add esp,14h
131: }

  当BoundsChecker介入后,函数malloc的前三条汇编指令被替换成一条jmp指令,原来的三条指令被搬到地址01F41EC8处了。当程序进入malloc后先jmp到01F41EC8,执行原来的三条指令,然后就是BoundsChecker的天下了。大致上它会先记录函数的返回地址(函数的返回地址在stack上,所以很容易修改),然后把返回地址指向属于BoundsChecker的代码,接着跳到malloc函数原来的指令,也就是在00403c15的地方。当malloc函数结束的时候,由于返回地址被修改,它会返回到BoundsChecker的代码中,此时BoundsChecker会记录由malloc分配的内存的指针,然后再跳转到到原来的返回地址去。

  如果内存分配/释放函数在DLL中,BoundsChecker则采用另一种方法来截获对这些函数的调用。BoundsChecker通过修改程序的DLL Import Table让table中的函数地址指向自己的地址,以达到截获的目的。

  截获住这些分配和释放函数,BoundsChecker就能记录被分配的内存或资源的生命周期。接下来的问题是如何与源代码相关,也就是说当BoundsChecker检测到内存泄漏,它如何报告这块内存块是哪段代码分配的。答案是调试信息(Debug Information)。当我们编译一个Debug版的程序时,编译器会把源代码和二进制代码之间的对应关系记录下来,放到一个单独的文件里(.pdb)或者直接连结进目标程序,通过直接读取调试信息就能得到分配某块内存的源代码在哪个文件,哪一行上。使用Code Injection和Debug Information,使BoundsChecker不但能记录呼叫分配函数的源代码的位置,而且还能记录分配时的Call Stack,以及Call Stack上的函数的源代码位置。这在使用像MFC这样的类库时非常有用,以下我用一个例子来说明:

void ShowXItemMenu()
{
 …
 CMenu menu;

 menu.CreatePopupMenu();
 
//add menu items.
 menu.TrackPropupMenu();
 …
}

void ShowYItemMenu( )
{
 …
 CMenu menu;
 menu.CreatePopupMenu();
 
//add menu items.
 menu.TrackPropupMenu();
 menu.Detach();
//this will cause HMENU leak
 …
}

BOOL CMenu::CreatePopupMenu()
{
 …
 hMenu 
= CreatePopupMenu();
 …
}
当调用ShowYItemMenu()时,我们故意造成HMENU的泄漏。但是,对于BoundsChecker来说被泄漏的HMENU是在class CMenu::CreatePopupMenu()中分配的。假设的你的程序有许多地方使用了CMenu的CreatePopupMenu()函数,如CMenu::CreatePopupMenu()造成的,你依然无法确认问题的根结到底在哪里,在ShowXItemMenu()中还是在ShowYItemMenu()中,或者还有其它的地方也使用了CreatePopupMenu()?有了Call Stack的信息,问题就容易了。BoundsChecker会如下报告泄漏的HMENU的信息:
Function
File
Line

CMenu::CreatePopupMenu
E:\
8168\vc98\mfc\mfc\include\afxwin1.inl
1009

ShowYItemMenu
E:\testmemleak\mytest.cpp
100
  
这里省略了其他的函数调用

如此,我们很容易找到发生问题的函数是ShowYItemMenu()。当使用MFC之类的类库编程时,大部分的API调用都被封装在类库的class里,有了Call Stack信息,我们就可以非常容易的追踪到真正发生泄漏的代码。

  记录Call Stack信息会使程序的运行变得非常慢,因此默认情况下BoundsChecker不会记录Call Stack信息。可以按照以下的步骤打开记录Call Stack信息的选项开关:

  1. 打开菜单:BoundsChecker|Setting…

  2. 在Error Detection页中,在Error Detection Scheme的List中选择Custom

  3. 在Category的Combox中选择 Pointer and leak error check

  4. 钩上Report Call Stack复选框

  5. 点击Ok

  基于Code Injection,BoundsChecker还提供了API Parameter的校验功能,memory over run等功能。这些功能对于程序的开发都非常有益。由于这些内容不属于本文的主题,所以不在此详述了。

  尽管BoundsChecker的功能如此强大,但是面对隐式内存泄漏仍然显得苍白无力。所以接下来我们看看如何用Performance Monitor检测内存泄漏。

  使用Performance Monitor检测内存泄漏

  NT的内核在设计过程中已经加入了系统监视功能,比如CPU的使用率,内存的使用情况,I/O操作的频繁度等都作为一个个Counter,应用程序可以通过读取这些Counter了解整个系统的或者某个进程的运行状况。Performance Monitor就是这样一个应用程序。

  为了检测内存泄漏,我们一般可以监视Process对象的Handle Count,Virutal Bytes 和Working Set三个Counter。Handle Count记录了进程当前打开的HANDLE的个数,监视这个Counter有助于我们发现程序是否有Handle泄漏;Virtual Bytes记录了该进程当前在虚地址空间上使用的虚拟内存的大小,NT的内存分配采用了两步走的方法,首先,在虚地址空间上保留一段空间,这时操作系统并没有分配物理内存,只是保留了一段地址。然后,再提交这段空间,这时操作系统才会分配物理内存。所以,Virtual Bytes一般总大于程序的Working Set。监视Virutal Bytes可以帮助我们发现一些系统底层的问题; Working Set记录了操作系统为进程已提交的内存的总量,这个值和程序申请的内存总量存在密切的关系,如果程序存在内存的泄漏这个值会持续增加,但是Virtual Bytes却是跳跃式增加的。

  监视这些Counter可以让我们了解进程使用内存的情况,如果发生了泄漏,即使是隐式内存泄漏,这些Counter的值也会持续增加。但是,我们知道有问题却不知道哪里有问题,所以一般使用Performance Monitor来验证是否有内存泄漏,而使用BoundsChecker来找到和解决。

  当Performance Monitor显示有内存泄漏,而BoundsChecker却无法检测到,这时有两种可能:第一种,发生了偶发性内存泄漏。这时你要确保使用Performance Monitor和使用BoundsChecker时,程序的运行环境和操作方法是一致的。第二种,发生了隐式的内存泄漏。这时你要重新审查程序的设计,然后仔细研究Performance Monitor记录的Counter的值的变化图,分析其中的变化和程序运行逻辑的关系,找到一些可能的原因。这是一个痛苦的过程,充满了假设、猜想、验证、失败,但这也是一个积累经验的绝好机会。

  总结

  内存泄漏是个大而复杂的问题,即使是Java和.Net这样有Gabarge Collection机制的环境,也存在着泄漏的可能,比如隐式内存泄漏。由于篇幅和能力的限制,本文只能对这个主题做一个粗浅的研究。其他的问题,比如多模块下的泄漏检测,如何在程序运行时对内存使用情况进行分析等等,都是可以深入研究的题目。如果您有什么想法,建议或发现了某些错误,欢迎和我交流。


posted @ 2006-10-19 13:45 泡泡牛 阅读(9123) | 评论 (2)编辑 收藏
C语言中可变参数的用法

我们在C语言编程中会遇到一些参数个数可变的函数,例如printf()这个函数,这里将介绍可变函数的写法以及原理.

* 1. 可变参数的宏

一般在调试打印Debug 信息的时候, 需要可变参数的宏. 从C99开始可以使编译器标准支持可变参数宏(variadic macros), 另外GCC 也支持可变参数宏, 但是两种在细节上可能存在区别.

1. __VA_ARGS__

__VA_ARGS__ 将"..." 传递给宏.如
#define debug(format, ...) fprintf(stderr, fmt, __VA_ARGS__)

在GCC中也支持这类表示, 但是在G++ 中不支持这个表示.

2. GCC 的复杂宏

GCC使用一种不同的语法从而可以使你可以给可变参数一个名字,如同其它参数一样。
#define debug(format, args...) fprintf (stderr, format, args)

这和上面举的那个定义的宏例子是完全一样的,但是这么写可读性更强并且更容易进行描述。

3. ##__VA_ARGS__

上面两个定义的宏, 如果出现debug("A Message") 的时候, 由于宏展开后有个多余的逗号, 所以将导致编译错误. 为了解决这个问题,CPP使用一个特殊的‘##’操作。

#define debug(format, ...) fprintf (stderr, format, ## __VA_ARGS__)
这里,如果可变参数被忽略或为空,‘##’操作将使预处理器(preprocessor)去除掉它前面的那个逗号。如果你在宏调用时,确实提供了一些可变参数,GNU CPP也会工作正常,它会把这些可变参数放到逗号的后面。

4. 其他方法

一种流行的技巧是用一个单独的用括弧括起来的的 "参数" 定义和调用宏, 参数在宏扩展的时候成为类似 printf() 那样的函数的整个参数列表。
#define DEBUG(args) (printf("DEBUG: "), printf(args))


* 2. 可变参数的函数

写可变参数的C函数要在程序中用到以下这些宏:
void va_start( va_list arg_ptr, prev_param )
type va_arg( va_list arg_ptr, type )
void va_end( va_list arg_ptr )

va在这里是variable-argument(可变参数)的意思,这些宏定义在stdarg.h中.下面我们写一个简单的可变参数的函数,该函数至少有一个整数参数,第二个参数也是整数,是可选的.函数只是打印这两个参数的值.
void simple_va_fun(int i, ...)
{
    va_list arg_ptr;
    int j=0;
    
    va_start(arg_ptr, i);
    j=va_arg(arg_ptr, int);
    va_end(arg_ptr);
    printf("%d %d\n", i, j);
    return;
}

在程序中可以这样调用:
simple_va_fun(100);
simple_va_fun(100,200);

从这个函数的实现可以看到,使用可变参数应该有以下步骤:
1)首先在函数里定义一个va_list型的变量,这里是arg_ptr,这个变量是指向参数的指针.
2)然后用va_start宏初始化变量arg_ptr,这个宏的第二个参数是第一个可变参数的前一个参数,是一个固定的参数.
3)然后用va_arg返回可变的参数,并赋值给整数j. va_arg的第二个参数是你要返回的参数的类型,这里是int型.
4)最后用va_end宏结束可变参数的获取.然后你就可以在函数里使用第二个参数了.如果函数有多个可变参数的,依次调用va_arg获取各个参数.

如果我们用下面三种方法调用的话,都是合法的,但结果却不一样:
1)simple_va_fun(100);
结果是:100 -123456789(会变的值)
2)simple_va_fun(100,200);
结果是:100 200
3)simple_va_fun(100,200,300);
结果是:100 200

我们看到第一种调用有错误,第二种调用正确,第三种调用尽管结果正确,但和我们函数最初的设计有冲突.下面一节我们探讨出现这些结果的原因和可变参数在编译器中是如何处理的.
* 3. 可变参数函数原理

va_start,va_arg,va_end是在stdarg.h中被定义成宏的,由于硬件平台的不同,编译器的不同,所以定义的宏也有所不同,下面以VC++中stdarg.h里x86平台的宏定义摘录如下:

typedef char * va_list;
#define _INTSIZEOF(n) ((sizeof(n)+sizeof(int)-1)&~(sizeof(int) - 1) )
#define va_start(ap,v) ( ap = (va_list)&v + _INTSIZEOF(v) )
#define va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
#define va_end(ap) ( ap = (va_list)0 )

定义_INTSIZEOF(n)主要是为了内存对齐,C语言的函数是从右向左压入堆栈的(设数据进栈方向为从高地址向低地址发展,即首先压入的数据在高地址). 下图是函数的参数在堆栈中的分布位置:

低地址    |-----------------------------|<-- &v
        |第n-1个参数(最后一个固定参数)|
        |-----------------------------|<--va_start后ap指向
        |第n个参数(第一个可变参数) |
        |-----------------------------|
        |....... |
        |-----------------------------|
        |函数返回地址 |
高地址  |-----------------------------|

1. va_list 被定义为char *
2. va_start 将地址ap定义为 &v+_INTSIZEOF(v),而&v是固定参数在堆栈的地址,所以va_start(ap, v)以后,ap指向第一个可变参数在堆栈的地址
3. va_arg 取得类型t的可变参数值,以int型为例,va_arg取int型的返回值:
   j= ( *(int*)((ap += _INTSIZEOF(int))-_INTSIZEOF(int)) );
4. va_end 使ap不再指向堆栈,而是跟NULL一样.这样编译器不会为va_end产生代码.

在不同的操作系统和硬件平台的定义有些不同,但原理却是相似的.


* 4. 小结

对于可变参数的函数,因为va_start, va_arg, va_end等定义成宏,所以它显得很愚蠢,可变参数的类型和个数需要在该函数中由程序代码控制;另外,编译器对可变参数的函数的原型检查不够严格,对编程查错不利.
所以我们写一个可变函数的C函数时,有利也有弊,所以在不必要的场合,无需用到可变参数.如果在C++里,我们应该利用C++的多态性来实现可变参数的功能,尽量避免用C语言的方式来实现.


* 5. 附一些代码

#define debug(format, ...) fprintf(stderr, fmt, __VA_ARGS__)
#define debug(format, args...) fprintf (stderr, format, args)
#define debug(format, ...) fprintf (stderr, format, ## __VA_ARGS__)

// 使用va... 实现
void debug(const char *fmt, ...)
{
    int nBuf;
    char szBuffer[1024];
    va_list args;

    va_start(args, fmt);
    nBuf = vsprintf(szBuffer, fmt, args) ;
    assert(nBuf >= 0);

    printf("QDOGC ERROR:%s\n",szBuffer);
    va_end(args);
}

posted @ 2006-10-19 12:55 泡泡牛 阅读(3685) | 评论 (1)编辑 收藏
仅列出标题
共5页: 1 2 3 4 5