﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>IT博客-我的博客-文章分类-VisualC＋＋</title><link>http://www.cnitblog.com/luoyi/category/1083.html</link><description>技术园地</description><language>zh-cn</language><lastBuildDate>Sat, 01 Oct 2011 08:02:24 GMT</lastBuildDate><pubDate>Sat, 01 Oct 2011 08:02:24 GMT</pubDate><ttl>60</ttl><item><title>用VC进行COM编程所必须掌握的理论知识</title><link>http://www.cnitblog.com/luoyi/articles/3609.html</link><dc:creator>罗毅</dc:creator><author>罗毅</author><pubDate>Thu, 27 Oct 2005 07:51:00 GMT</pubDate><guid>http://www.cnitblog.com/luoyi/articles/3609.html</guid><wfw:comment>http://www.cnitblog.com/luoyi/comments/3609.html</wfw:comment><comments>http://www.cnitblog.com/luoyi/articles/3609.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/luoyi/comments/commentRss/3609.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/luoyi/services/trackbacks/3609.html</trackback:ping><description><![CDATA[     这篇文章是给初学者看的，尽量写得比较通俗易懂，并且尽量避免编程细节。完全是根据<BR>我自己的学习体会写的，其中若有技术上的错误之处，请大家多多指正。<BR><BR>1。为什么用COM<BR>2。用VC进行COM编程，必需要掌握哪些COM理论知识<BR><BR>一、为什么要用COM<BR>   软件工程发展到今天，从一开始的结构化编程，到面向对象编程，再到现在的COM，编程，目<BR>标只有一个，就是希望软件能象积方块一样是累起来的，是组装起来的，而不是一点点编出来的。<BR>结构化编程是函数块的形式，通过把一个软件划分成许多模块，每个模块完成各自不同的功<BR>能，尽量做到高内聚低藕合，这已经是一个很好的开始，我们可以把不同的模块分给不同的人去<BR>做，然后合到一块，这已经有了组装的概念了。软件工程的核心就是要模块化，最理想的情况就<BR>是100%内聚0%藕合。整个软件的发展也都是朝着这个方向走的。结构化编程方式只是一个开始。<BR>下一步就出现了面向对象编程，它相对于面向功能的结构化方式是一个巨大的进步。我们知道整<BR>个自然界都是由各种各样不同的事物组成的，事物之间存在着复杂的千丝万缕的关系，而正是靠<BR>着事物之间的联系、交互作用，我们的世界才是有生命力的才是活动的。我们可以认为在自然界<BR>中事物做为一个概念，它是稳定的不变的，而事物之间的联系是多变的、运动的。事物应该是这<BR>个世界的本质所在。面向对象的着眼点就是事物，就是这种稳定的概念。每个事物都有其固有的<BR>属性，都有其固有的行为，这些都是事物本身所固有的东西，而面向对象的方法就是描述出这种<BR>稳定的东西。而面向功能的模块化方法它的着眼点是事物之间的联系，它眼中看不到事物的概念<BR>它只注重功能，我们平常在划分模块的时侯有没有想过这个函数与哪些对象有关呢？很少有人这<BR>么想，一个函数它实现一种功能，这个功能必定与某些事物想联系，我们没有去掌握事物本身而<BR>只考虑事物之间是怎么相互作用而完成一个功能的。说白了，这叫本末倒置，也叫急功近利，因<BR>为不是我们智慧不够，只是因为我们没有多想一步。面向功能的结构化方法因为它注意的只是事<BR>物之间的联系，而联系是多变的，事物本身可能不会发生大的变化，而联系则是很有可能发生改<BR>变的，联系一变，那就是另一个世界了，那就是另一种功能了。如果我们用面向对象的方法，我<BR>们就可以以不变应万变，只要事先把事物用类描述好，我们要改变的只是把这些类联系起来方法<BR>只是重新使用我们的类库，而面向过程的方法因为它构造的是一个不稳定的世界，所以一点小小<BR>的变化也可能导致整个系统都要改变。然而面向对象方法仍然有问题，问题在于重用的方法。搭<BR>积木式的软件构造方法的基础是有许许多多各种各样的可重用的部件、模块。我们首先想到的是<BR>类库，因为我们用面向对象的方法产生的直接结果就是许多的类。但类库的重用是基于源码的方<BR>式，这是它的重大缺陷。首先它限制了编程语言，你的类库总是用一种语言写的吧，那你就不能<BR>拿到别的语言里用了。其次你每次都必须重新编译，只有编译了才能与你自己的代码结合在一起<BR>生成可执行文件。在开发时这倒没什么，关键在于开发完成后，你的EXE都已经生成好了，如果<BR>这时侯你的类库提供厂商告诉你他们又做好了一个新的类库，功能更强大速度更快，而你为之心<BR>动又想把这新版的类库用到你自己的程序中，那你就必须重新编译、重新调试！这离我们理想的<BR>积木式软件构造方法还有一定差距，在我们的设想里希望把一个模块拿出来再换一个新的模块是<BR>非常方便的事，可是现在不但要重新编译，还要冒着很大的风险，因为你可能要重新改变你自己<BR>的代码。另一种重用方式很自然地就想到了是DLL的方式。Windows里到处是DLL，它是Windows<BR>的基础，但DLL也有它自己的缺点。总结一下它至少有四点不足。(1)函数重名问题。DLL里是一<BR>个一个的函数，我们通过函数名来调用函数，那如果两个DLL里有重名的函数怎么办？(2)各编译<BR>器对C＋＋函数的名称修饰不兼容问题。对于C＋＋函数，编译器要根据函数的参数信息为它生成<BR>修饰名，DLL库里存的就是这个修饰名，但是不同的编译器产生修饰的方法不一样，所以你在VC<BR>里编写的DLL在BC里就可以用不了。不过也可以用extern "C";来强调使用标准的C函数特性，关闭<BR>修饰功能，但这样也丧失了C＋＋的重载多态性功能。(3)路径问题。放在自己的目录下面，别人<BR>的程序就找不到，放在系统目录下，就可能有重名的问题。而真正的组件应该可以放在任何地方<BR>甚至可以不在本机，用户根本不需考虑这个问题。(4)DLL与EXE的依赖问题。我们一般都是用隐<BR>式连接的方式，就是编程的时侯指明用什么DLL，这种方式很简单，它在编译时就把EXE与DLL绑<BR>在一起了。如果DLL发行了一个新版本，我们很有必要重新链接一次，因为DLL里面函数的地址可<BR>能已经发生了改变。DLL的缺点就是COM的优点。首先我们要先把握住一点，COM和DLL一样都是<BR>基于二进制的代码重用，所以它不存在类库重用时的问题。另一个关键点是，COM本身也是DLL，<BR>既使是ActiveX控件.ocx它实际上也是DLL，所以说DLL在还是有重用上有很大的优势，只不过我们<BR>通过制订复杂的COM协议，通COM本身的机制改变了重用的方法，以一种新的方法来利用DLL，来<BR>克服DLL本身所固有的缺陷，从而实现更高一级的重用方法。COM没有重名问题，因为根本不是通<BR>过函数名来调用函数，而是通过虚函数表，自然也不会有函数名修饰的问题。路径问题也不复存<BR>在，因为是通过查注册表来找组件的，放在什么地方都可以，即使在别的机器上也可以。也不用<BR>考虑和EXE的依赖关系了，它们二者之间是松散的结合在一起，可以轻松的换上组件的一个新版<BR>本，而应用程序混然不觉。<BR><BR><BR>二、用VC进行COM编程，必须要掌握哪些COM理论知识<BR>　　我见过很多人学COM，看完一本书后觉得对COM的原理比较了解了，COM也不过如此，可是就<BR>是不知道该怎么编程序，我自己也有这种情况，我经历了这样的阶段走过来的。要学COM的基本<BR>原理，我推荐的书是《COM技术内幕》。但仅看这样的书是远远不够的，我们最终的目的是要学<BR>会怎么用COM去编程序，而不是拼命的研究COM本身的机制。所以我个人觉得对COM的基本原理不<BR>需要花大量的时间去追根问底，没有必要，是吃力不讨好的事。其实我们只需要掌握几个关键概<BR>念就够了。这里我列出了一些我自己认为是用VC编程所必需掌握的几个关键概念。(这里所说的<BR>均是用C语言条件下的COM编程方式)<BR>(1) COM组件实际上是一个C++类，而接口都是纯虚类。组件从接口派生而来<BR>    我们可以简单的用纯粹的C++的语法形式来描述COM是个什么东西：<BR>class IObject<BR>{<BR>public:<BR>virtual Function1(...) = 0;<BR>virtual Function2(...) = 0;<BR>....<BR>};<BR>class MyObject : public IObject<BR>{<BR>public:<BR>virtual Function1(...){...}<BR>virtual Function2(...){...}<BR>....<BR>};<BR>    看清楚了吗？IObject就是我们常说的接口，MyObject就是所谓的COM组件。切记切记接口都<BR>    纯虚类，它所包含的函数都是纯虚函数，而且它没有成员变量。而COM组件就是从这些纯虚<BR>    类继承下来的派生类，它实现了这些虚函数，仅此而已。从上面也可以看出，COM组件是以<BR>    C++为基础的，特别重要的是虚函数和多态性的概念，COM中所有函数都是虚函数，都必须通<BR>    过虚函数表VTable来调用，这一点是无比重要的，必需时刻牢记在心。为了让大家确切了解<BR>　　一下虚函数表是什么样子，从COM＋技术内幕中COPY了下面这个示例图：<BR>　　<BR><BR><BR>(2) COM组件有三个最基本的接口类，分别是IUnknown、IClassFactory、IDispatch<BR>    COM规范规定任何组件、任何接口都必须从IUnknown继承，IUnknown包含三个函数，分别是<BR>    QueryInterface、AddRef、Release。这三个函数是无比重要的，而且它们的排列顺序也是不<BR>    可改变的。QueryInterface用于查询组件实现的其它接口，说白了也就是看看这个组件的父<BR>    类中还有哪些接口类，AddRef用于增加引用计数，Release用于减少引用计数。引用计数也<BR>    是COM中的一个非常重要的概念。大体上简单的说来可以这么理解，COM组件是个DLL，当客<BR>    户程序要用它时就要把它装到内存里。另一方面，一个组件也不是只给你一个人用的，可能<BR>    会有很多个程序同时都要用到它。但实际上DLL只装载了一次，即内存中只有一个COM组件，<BR>    那COM组件由谁来释放？由客户程序吗？不可能，因为如果你释放了组件，那别人怎么用，<BR>    所以只能由COM组件自己来负责。所以出现了引用计数的概念，COM维持一个计数，记录当前<BR>    有多少人在用它，每多一次调用计数就加一，少一个客户用它就减一，当最后一个客户释放<BR>    它的时侯，COM知道已经没有人用它了，它的使用已经结束了，那它就把它自己给释放了。<BR>    引用计数是COM编程里非常容易出错的一个地方，但所幸VC的各种各种的类库里已经基本上<BR>    把AddRef的调用给隐含了，在我的印象里，我编程的时侯还从来没有调用过AddRef，我们<BR>    只需在适当的时侯调用Release。至少有两个时侯要记住调用Release，第一个是调用了<BR>    QueryInterface以后，第二个是调用了任何得到一个接口的指针的函数以后，记住多查MSDN<BR>    以确定某个函数内部是否调用了AddRef，如果是的话那调用Release的责任就要归你了。<BR>    IUnknown的这三个函数的实现非常规范但也非常烦琐，容易出错，所幸的事我们可能永远也<BR>    不需要自己来实现它们。<BR>    IClassFactory的作用是创建COM组件。我们已经知道COM组件实际上就是一个类，那我们平<BR>    常是怎么实例化一个类对象的？是用‘new’命令!很简单吧，COM组件也一样如此。但是谁<BR>    来new它呢？不可能是客户程序，因为客户程序不可能知道组件的类名字，如果客户知道组<BR>    件的类名字那组件的可重用性就要打个大大的折扣了，事实上客户程序只不过知道一个代表<BR>    着组件的128位的数字串而已，这个等会再介绍。所以客户无法自己创建组件，而且考虑一<BR>    下，如果组件是在远程的机器上，你还能new出一个对象吗？所以创建组件的责任交给了一<BR>    个单独的对象，这个对象就是类厂。每个组件都必须有一个与之相关的类厂，这个类厂知道<BR>    怎么样创建组件，当客户请求一个组件对象的实例时，实际上这个请求交给了类厂，由类厂<BR>    创建组件实例，然后把实例指针交给客户程序。这个过程在跨进程及远程创建组件时特别有<BR>    用，因为这时就不是一个简单的new操作就可以的了，它必须要经过调度，而这些复杂的操<BR>    作都交给类厂对象去做了。IClassFactory最重要的一个函数就是CreateInstance，顾名思议<BR>    就是创建组件实例，一般情况下我们不会直接调用它，API函数都为我们封装好它了，只有<BR>    某些特殊情况下才会由我们自己来调用它，这也是VC编写COM组件的好处，使我们有了更多<BR>    的控制机会，而VB给我们这样的机会则是太少太少了。<BR>    IDispatch叫做调度接口。它的作用何在呢？这个世上除了C++还有很多别的语言，比如VB、<BR>    VJ、VBScript、JavaScript等等。可以这么说，如果这世上没有这么多乱七八糟的语言，那<BR>    就不会有IDispatch。:-) 我们知道COM组件是C++类，是靠虚函数表来调用函数的，对于VC来<BR>    说毫无问题，这本来就是针对C++而设计的，以前VB不行，现在VB也可以用指针了，也可以<BR>    通过VTable来调用函数了，VJ也可以，但还是有些语言不行，那就是脚本语言，典型的如<BR>    VBScript、JavaScript。不行的原因在于它们并不支持指针，连指针都不能用还怎么用多态<BR>    性啊，还怎么调这些虚函数啊。唉，没办法，也不能置这些脚本语言于不顾吧，现在网页上<BR>    用的都是这些脚本语言，而分布式应用也是COM组件的一个主要市场，它不得不被这些脚本<BR>    语言所调用，既然虚函数表的方式行不通，我们只能另寻他法了。时势造英雄，IDispatch应<BR>    运而生。:-)  调度接口把每一个函数每一个属性都编上号，客户程序要调用这些函数属性的<BR>    时侯就把这些编号传给IDispatch接口就行了，IDispatch再根据这些编号调用相应的函数，仅<BR>    此而已。当然实际的过程远比这复杂，当给一个编号就能让别人知道怎么调用一个函数那不<BR>    是天方夜潭吗，你总得让别人知道你要调用的函数要带什么参数，参数类型什么以及返回什<BR>    东西吧，而要以一种统一的方式来处理这些问题是件很头疼的事。IDispatch接口的主要函数<BR>    是Invoke，客户程序都调用它，然后Invoke再调用相应的函数，如果看一看MS的类库里实现<BR>    Invoke的代码就会惊叹它实现的复杂了，因为你必须考虑各种参数类型的情况，所幸我们不<BR>    需要自己来做这件事，而且可能永远也没这样的机会。:-) <BR>(3) dispinterface接口、Dual接口以及Custom接口<BR>    这一小节放在这里似乎不太合适，因为这是在ATL编程时用到的术语。我在这里主要是想谈<BR>    一下自动化接口的好处及缺点，用这三个术语来解释可能会更好一些，而且以后迟早会遇上<BR>    它们，我将以一种通俗的方式来解释它们，可能并非那么精确，就好象用伪代码来描述算法<BR>    一样。-:)<BR>       所谓的自动化接口就是用IDispatch实现的接口。我们已经讲解过IDispatch的作用了，它的好<BR>    处就是脚本语言象VBScript、 JavaScript也能用COM组件了，从而基本上做到了与语言无关<BR>    它的缺点主要有两个，第一个就是速度慢效率低。这是显而易见的，通过虚函数表一下子就<BR>    可以调用函数了，而通过Invoke则等于中间转了道手续，尤其是需要把函数参数转换成一种<BR>    规范的格式才去调用函数，耽误了很多时间。所以一般若非是迫不得已我们都想用VTable的<BR>    方式调用函数以获得高效率。第二个缺点就是只能使用规定好的所谓的自动化数据类型。如<BR>    果不用IDispatch我们可以想用什么数据类型就用什么类型，VC会自动给我们生成相应的调度<BR>    代码。而用自动化接口就不行了，因为Invoke的实现代码是VC事先写好的，而它不能事先预<BR>    料到我们要用到的所有类型，它只能根据一些常用的数据类型来写它的处理代码，而且它也<BR>    要考虑不同语言之间的数据类型转换问题。所以VC自动化接口生成的调度代码只适用于它所<BR>    规定好的那些数据类型，当然这些数据类型已经足够丰富了，但不能满足自定义数据结构的<BR>    要求。你也可以自己写调度代码来处理你的自定义数据结构，但这并不是一件容易的事。<BR>    考虑到IDispatch的种种缺点(它还有一个缺点，就是使用麻烦，:-) )现在一般都推荐写双接<BR>    口组件，称为dual接口，实际上就是从IDispatch继承的接口。我们知道任何接口都必须从<BR>    IUnknown继承，IDispatch接口也不例外。那从IDispatch继承的接口实际上就等于有两个基<BR>　　类，一个是IUnknown，一个是IDispatch，所以它可以以两种方式来调用组件，可以通过<BR>　　IUnknown用虚函数表的方式调用接口方法，也可以通过IDispatch::Invoke自动化调度来调用<BR>　　这就有了很大的灵活性，这个组件既可以用于C++的环境也可以用于脚本语言中，同时满足<BR>    了各方面的需要。<BR>    相对比的，dispinterface是一种纯粹的自动化接口，可以简单的就把它看作是IDispatch接口<BR>    (虽然它实际上不是的)，这种接口就只能通过自动化的方式来调用，COM组件的事件一般都<BR>    用的是这种形式的接口。<BR>    Custom接口就是从IUnknown接口派生的类，显然它就只能用虚函数表的方式来调用接口了<BR>(4) COM组件有三种，进程内、本地、远程。对于后两者情况必须调度接口指针及函数参数。<BR>    COM是一个DLL，它有三种运行模式。它可以是进程内的，即和调用者在同一个进程内，也可<BR>    以和调用者在同一个机器上但在不同的进程内，还可以根本就和调用者在两台机器上。<BR>    这里有一个根本点需要牢记，就是COM组件它只是一个DLL，它自己是运行不起来的，必须<BR>    有一个进程象父亲般照顾它才行，即COM组件必须在一个进程内.那谁充当看护人的责任呢？<BR>    先说说调度的问题。调度是个复杂的问题，以我的知识还讲不清楚这个问题，我只是一般<BR>    性的谈谈几个最基本的概念。我们知道对于WIN32程序，每个进程都拥有4GB的虚拟地址空<BR>　　间，每个进程都有其各自的编址，同一个数据块在不同的进程里的编址很可能就是不一样<BR>　　的，所以存在着进程间的地址转换问题。这就是调度问题。对于本地和远程进程来说，DLL<BR>　　和客户程序在不同的编址空间，所以要传递接口指针到客户程序必须要经过调度。Windows<BR>   　已经提供了现成的调度函数，就不需要我们自己来做这个复杂的事情了。对远程组件来说<BR>　　函数的参数传递是另外一种调度。DCOM是以RPC为基础的，要在网络间传递数据必须遵守标<BR>　　准的网上数据传输协议，数据传递前要先打包，传递到目的地后要解包，这个过程就是调<BR>　　度，这个过程很复杂，不过Windows已经把一切都给我们做好了，一般情况下我们不需要自<BR>　　己来编写调度DLL。<BR>　　我们刚说过一个COM组件必须在一个进程内。对于本地模式的组件一般是以EXE的形式出现，<BR>　　所以它本身就已经是一个进程。对于远程DLL，我们必须找一个进程，这个进程必须包含了<BR>　　调度代码以实现基本的调度。这个进程就是dllhost.exe。这是COM默认的DLL代理。实际上在<BR>　　分布式应用中，我们应该用MTS来作为DLL代理，因为MTS有着很强大的功能，是专门的用于<BR>　　管理分布式DLL组件的工具。<BR>　　调度离我们很近又似乎很远，我们编程时很少关注到它，这也是COM的一个优点之一，既平<BR>　　台无关性，无论你是远程的、本地的还是进程内的，编程是一样的，一切细节都由COM自己<BR>　　处理好了，所以我们也不用深究这个问题，只要有个概念就可以了，当然如果你对调度有<BR>　　自己特殊的要求就需要深入了解调度的整个过程了，这里推荐一本《COM+技术内幕》，这<BR>　　绝对是一本讲调度的好书。<BR>(5) COM组件的核心是IDL。<BR>    我们希望软件是一块块拼装出来的，但不可能是没有规定的胡乱拼接，总是要遵守一定的<BR>    标准，各个模块之间如何才能亲密无间的合作，必须要事先共同制订好它们之间交互的规<BR>    范，这个规范就是接口。我们知道接口实际上都是纯虚类，它里面定义好了很多的纯虚函<BR>    数，等着某个组件去实现它，这个接口就是两个完全不相关的模块能够组合在一起的关键<BR>    试想一下如果我们是一个应用软件厂商，我们的软件中需要用到某个模块，我们没有时间<BR>    自己开发，所以我们想到市场上找一找看有没有这样的模块，我们怎么去找呢？也许我们<BR>    需要的这个模块在业界已经有了标准，已经有人制订好了标准的接口，有很多组件工具厂<BR>    商已经在自己的组件中实现了这个接口，那我们寻找的目标就是这些已经实现了接口的组<BR>    件，我们不关心组件从哪来，它有什么其它的功能，我们只关心它是否很好的实现了我们<BR>    制订好的接口。这种接口可能是业界的标准，也可能只是你和几个厂商之间内部制订的协<BR>    议，但总之它是一个标准，是你的软件和别人的模块能够组合在一起的基础，是COM组件<BR>    通信的标准。<BR>    COM具有语言无关性，它可以用任何语言编写，也可以在任何语言平台上被调用。但至今为<BR>    止我们一直是以C++的环境中谈COM，那它的语言无关性是怎么体现出来的呢？或者换句话<BR>    说，我们怎样才能以语言无关的方式来定义接口呢？前面我们是直接用纯虚类的方式定义<BR>    的，但显然是不行的，除了C++谁还认它呢？正是出于这种考虑，微软决定采用IDL来定义<BR>    接口。说白了，IDL实际上就是一种大家都认识的语言，用它来定义接口，不论放到哪个<BR>    语言平台上都认识它。我们可以想象一下理想的标准的组件模式，我们总是从IDL开始，<BR>    先用IDL制订好各个接口，然后把实现接口的任务分配不同的人，有的人可能善长用VC，<BR>    有的人可能善长用VB，这没关系，作为项目负责人我不关心这些，我只关心你最后把DLL<BR>    拿给我。这是一种多么好的开发模式，可以用任何语言来开发，也可以用任何语言也欣赏<BR>    你的开发成果。<BR>(6) COM组件的运行机制，即COM是怎么跑起来的。<BR>    这部分我们将构造一个创建COM组件的最小框架结构，然后看一看其内部处理流程是怎样的<BR>       IUnknown *pUnk=NULL;<BR>       IObject *pObject=NULL;<BR>       CoInitialize(NULL);<BR>       CoCreateInstance(CLSID_Object, CLSCTX_INPROC_SERVER, NULL, IID_IUnknown,<BR>(void**)&pUnk);<BR>       pUnk->QueryInterface(IID_IOjbect, (void**)&pObject);<BR>       pUnk->Release();<BR>       pObject->Func();<BR>       pObject->Release();<BR>       CoUninitialize();<BR>       这就是一个典型的创建COM组件的框架，不过我的兴趣在CoCreateInstance身上，让我们<BR>    来看看它内部做了一些什么事情。以下是它内部实现的一个伪代码:<BR>       CoCreateInstance(....)<BR>       {<BR>    .......<BR>    IClassFactory *pClassFactory=NULL;<BR>    CoGetClassObject(CLSID_Object, CLSCTX_INPROC_SERVER, NULL, IID_IClassFactory,<BR>        (void **)&pClassFactory);<BR>    pClassFactory->CreateInstance(NULL, IID_IUnknown, (void**)&pUnk);<BR>    pClassFactory->Release();<BR>    ........<BR>       }<BR>       这段话的意思就是先得到类厂对象，再通过类厂创建组件从而得到IUnknown指针。<BR>    继续深入一步，看看CoGetClassObject的内部伪码：<BR>      CoGetClassObject(.....)<BR>      {<BR>   <SPAN class=remark>//通过查注册表CLSID_Object，得知组件DLL的位置、文件名<BR>   //装入DLL库<BR>   //使用函数GetProcAddress(...)得到DLL库中函数DllGetClassObject的函数指针。<BR>   //调用DllGetClassObject </SPAN><BR>      }<BR>       DllGetClassObject是干什么的，它是用来获得类厂对象的。只有先得到类厂才能去创建组件.<BR>       下面是DllGetClassObject的伪码：<BR>       DllGetClassObject(...)<BR>       {<BR>    ......<BR>    CFactory* pFactory= new CFactory; <SPAN class=remark>//类厂对象</SPAN><BR>    pFactory->QueryInterface(IID_IClassFactory, (void**)&pClassFactory);<BR>    <SPAN class=remark>//查询IClassFactory指针</SPAN><BR>    pFactory->Release();<BR>    ......<BR>       }<BR>       CoGetClassObject的流程已经到此为止，现在返回CoCreateInstance，看看CreateInstance<BR>       的伪码：<BR>       CFactory::CreateInstance(.....)<BR>       {<BR>    ...........<BR>    CObject *pObject = new CObject; <SPAN class=remark>//组件对象</SPAN><BR>    pObject->QueryInterface(IID_IUnknown, (void**)&pUnk);<BR>    pObject->Release();<BR>    ...........<BR>       }<BR>   下图是从COM+技术内幕中COPY来的一个例图，从图中可以清楚的看到CoCreateInstance的整个<BR>流程。        <BR><BR>(7) 一个典型的自注册的COM DLL所必有的四个函数<BR>　　DllGetClassObject:用于获得类厂指针<BR>　　DllRegisterServer:注册一些必要的信息到注册表中<BR>DllUnregisterServer:卸载注册信息<BR>    DllCanUnloadNow:系统空闲时会调用这个函数，以确定是否可以卸载DLL<BR>    DLL还有一个函数是DllMain,这个函数在COM中并不要求一定要实现它，但是在VC生成的组<BR>　　件中自动都包含了它，它的作用主要是得到一个全局的实例对象。<BR>(8) 注册表在COM中的重要作用<BR>    首先要知道GUID的概念，COM中所有的类、接口、类型库都用GUID来唯一标识，GUID是一<BR>    个128位的字串，根据特制算法生成的GUID可以保证是全世界唯一的。<BR>    COM组件的创建，查询接口都是通过注册表进行的。有了注册表，应用程序就不需要知道<BR>    组件的DLL文件名、位置，只需要根据CLSID查就可以了。当版本升级的时侯，只要改一下<BR>    注册表信息就可以神不知鬼不觉的转到新版本的DLL。<BR><BR><BR>    本文是本人一时兴起的涂鸭之作，讲得并不是很全面，还有很多有用的体会没写出来，以后<BR>如果有时间有兴趣再写出来。希望这篇文章能给大家带来一点用处，那我一晚上的辛苦就没有白<BR><img src ="http://www.cnitblog.com/luoyi/aggbug/3609.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/luoyi/" target="_blank">罗毅</a> 2005-10-27 15:51 <a href="http://www.cnitblog.com/luoyi/articles/3609.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>COM技术初探（一）</title><link>http://www.cnitblog.com/luoyi/articles/3608.html</link><dc:creator>罗毅</dc:creator><author>罗毅</author><pubDate>Thu, 27 Oct 2005 07:34:00 GMT</pubDate><guid>http://www.cnitblog.com/luoyi/articles/3608.html</guid><wfw:comment>http://www.cnitblog.com/luoyi/comments/3608.html</wfw:comment><comments>http://www.cnitblog.com/luoyi/articles/3608.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/luoyi/comments/commentRss/3608.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/luoyi/services/trackbacks/3608.html</trackback:ping><description><![CDATA[<TABLE cellSpacing=0 cellPadding=0 width="100%" border=0>
<TBODY>
<TR bgColor=#a0d39b>
<TD height=30>&nbsp;<IMG height=13 src="file:///E:/书籍/Visual%20C++/COM组件/COM技术初探（一）.files/vckcom.gif" width=109></TD>
<TD vAlign=bottom align=right height=30><IMG height=27 src="file:///E:/书籍/Visual%20C++/COM组件/COM技术初探（一）.files/earch.gif" width=89></TD></TR>
<TR bgColor=#eeeeee>
<TD width="76%"><FONT class=small color=#333333>::</FONT><A href="http://www.vckbase.com/"><FONT color=black><SPAN class=small>首页</SPAN></FONT></A> &gt;&gt; <A href="http://www.vckbase.com/document"><FONT color=black><SPAN class=small>文档中心</SPAN></FONT></A> &gt;&gt; <A href="http://www.vckbase.com/document/journal"><SPAN class=small><FONT color=black>在线杂志</FONT></SPAN></A> &gt;&gt; <A href="http://www.vckbase.com/document/listdoc.asp?sclsid=1701"><SPAN class=small><FONT color=black>COM技术(COM/DCOM/COM+)</FONT></SPAN></A></TD>
<TD class=small align=right width="24%">[ <A href="http://www.vckbase.com/document/journal/redir.asp?journal=26"><SPAN class=small><FONT color=black>在线杂志 第26期</FONT></SPAN></A> ] </TD></TR></TBODY></TABLE>
<TABLE height=35 cellSpacing=0 cellPadding=0 width="100%" border=0>
<FORM name=form2 action=/SYS/script/find.asp method=post>
<TBODY>
<TR>
<TD>
<DIV align=right><INPUT class=rect maxLength=20 size=10 name=keyword> <SELECT class=rect name=gclsid> <OPTION value=100 selected>文档</OPTION> <OPTION value=200>代码</OPTION> <OPTION value=400>工具</OPTION></SELECT> <INPUT type=image height=15 width=21 src="file:///E:/书籍/Visual%20C++/COM组件/COM技术初探（一）.files/go.gif" align=absMiddle border=0 name=imageField> </DIV></TD></TR></FORM></TBODY></TABLE>
<TABLE cellSpacing=0 cellPadding=0 width="100%" border=0>
<TBODY>
<TR>
<TD background=COM技术初探（一）.files/dotline2.gif height=1></TD></TR></TBODY></TABLE>
<TABLE cellSpacing=0 cellPadding=0 width="100%" border=0>
<TBODY>
<TR>
<TD width=40 height=74></TD>
<TD vAlign=top width=* height=74><BR>[ <FONT color=#009900>原创文档</FONT> 本文适合中级读者 已阅读36043次 ]<BR><BR><BR>
<TABLE cellSpacing=0 cellPadding=0 width="100%" border=0>
<TBODY>
<TR>
<TD>
<P align=center><STRONG>COM技术初探（一）</STRONG><BR><BR>作者: <A href="mailto:emilioyee@163.com">venture</A></P>
<P><BR><A href="http://www.vckbase.com/code/downcode.asp?id=2167">下载源代码</A> <BR><BR><STRONG>目录</STRONG> 
<HR noShade SIZE=1>

<TABLE cellSpacing=0 cellPadding=20 width="80%" bgColor=#ffffff border=0>
<TBODY>
<TR>
<TD class=small><A href="http://www.vckbase.com/document/viewdoc/?id=915#一、COM是一个更好的C++"><STRONG>一. COM是一个更好的 C++</STRONG></A><BR>&nbsp;&nbsp; <A href="http://www.vckbase.com/document/viewdoc/?id=915#1、COM 是什么">1. COM 是什么</A><BR>&nbsp;&nbsp; <A href="http://www.vckbase.com/document/viewdoc/?id=915#2、从 C++ 到 DLL 再到 COM">2. 从 C++ 到 DLL 再到 COM</A><BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<A href="http://www.vckbase.com/document/viewdoc/?id=915#2.1 C++">2.1 C++</A><BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<A href="http://www.vckbase.com/document/viewdoc/?id=915#2.2 DLL">2.2 DLL</A><BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<A href="http://www.vckbase.com/document/viewdoc/?id=915#2.3 COM">2.3 COM</A><BR><BR><A href="http://www.vckbase.com/document/viewdoc/?id=915#二、COM基础"><STRONG>二. COM基础</STRONG></A><BR>&nbsp;&nbsp; <A href="http://www.vckbase.com/document/viewdoc/?id=915#1、 COM基本知识">1. COM基本知识</A><BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<A href="http://www.vckbase.com/document/viewdoc/?id=915#1.1 返回值HRESULT">1.1 返回值HRESULT</A><BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<A href="http://www.vckbase.com/document/viewdoc/?id=915#1.2 初识 IDL">1.2 初识idl</A><BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<A href="http://www.vckbase.com/document/viewdoc/?id=915#1.3 IUnkown接口">1.3 IUnkown接口</A><BR>&nbsp;&nbsp; <A href="http://www.vckbase.com/document/viewdoc/?id=915#2、一个比较简单的COM">2. 一个比较简单的COM</A><BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<A href="http://www.vckbase.com/document/viewdoc/?id=915#2.1 interface.h 文件">2.1 interface.h文件</A><BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<A href="http://www.vckbase.com/document/viewdoc/?id=915#2.2 math.h文件">2.2 math.h文件</A><BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<A href="http://www.vckbase.com/document/viewdoc/?id=915#2.3 math.cpp文件">2.3 math.cpp文件</A><BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<A href="http://www.vckbase.com/document/viewdoc/?id=915#2.4 simple.cpp文件">2.4 simple.cpp文件</A><BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<A href="http://www.vckbase.com/document/viewdoc/?id=915#2.5 Math组件的二进制结构图">2.5 Math组件的二进制结构图</A><BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<A href="http://www.vckbase.com/document/viewdoc/?id=915#2.6 小结">2.6 小结</A><BR><BR><STRONG>三. 纯手工创建一个COM组件</STRONG><BR>&nbsp;&nbsp; 1. 从建工程到实现注册<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;1.1 创建一个类型为win32 dll工程<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;1.2 定义接口文件<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;1.3 增加注册功能<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 1.3.1 增加一个MathCOM.def文件<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 1.3.2 DllRegisterServer()和DllUnregisterServer()<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;1.4 MathCOM.cpp文件<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;1.5 小结<BR>&nbsp;&nbsp; 2. 实现ISmipleMath,IAdvancedMath接口和DllGetClassObject()<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;2.1 实现ISmipleMath和IAdvancedMath接口<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;2.2 COM组件调入大致过程<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;2.3 DllGetClassObject()实现<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;2.4 客户端<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;2.5 小结<BR>&nbsp;&nbsp; 3. 类厂<BR><BR><STRONG>附录</STRONG>&nbsp;&nbsp; <BR>A 我对dll的一点认识<BR>一. 没有lib的dll<BR>&nbsp;&nbsp; 1.1 建一个没有lib的dll<BR>&nbsp;&nbsp; 1.2 调试没有lib的dll<BR>二. 带有lib的dll<BR>&nbsp;&nbsp; 2.1 创建一个带有lib的dll<BR>&nbsp;&nbsp; 2.2 调试带有引用但没有头文件的dll<BR>三. 带有头文件的dll<BR>&nbsp;&nbsp; 3.1 创建一个带有引出信息头文件的dll<BR>&nbsp;&nbsp; 3.2 调试带有头文件的dll<BR>四. 小结 </TD></TR></TBODY></TABLE>
<HR noShade SIZE=1>

<P><BR><BR><IMG src="file:///E:/书籍/Visual%20C++/COM组件/COM技术初探（一）.files/paragraph.gif"> <A name=一、COM是一个更好的C++>一、COM是一个更好的C++</A><BR><BR><A name="1、COM 是什么">1、COM 是什么</A><BR><BR><A title="Don Box 是公认 COM 界的老大，拥有''鬼才''之称。在1993年，他与人共同创办了 DevelopMentor，并在 DevelopMentor 开展他的COM教育工作。他曾经是 MSJ ( Microsoft Systems Jouranl ) 的特约编辑，负责《House of Com》专栏。现就职于Microsoft。他是Microsoft XML系统的设计师。" href="http://www.vckbase.com/document/viewdoc/?id=915#1" name=DonBox>Don Box</A> 说"COM IS LOVE"。COM 的全称是 Component Object Model 组件对象模型。 <BR><BR><A name="2、从 C++ 到 DLL 再到 COM">2、从 C++ 到 DLL 再到 COM</A><BR><BR><A name="2.1 C++">2.1 C++</A><BR><BR>如某一软件厂商发布一个类库(CMath四则运算)，此时类库的可执行代码将成为客户应用中不可分割的一部分。假设此类库的所产生的机器码在目标可执行文件中占有4MB的空间。当三个应用程序都使用CMath库时，那么每个可执行文件都包含4MB的类库代码(见图1.1)。当三个应用程序共同运行时，他们将会占用12MB的虚拟内存。问题还远不于此。一旦类库厂商发现CMath类库有一个缺陷后，发布一个新的类库，此时需要要求所有运用此类库的应用程序。此外别无他法了。<BR><BR><IMG src="file:///E:/书籍/Visual%20C++/COM组件/COM技术初探（一）.files/comimg1.gif" border=0><BR><IMG src="file:///E:/书籍/Visual%20C++/COM组件/COM技术初探（一）.files/comimg2.gif" border=0><BR><IMG src="file:///E:/书籍/Visual%20C++/COM组件/COM技术初探（一）.files/comimg3.gif" border=0><BR>图1.1 CMath 的三个客户<BR><BR><A name="2.2 DLL">2.2 DLL</A><BR><BR>解决上面问题的一个技术是将CMath类做成动态链接库(DLL ，Dynamic Link Library)的<A title="有关dll与lib文件的关系请参阅附录A  " href="http://www.vckbase.com/document/viewdoc/?id=915#形式封装" name=形式封装>形式封装</A>起来 。<BR>在使用这项技术的时候，CMath的所有方法都将被加到 CMath dll 的引出表(export list)中，而且链接器将会产生一个引入库(import library)。这个库暴露了CMath的方法<A title=如果想了解有关引出表，引入库的知识，请参阅http://msdn.microsoft.com/msdnmag/issues/02/02/PE/default.aspx href="http://www.vckbase.com/document/viewdoc/?id=915#成员的符号" name=成员的符号>成员的符号</A> 。当客户链接引入库时，有一些存根会被引入到可执行文件中，它在运行时通知装载器动态装载 <A title=如果想了解dll如何被装载，请参阅http://msdn.microsoft.com/msdnmag/issues/02/03/Loader/default.aspx href="http://www.vckbase.com/document/viewdoc/?id=915#CMath Dll" name="CMath Dll">CMath Dll</A>。<BR>当 CMath 位于dll中时，他的运行模型见图1.2<BR><BR><IMG src="file:///E:/书籍/Visual%20C++/COM组件/COM技术初探（一）.files/comimg4.gif" border=0><BR>图1.2 CMath引入库<BR><BR><A name="2.3 COM">2.3 COM</A><BR><BR>"简单地把C++类定义从dll中引出来"这种方案并不能提供合理的二进制组件结构。因为C++类那既是接口也是实现。这里需要把接口从实现中分离出来才能提供二进制组件结构。此时需要有二个C++类，一个作为接口类另一个作为实现类。让我们开始COM之旅吧。<BR><BR><IMG src="file:///E:/书籍/Visual%20C++/COM组件/COM技术初探（一）.files/paragraph.gif"> <A name=二、COM基础>二、COM基础</A><BR><BR><A name="1、 COM基本知识">1、 COM基本知识</A><BR><BR><A name="1.1 返回值HRESULT">1.1 返回值HRESULT</A><BR><BR>COM要求所有的方法都会返回一个HRESULT类型的错误号。HRESULT 其实就一个类型定义： </P><PRE>    typedef LONG HRESULT;</PRE>有关HRESULT的定义见 winerror.h 文件 <PRE>//  Values are 32 bit values layed out as follows:
//
//  3 3 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1
//  1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0
//  +-+----+-------------------------+---------------------------------+
//  |S| Res|     Facility            |     Code                        |
//  +-+----+-------------------------+---------------------------------+
//
//  where
//
//      S - is the severity code
//
//          0 - Success
//          1 - Error
//
//      Res- is a reserved bit
//
//      Facility - is the facility code
//
//      Code - is the facility''s status code
</PRE>我们一般下面的宏来判断方法是否成功： <PRE>#define SUCCEEDED(hr)(long(hr)&gt;=0)
#define FAILED(hr)(long(hr)&lt;0)
</PRE><A name="1.2 初识 IDL">1.2 初识 IDL</A><BR><BR>每个标准的COM组件都需要一个接口定义文件，文件的扩展名为<A title="IDL 的全称是Interface Definition Language 接口定义语言。如果想了解更多的IDL语法请参阅 中国电力出版社《IDL 精髓》。" href="http://www.vckbase.com/document/viewdoc/?id=915#IDL" name=IDL>IDL</A>。让我们看IUnknow接口的定义文件是怎样的。<PRE>[
  local,
  object,
  uuid(00000000-0000-0000-C000-000000000046),
  pointer_default(unique)
]

interface IUnknown
{
    typedef [unique] IUnknown *LPUNKNOWN;

cpp_quote("//////////////////////////////////////////////////////////////////")
cpp_quote("// IID_IUnknown and all other system IIDs are provided in UUID.LIB")
cpp_quote("// Link that library in with your proxies, clients and servers")
cpp_quote("//////////////////////////////////////////////////////////////////")

    HRESULT QueryInterface(
        [in] REFIID riid,
        [out, iid_is(riid)] void **ppvObject);
    ULONG AddRef();
    ULONG Release();
}

[local]属性禁止产生网络代码。
[object]属性是表明定义的是一个COM接口，而不是DEC风格的接口。
[uuid]属性给接口一个GUID。
[unique]属性表明null(空)指针为一个合法的参数值。
[pointer_defaul]属性所有的<A title="COM中的方法中任何有名字的指针参数都被称为顶级指针(top-level pointer).在顶级指针解析得到的数据中，其中隐含的任何次级指针称为内嵌指针(embedded pointer)。" href="http://www.vckbase.com/document/viewdoc/?id=915#内嵌指针" name=内嵌指针>内嵌指针</A>指定一个默认指针属性
typedef [unique] IUnknown *LPUNKNOWN;这是一个类型定义
cpp_quote这个比较有趣，这是一个在idl文件写注解的方法。这些注解将保存到***.h和***_i.c文件中
[in]表示这个参数是入参
[out]表示这个参数是出参
[iid_is(riid)]表示这个参数需要前一个的<A title=" riid 是GUID ''IID-''Reference IID" href="http://www.vckbase.com/document/viewdoc/?id=915#riid" name=riid>riid </A>参数。</PRE>注意：所有具有out属性的参数都需要是指针类型。<BR><BR><A name="1.3 IUnkown接口">1.3 IUnkown接口</A><BR><BR>在整个例子除了IUnkown这个东西，其他应该不会感到陌生吧！COM要求(最基本的要求)所有的接口都需要从IUnknown接口直接或间接继承，所以IUnknown接口有"万恶之源"之称。<BR>IUnkown接口定义了三个方法。 <PRE>HRESULT QueryInterface([in] REFIID riid,[out] void **ppv);
ULONG AddRef();
ULONG Release();    </PRE>其中 <A title=AddRef()方法绝大多数情况下是不需要由客户端来调用(至少我到现在还没有碰到过) href="http://www.vckbase.com/document/viewdoc/?id=915#AddReft()" name=AddReft()>AddReft()</A> 和Release()负责对象引用计数用的，而 QueryInterface()方法是用于查询所实现接口用的。每当COM组件被引用一次就应调用一次AddRef()方法。而当客户端在释放COM组件的某个接口时就需要调用Release()方法。<BR>这里所讲的请在下面的例子仔细体会。<BR><BR><A name=2、一个比较简单的COM>2、一个比较简单的COM</A><BR><BR>此例子共有四个文件组成：<BR>　 
<TABLE id=AutoNumber1 style="BORDER-COLLAPSE: collapse" borderColor=#111111 cellSpacing=0 cellPadding=0 width="73%" border=1>
<TBODY>
<TR>
<TD align=middle width="50%"><B>文件名</B></TD>
<TD align=middle width="50%"><B>说明</B></TD></TR>
<TR>
<TD width="50%">Interface.h</TD>
<TD width="50%">接口类定义文件</TD></TR>
<TR>
<TD width="50%">Math.h和Math.cpp</TD>
<TD width="50%">实现类文件</TD></TR>
<TR>
<TD width="50%">Simple.cpp 主函数文件</TD>
<TD width="50%">这里用来当作COM的客户端</TD></TR></TBODY></TABLE>
<P><A name="2.1 interface.h 文件">2.1 interface.h 文件</A><BR></P><PRE>#ifndef INTERFACE_H
#define INTERFACE_H
#include &lt;unknwn.h&gt;

//{7C8027EA-A4ED-467c-B17E-1B51CE74AF57}
static const GUID IID_ISimpleMath = 
{ 0x7c8027ea, 0xa4ed, 0x467c, { 0xb1, 0x7e, 0x1b, 0x51, 0xce, 0x74, 0xaf, 0x57 } };

//{CA3B37EA-E44A-49b8-9729-6E9222CAE84F}
static const GUID IID_IAdvancedMath = 
{ 0xca3b37ea, 0xe44a, 0x49b8, { 0x97, 0x29, 0x6e, 0x92, 0x22, 0xca, 0xe8, 0x4f } };

interface ISimpleMath : public IUnknown
{
public:
	virtual int Add(int nOp1, int nOp2) = 0;		
	virtual int Subtract(int nOp1, int nOp2) = 0;
	virtual int Multiply(int nOp1, int nOp2) = 0;
	virtual int Divide(int nOp1, int nOp2) = 0;
};

interface IAdvancedMath : public IUnknown
{
public:
	virtual int Factorial(int nOp1) = 0;
	virtual int Fabonacci(int nOp1) = 0;
};
#endif    </PRE>此文件首先 #include &lt;unknwn.h&gt; 将 <A title=参见1.5IUnkown接口 href="http://www.vckbase.com/document/viewdoc/?id=915#IUnknown 接口" name="IUnknown 接口">IUnknown 接口</A>定义文件包括进来。<BR>接下来定义了两个接口，GUID（Globally Unique Identifier全局唯一标识符）它能保证时间及空间上的唯一。<BR>ISmipleMath接口里定义了四个方法，而IAdvancedMath接口里定义了二个方法。这些方法都是虚函数，而整个 ISmipleMath 与 IAdvancedMath 抽象类就作为二进制的接口。<BR><BR><A name="2.2 math.h文件">2.2 math.h文件</A> <PRE>#include "interface.h"

class CMath : public ISimpleMath,
			  public IAdvancedMath
{
private:
	ULONG m_cRef;

private:
	int calcFactorial(int nOp);
	int calcFabonacci(int nOp);

public:
	//IUnknown Method
	STDMETHOD(QueryInterface)(REFIID riid, void **ppv);
	STDMETHOD_(ULONG, AddRef)();
	STDMETHOD_(ULONG, Release)();

	//	ISimpleMath Method
	int Add(int nOp1, int nOp2);
	int Subtract(int nOp1, int nOp2);
	int Multiply(int nOp1, int nOp2);
	int Divide(int nOp1, int nOp2);

	//	IAdvancedMath Method
	int Factorial(int nOp);
	int Fabonacci(int nOp);
};    </PRE>此类为实现类，他实现了ISmipleMath和IAdvancedMath两个接口类(当然也可以只实现一个接口类)。<BR>请注意：m_cRef 是用来对象计数用的。当 m_cRef 为0组件对象应该自动删除。<BR><BR><A name="2.3 math.cpp文件">2.3 math.cpp文件 </A><PRE>#include "interface.h"
#include "math.h"

STDMETHODIMP CMath::QueryInterface(REFIID riid, void **ppv)
{//	这里这是实现dynamic_cast的功能，但由于dynamic_cast与编译器相关。
	if(riid == IID_ISimpleMath)
		*ppv = static_cast<ISIMPLEMATH *>(this);
	else if(riid == IID_IAdvancedMath)
		*ppv = static_cast<IADVANCEDMATH *>(this);
	else if(riid == IID_IUnknown)
		*ppv = static_cast<ISIMPLEMATH *>(this);
	else {
		*ppv = 0;
		return E_NOINTERFACE;
	}

	reinterpret_cast<IUNKNOWN *>(*ppv)-&gt;AddRef();	//这里要这样是因为引用计数是针对组件的
	return S_OK;
}

STDMETHODIMP_(ULONG) CMath::AddRef()
{
	return ++m_cRef;
}

STDMETHODIMP_(ULONG) CMath::Release()
{
	ULONG res = --m_cRef;	// 使用临时变量把修改后的引用计数值缓存起来
	if(res == 0)		// 因为在对象已经销毁后再引用这个对象的数据将是非法的
		delete this;
	return res;
}

int CMath::Add(int nOp1, int nOp2)
{
	return nOp1+nOp2;
}

int CMath::Subtract(int nOp1, int nOp2)
{
	return nOp1 - nOp2;
}

int CMath::Multiply(int nOp1, int nOp2)
{
	return nOp1 * nOp2;
}

int CMath::Divide(int nOp1, int nOp2)
{
	return nOp1 / nOp2;
}

int CMath::calcFactorial(int nOp)
{
	if(nOp &lt;= 1)
		return 1;

	return nOp * calcFactorial(nOp - 1);
}

int CMath::Factorial(int nOp)
{
	return calcFactorial(nOp);
}

int CMath::calcFabonacci(int nOp)
{
	if(nOp &lt;= 1)
		return 1;

	return calcFabonacci(nOp - 1) + calcFabonacci(nOp - 2);
}

int CMath::Fabonacci(int nOp)
{
	return calcFabonacci(nOp);
}
 CMath::CMath()
{
	m_cRef=0;
}   </PRE>此文件是CMath类定义文件。<BR><BR><A name="2.4 simple.cpp文件">2.4 simple.cpp文件</A> <PRE>#include "math.h"
#include &lt;iostream&gt;

using namespace std;

int main(int argc, char* argv[])
{
	ISimpleMath *pSimpleMath = NULL;//声明接口指针
	IAdvancedMath *pAdvMath = NULL;

	<ISIMPLEMATH *><IADVANCEDMATH *><ISIMPLEMATH *><IUNKNOWN *>//创建对象实例，我们暂时这样创建对象实例，COM有创建对象实例的机制
	CMath *pMath = new CMath;	

	<ISIMPLEMATH *><IADVANCEDMATH *><ISIMPLEMATH *><IUNKNOWN *>//查询对象实现的接口ISimpleMath
	pMath-&gt;QueryInterface(IID_ISimpleMath, (void **)&amp;pSimpleMath);	
	if(pSimpleMath)
		cout &lt;&lt; "10 + 4 = " &lt;&lt; pSimpleMath-&gt;Add(10, 4) &lt;&lt; endl;

	<ISIMPLEMATH *><IADVANCEDMATH *><ISIMPLEMATH *><IUNKNOWN *>//查询对象实现的接口IAdvancedMath
	pSimpleMath-&gt;QueryInterface(IID_IAdvancedMath, (void **)&amp;pAdvMath);
	if(pAdvMath)
		cout &lt;&lt; "10 Fabonacci is " &lt;&lt; pAdvMath-&gt;Fabonacci(10) &lt;&lt; endl;	

	pAdvMath-&gt;Release();
	pSimpleMath-&gt;Release();
	return 0;
}</PRE>此文件相当于客户端的代码，首先创建一个CMath对象,再根据此对象去查询所需要的接口，如果正确得到所需接口指针，再调用接口的方法，最后再将接口的释放掉。<BR><BR><A name="2.5 Math组件的二进制结构图">2.5 Math组件的二进制结构图</A><BR><BR><BR><IMG src="file:///E:/书籍/Visual%20C++/COM组件/COM技术初探（一）.files/comimg6.jpg" border=0><BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 图1.3 Math组件二进制结构图<BR><BR><A name="2.6 小结">2.6 小结</A><BR><BR>此例子从严格意义上来并不是真正的COM组件(他不是dll)，但他已符合COM的最小要求(实现IUnknown接口)。接下来我们来做一COM dll(但还不用ATL)。<BR><BR>（待续）<BR></TD></TR></TBODY></TABLE></TD></TR></TBODY></TABLE><BR>
<DIV align=center><BR><BR>
<TABLE class=small height=18 cellSpacing=0 cellPadding=0 width="98%" border=0>
<TBODY>
<TR vAlign=center>
<TD width="47%" bgColor=#a0d39b><IMG height=10 src="file:///E:/书籍/Visual%20C++/COM组件/COM技术初探（一）.files/toplogo.gif" width=10>最新评论 <A href="http://www.vckbase.com/SYS/script/viewcomment.asp?gclsid=100&amp;itemid=915" target=_blank><SPAN class=small>[发表评论]</SPAN></A> <A href="http://www.vckbase.com/support/contribute.html" target=_blank><SPAN class=small>[文章投稿]</SPAN></A></TD>
<TD align=right width="53%" bgColor=#a0d39b><IMG height=9 src="" width=9> <A href="http://www.vckbase.com/SYS/script/viewcomment.asp?gclsid=100&amp;itemid=915" target=_blank><SPAN class=small>查看所有评论</SPAN></A> <IMG height=9 src="" width=9> <A href="http://www.vckbase.com/SYS/script/writemail.asp?gclsid=100&amp;itemid=915&amp;title=COM%bc%bc%ca%f5%b3%f5%cc%bd%a3%a8%d2%bb%a3%a9" target=_blank><SPAN class=small>推荐给好友</SPAN></A> <IMG height=9 src="" width=9> <A href="javascript:window.print();"><SPAN class=small>打印</SPAN></A></TD></TR></TBODY></TABLE>
<TABLE class=small cellSpacing=1 cellPadding=0 width="98%" bgColor=#ffffff border=0>
<TBODY>
<TR>
<TD bgColor=#ffffff><BR><IMG height=11 src="file:///E:/书籍/Visual%20C++/COM组件/COM技术初探（一）.files/doc2.gif" width=11 align=absMiddle> 没看懂。。。。。 ( 09910628 发表于 2005-6-29 18:45:00)<BR>&nbsp;<BR><IMG height=11 src="file:///E:/书籍/Visual%20C++/COM组件/COM技术初探（一）.files/doc2.gif" width=11 align=absMiddle> 《COM本质论》 ( Vcleaner 发表于 2005-4-28 13:50:00)<BR>&nbsp;<BR><IMG height=11 src="file:///E:/书籍/Visual%20C++/COM组件/COM技术初探（一）.files/doc2.gif" width=11 align=absMiddle> 请问<BR>//{7C8027EA-A4ED-467c-B17E-1B51CE74AF57}<BR>static&nbsp;const&nbsp;GUID&nbsp;IID_ISimpleMath&nbsp;=&nbsp;<BR>{&nbsp;0x7c8027ea,&nbsp;0xa4ed,&nbsp;0x467c,&nbsp;{&nbsp;0xb1,&nbsp;0x7e,&nbsp;0x1b,&nbsp;0x51,&nbsp;0xce,&nbsp;0x74,&nbsp;0xaf,&nbsp;0x57&nbsp;}&nbsp;};<BR><BR>//{CA3B37EA-E44A-49b8-9729-6E9222CAE84F}<BR>static&nbsp;const&nbsp;GUID&nbsp;IID_IAdvancedMath&nbsp;=&nbsp;<BR>{&nbsp;0xca3b37ea,&nbsp;0xe44a,&nbsp;0x49b8,&nbsp;{&nbsp;0x97,&nbsp;0x29,&nbsp;0x6e,&nbsp;0x92,&nbsp;0x22,&nbsp;0xca,&nbsp;0xe8,&nbsp;0x4f&nbsp;}&nbsp;};<BR>这个所谓的时间上和空间上的唯一性的GUID是怎么定出来的？<BR>( wxhhsh 发表于 2005-1-25 17:06:00)<BR>&nbsp;<BR><IMG height=11 src="file:///E:/书籍/Visual%20C++/COM组件/COM技术初探（一）.files/doc2.gif" width=11 align=absMiddle> reinterpret_cast(*ppv)-&gt;AddRef();&nbsp;<BR>D:\lab\COM\VCK\LessonI\math.cpp(17)&nbsp;:&nbsp;error&nbsp;C2059:&nbsp;syntax&nbsp;error&nbsp;:&nbsp;'('<BR><BR>reinterpret_cast&lt;CMath*&gt;(*ppv)-&gt;AddRef();&nbsp;就OK了<BR><BR>( rikky1980 发表于 2004-10-27 9:32:00)<BR>&nbsp;<BR><IMG height=11 src="file:///E:/书籍/Visual%20C++/COM组件/COM技术初探（一）.files/doc2.gif" width=11 align=absMiddle> 最后那个图错了吧<BR>我觉的IAdvancedMath也应该继承IUknown ( qiangv 发表于 2004-10-14 11:33:00)<BR>&nbsp;<BR><IMG height=11 src="file:///E:/书籍/Visual%20C++/COM组件/COM技术初探（一）.files/doc2.gif" width=11 align=absMiddle> 肯到骨头了<BR>( boywys 发表于 2004-9-22 10:25:00)<BR>&nbsp;<BR><IMG height=11 src="file:///E:/书籍/Visual%20C++/COM组件/COM技术初探（一）.files/doc2.gif" width=11 align=absMiddle> //写的非常好，不过有一个函数没有写好：<BR>STDMETHODIMP&nbsp;CMath::QueryInterface(REFIID&nbsp;riid,&nbsp;void&nbsp;**ppv)<BR>{<BR>// 这里这是实现dynamic_cast的功能，但由于dynamic_cast与编译器相关。<BR>if(riid&nbsp;==&nbsp;IID_ISimpleMath)<BR>*ppv&nbsp;=&nbsp;static_cast&lt;ISimpleMath*&gt;(this);<BR>else&nbsp;if(riid&nbsp;==&nbsp;IID_IAdvancedMath)<BR>*ppv&nbsp;=&nbsp;static_cast&lt;IAdvancedMath*&gt;(this);<BR>else&nbsp;if(riid&nbsp;==&nbsp;IID_IUnknown)<BR>*ppv&nbsp;=&nbsp;static_cast&lt;CMath*&gt;(this);<BR>else&nbsp;{<BR>*ppv&nbsp;=&nbsp;0;<BR>return&nbsp;E_NOINTERFACE;<BR>}<BR>&nbsp;&nbsp;//这样就不会有问题了。 ( diaoxueli 发表于 2004-3-5 13:35:00)<BR>&nbsp;<BR><IMG height=11 src="file:///E:/书籍/Visual%20C++/COM组件/COM技术初探（一）.files/doc2.gif" width=11 align=absMiddle> 怎么运行的时候不对啊，编译有问题，后来改了，但是现在运行结果不对，在<BR>cout&nbsp;&lt;&lt;&nbsp;"10&nbsp;Fabonacci&nbsp;is&nbsp;"&nbsp;&lt;&lt;&nbsp;pAdvMath-&gt;Fabonacci(10)&nbsp;&lt;&lt;&nbsp;endl; <BR>这句的时候出错，是不是程序有问题？ ( allensky 发表于 2003-11-16 16:54:00)<BR>&nbsp;<BR><IMG height=11 src="file:///E:/书籍/Visual%20C++/COM组件/COM技术初探（一）.files/doc2.gif" width=11 align=absMiddle> 最近刚刚接触COM，都不是很懂，看了这篇文章，总算是有点明白了，谢谢 ( allensky 发表于 2003-11-15 23:50:00)<BR>&nbsp;<BR><IMG height=11 src="file:///E:/书籍/Visual%20C++/COM组件/COM技术初探（一）.files/doc2.gif" width=11 align=absMiddle> Thanks!希望以后多写一些&nbsp;这类的文章，写的越简单越好！ ( Jerry_知识库 发表于 2003-11-7 16:20:00)<BR>&nbsp;<BR>.......................................................<BR><A href="http://www.vckbase.com/SYS/script/viewcomment.asp?gclsid=100&amp;itemid=915" target=_blank><SPAN class=small>More...</SPAN></A> </TD></TR></TBODY></TABLE></DIV><BR>
<DIV align=right><BR><SPAN class=small>版权所有 © 2004 VC知识库&nbsp; <BR><BR></SPAN></DIV><img src ="http://www.cnitblog.com/luoyi/aggbug/3608.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/luoyi/" target="_blank">罗毅</a> 2005-10-27 15:34 <a href="http://www.cnitblog.com/luoyi/articles/3608.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>VC调试入门</title><link>http://www.cnitblog.com/luoyi/articles/3607.html</link><dc:creator>罗毅</dc:creator><author>罗毅</author><pubDate>Thu, 27 Oct 2005 07:30:00 GMT</pubDate><guid>http://www.cnitblog.com/luoyi/articles/3607.html</guid><wfw:comment>http://www.cnitblog.com/luoyi/comments/3607.html</wfw:comment><comments>http://www.cnitblog.com/luoyi/articles/3607.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/luoyi/comments/commentRss/3607.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/luoyi/services/trackbacks/3607.html</trackback:ping><description><![CDATA[<P align=center><B>VC调试入门<BR></B><BR><BR>作者：<A href="mailto:arongustc@hotmail.com">阿荣</A></P>
<P><BR><IMG src="file:///E:/书籍/image/paragraph.gif"> 概述<BR>调试是一个程序员最基本的技能，其重要性甚至超过学习一门语言。不会调试的程序员就意味着他即使会一门语言，却不能编制出任何好的软件。<BR>这里我简要的根据自己的经验列出调试中比较常用的技巧，希望对大家有用。<BR>本文约定，在选择菜单时，通过/表示分级菜单，例如File/Open表示顶级菜单File的子菜单Open。<BR>&nbsp;<BR><IMG src="file:///E:/书籍/image/paragraph.gif"> 设置<BR>为了调试一个程序，首先必须使程序中包含调试信息。一般情况下，一个从AppWizard创建的工程中包含的Debug Configuration自动包含调试信息，但是是不是Debug版本并不是程序包含调试信息的决定因素，程序设计者可以在任意的Configuration中增加调试信息，包括Release版本。<BR>为了增加调试信息，可以按照下述步骤进行： 
<UL>
<LI>打开Project settings对话框（可以通过快捷键ALT+F7打开，也可以通过IDE菜单Project/Settings打开) 
<LI>选择C/C++页，Category中选择general ，则出现一个Debug Info下拉列表框，可供选择的调试信息 方式包括： <BR>　 
<TABLE id=AutoNumber1 style="BORDER-COLLAPSE: collapse" borderColor=#111111 cellSpacing=0 cellPadding=0 width=614 border=1>
<TBODY>
<TR>
<TD align=middle width=56><B>命令行</B></TD>
<TD align=middle width=160><B>Project settings</B></TD>
<TD align=middle width=394><B>说明</B></TD></TR>
<TR>
<TD width=56>无</TD>
<TD width=160>None</TD>
<TD width=394>没有调试信息</TD></TR>
<TR>
<TD width=56>/Zd</TD>
<TD width=160>Line Numbers Only</TD>
<TD width=394>目标文件或者可执行文件中只包含全局和导出符号以及代码行信息，不包含符号调试信息</TD></TR>
<TR>
<TD width=56>/Z7</TD>
<TD width=160>C 7.0- Compatible</TD>
<TD width=394>目标文件或者可执行文件中包含行号和所有符号调试信息，包括变量名及类型，函数及原型等</TD></TR>
<TR>
<TD width=56>/Zi</TD>
<TD width=160>Program Database</TD>
<TD width=394>创建一个程序库(PDB)，包括类型信息和符号调试信息。</TD></TR>
<TR>
<TD width=56>/ZI</TD>
<TD width=160>Program Database for Edit and Continue</TD>
<TD width=394>除了前面/Zi的功能外，这个选项允许对代码进行调试过程中的修改和继续执行。这个选项同时使#pragma设置的优化功能无效</TD></TR></TBODY></TABLE><BR>
<LI>选择Link页，选中复选框"Generate Debug Info"，这个选项将使连接器把调试信息写进可执行文件和DLL 
<LI>如果C/C++页中设置了Program Database以上的选项，则Link incrementally可以选择。选中这个选项，将使程序可以在上一次编译的基础上被编译（即增量编译），而不必每次都从头开始编译。 </LI></UL><IMG src="file:///E:/书籍/image/paragraph.gif"> 断点<BR>断点是调试器设置的一个代码位置。当程序运行到断点时，程序中断执行，回到调试器。断点是 最常用的技巧。调试时，只有设置了断点并使程序回到调试器，才能对程序进行在线调试。<BR><BR>设置断点：可以通过下述方法设置一个断点。首先把光标移动到需要设置断点的代码行上，然后 
<UL>
<LI>按F9快捷键 
<LI>弹出Breakpoints对话框，方法是按快捷键CTRL+B或ALT+F9，或者通过菜单Edit/Breakpoints打开。打开后点击Break at编辑框的右侧的箭头，选择 合适的位置信息。一般情况下，直接选择line xxx就足够了，如果想设置不是当前位置的断点，可以选择Advanced，然后填写函数、行号和可执行文件信息。 </LI></UL>去掉断点：把光标移动到给定断点所在的行，再次按F9就可以取消断点。同前面所述，打开Breakpoints对话框后，也可以按照界面提示去掉断点。<BR><BR>条件断点：可以为断点设置一个条件，这样的断点称为条件断点。对于新加的断点，可以单击Conditions按钮，为断点设置一个表达式。当这个表达式发生改变时，程序就 被中断。底下设置包括“观察数组或者结构的元素个数”，似乎可以设置一个指针所指向的内存区的大小，但是我设置一个比较的值但是改动 范围之外的内存区似乎也导致断点起效。最后一个设置可以让程序先执行多少次然后才到达断点。<BR><BR>数据断点：数据断点只能在Breakpoints对话框中设置。选择“Data”页，就显示了设置数据断点的对话框。在编辑框中输入一个表达式，当这个 表达式的值发生变化时，数据断点就到达。一般情况下，这个表达式应该由运算符和全局变量构成，例如：在编辑框中输入 g_bFlag这个全局变量的名字，那么当程序中有g_bFlag= !g_bFlag时，程序就将停在这个语句处。<BR><BR>消息断点：VC也支持对Windows消息进行截获。他有两种方式进行截获：窗口消息处理函数和特定消息中断。<BR>在Breakpoints对话框中选择Messages页，就可以设置消息断点。如果在上面那个对话框中写入消息处理函数的名字，那么 每次消息被这个函数处理，断点就到达（我觉得如果采用普通断点在这个函数中截获，效果应该一样）。如果在底下的下拉 列表框选择一个消息，则每次这种消息到达，程序就中断。<BR><BR><IMG src="file:///E:/书籍/image/paragraph.gif"> 值<BR><B>Watch</B><BR>VC支持查看变量、表达式和内存的值。所有这些观察都必须是在断点中断的情况下进行。<BR>观看变量的值最简单，当断点到达时，把光标移动到这个变量上，停留一会就可以看到变量的值。<BR>VC提供一种被成为Watch的机制来观看变量和表达式的值。在断点状态下，在变量上单击右键，选择Quick Watch， 就弹出一个对话框，显示这个变量的值。<BR>单击Debug工具条上的Watch按钮，就出现一个Watch视图（Watch1,Watch2,Watch3,Watch4），在该视图中输入变量或者表达式，就可以观察 变量或者表达式的值。注意：这个表达式不能有副作用，例如++运算符绝对禁止用于这个表达式中，因为这个运算符将修改变量的值，导致 软件的逻辑被破坏。<BR><BR><B>Memory</B><BR>由于指针指向的数组，Watch只能显示第一个元素的值。为了显示数组的后续内容，或者要显示一片内存的内容，可以使用memory功能。在 Debug工具条上点memory按钮，就弹出一个对话框，在其中输入地址，就可以显示该地址指向的内存的内容。<B><BR><BR>Varibles</B><BR>Debug工具条上的Varibles按钮弹出一个框，显示所有当前执行上下文中可见的变量的值。特别是当前指令涉及的变量，以红色显示。<BR><BR><B>寄存器</B><BR>Debug工具条上的Reigsters按钮弹出一个框，显示当前的所有寄存器的值。<BR><BR><IMG src="file:///E:/书籍/image/paragraph.gif"> 进程控制<BR>VC允许被中断的程序继续运行、单步运行和运行到指定光标处，分别对应快捷键F5、F10/F11和CTRL+F10。各个快捷键功能如下： <BR>　 
<TABLE id=AutoNumber2 style="BORDER-COLLAPSE: collapse" borderColor=#111111 cellSpacing=0 cellPadding=0 width="91%" border=1>
<TBODY>
<TR>
<TD align=middle width="30%"><B>快捷键</B></TD>
<TD align=middle width="70%"><B>说明</B></TD></TR>
<TR>
<TD align=middle width="30%">F5</TD>
<TD width="70%">继续运行</TD></TR>
<TR>
<TD align=middle width="30%">F10</TD>
<TD width="70%">单步，如果涉及到子函数，不进入子函数内部</TD></TR>
<TR>
<TD align=middle width="30%">F11</TD>
<TD width="70%">单步，如果涉及到子函数，进入子函数内部</TD></TR>
<TR>
<TD align=middle width="30%">CTRL+F10</TD>
<TD width="70%">运行到当前光标处。</TD></TR></TBODY></TABLE>
<P><IMG src="file:///E:/书籍/image/paragraph.gif"> Call Stack<BR>调用堆栈反映了当前断点处函数是被那些函数按照什么顺序调用的。单击Debug工具条上的Call stack就显示Call Stack对话框。在CallStack对话框中显示了一个调用系列，最上面的是当前函数，往下依次是调用函数的上级函数。单击这些函数名可以跳到对应的函数中去。<BR><BR><IMG src="file:///E:/书籍/image/paragraph.gif"> 其他调试手段<BR>系统提供一系列特殊的函数或者宏来处理Debug版本相关的信息，如下： </P>
<TABLE id=AutoNumber3 style="BORDER-COLLAPSE: collapse" borderColor=#111111 cellSpacing=0 cellPadding=0 width="86%" border=1>
<TBODY>
<TR>
<TD align=middle width="42%"><B>宏名/函数名</B></TD>
<TD align=middle width="58%"><B>说明</B></TD></TR>
<TR>
<TD align=middle width="42%">TRACE</TD>
<TD width="58%">使用方法和printf完全一致，他在output框中输出调试信息</TD></TR>
<TR>
<TD align=middle width="42%">ASSERT</TD>
<TD width="58%">它接收一个表达式，如果这个表达式为TRUE，则无动作，否则中断当前程序执行。对于系统中出现这个宏 导致的中断，应该认为你的函数调用未能满足系统的调用此函数的前提条件。例如，对于一个还没有创建的窗口调用SetWindowText等。</TD></TR>
<TR>
<TD align=middle width="42%">VERIFY</TD>
<TD width="58%">和ASSERT功能类似，所不同的是，在Release版本中，ASSERT不计算输入的表达式的值，而VERIFY计算表达式的值。</TD></TR></TBODY></TABLE>
<P><IMG src="file:///E:/书籍/image/paragraph.gif"> 关注<BR>一个好的程序员不应该把所有的判断交给编译器和调试器，应该在程序中自己加以程序保护和错误定位，具体措施包括： 
<UL>
<LI>对于所有有返回值的函数，都应该检查返回值，除非你确信这个函数调用绝对不会出错，或者不关心它是否出错。 
<LI>一些函数返回错误，需要用其他函数获得错误的具体信息。例如accept返回INVALID_SOCKET表示accept失败，为了查明 具体的失败原因，应该立刻用WSAGetLastError获得错误码，并针对性的解决问题。 
<LI>有些函数通过异常机制抛出错误，应该用TRY-CATCH语句来检查错误 
<LI>程序员对于能处理的错误，应该自己在底层处理，对于不能处理的，应该报告给用户让他们决定怎么处理。如果程序出了异常， 却不对返回值和其他机制返回的错误信息进行判断，只能是加大了找错误的难度。 </LI></UL>另外：VC中要编制程序不应该一开始就写cpp/h文件，而应该首先创建一个合适的工程。因为只有这样，VC才能选择合适的编译、连接 选项。对于加入到工程中的cpp文件，应该检查是否在第一行显式的包含stdafx.h头文件，这是Microsoft Visual Studio为了加快编译 速度而设置的预编译头文件。在这个#include "stdafx.h"行前面的所有代码将被忽略，所以其他头文件应该在这一行后面被包含。<BR>对于.c文件，由于不能包含stdafx.h，因此可以通过Project settings把它的预编译头设置为“不使用”，方法是： 
<UL>
<LI>弹出Project settings对话框 
<LI>选择C/C++ 
<LI>Category选择Precompilation Header 
<LI>选择不使用预编译头。</LI></UL><img src ="http://www.cnitblog.com/luoyi/aggbug/3607.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/luoyi/" target="_blank">罗毅</a> 2005-10-27 15:30 <a href="http://www.cnitblog.com/luoyi/articles/3607.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>