新书推介:《语义网技术体系》
作者:瞿裕忠,胡伟,程龚
   XML论坛     W3CHINA.ORG讨论区     计算机科学论坛     SOAChina论坛     Blog     开放翻译计划     新浪微博  
 
  • 首页
  • 登录
  • 注册
  • 软件下载
  • 资料下载
  • 核心成员
  • 帮助
  •   Add to Google

    >> 本版讨论高级C/C++编程、代码重构(Refactoring)、极限编程(XP)、泛型编程等话题
    [返回] 计算机科学论坛计算机技术与应用『 C/C++编程思想 』 → COM 组件设计与应用(三、四)——数据类型 查看新帖用户列表

      发表一个新主题  发表一个新投票  回复主题  (订阅本版) 您是本帖的第 12350 个阅读者浏览上一篇主题  刷新本主题   树形显示贴子 浏览下一篇主题
     * 贴子主题: COM 组件设计与应用(三、四)——数据类型 举报  打印  推荐  IE收藏夹 
       本主题类别:     
     卷积内核 帅哥哟,离线,有人找我吗?
      
      
      威望:8
      头衔:总统
      等级:博士二年级(版主)
      文章:3942
      积分:27590
      门派:XML.ORG.CN
      注册:2004/7/21

    姓名:(无权查看)
    城市:(无权查看)
    院校:(无权查看)
    给卷积内核发送一个短消息 把卷积内核加入好友 查看卷积内核的个人资料 搜索卷积内核在『 C/C++编程思想 』的所有贴子 访问卷积内核的主页 引用回复这个贴子 回复这个贴子 查看卷积内核的博客楼主
    发贴心情 COM 组件设计与应用(三、四)——数据类型

    一、前言
      上回书介绍了GUID、CLSID、IID和接口的概念。本回的重点是介绍 COM 中的数据类型。咋还不介绍组件程序的设计步骤呀?咳......别着急,别着急!孔子曰:“饭要一口一口地吃”;老子语:“心急吃不了热豆腐”,孙子云:“走一步看一步吧” ...... 先掌握必要的知识,将来写起程序来才会得心应手也:-)

      走入正题之前,请大家牢牢记住一条原则:COM 组件是运行在分布式环境中的。比如,你写了一个组件程序(DLL或EXE),那么使用者可能是在本机的某个进程内加载组件(INPROC_SERVER);也可能是从另一个进程中调用组件的进程(LOCAL_SERVER);也可能是在这台计算机上调用地球那边计算机上的组件(REMOTE_SERVER)。所以在理解和设计的时候,要时时刻刻想起这句话。快!拿出小本本,记下来!

      二、HRESULT 函数返回值

      每个人在做程序设计的时候,都有他们各自的哲学思想。拿函数返回值来说,就有好多种形式。

       函数 返回值 返回值信息
    double sin(double)   浮点数值
    计算正玄值
    BOOL DeleteFile(LPCTSTR)   布尔值
    文件删除是否成功。如失败,需要GetLastError()才能取得失败原因
    void * malloc(size_t)   内存指针
    内存申请,如果失败,返回空指针 NULL
    LONG RegDeleteKey(HKEY,LPCTSTR)   整数
    删除注册表项。0表示成功,非0失败,同时这个值就反映了失败的原因
    UINT DragQueryFile(HDROP,UINT,LPTSTR,UINT)   整数
    取得拖放文件信息。以不同的参数调用,则返回不同的含义:

      一会儿表示文件个数,一会儿表示文件名长度,一会儿表示字符长度

    ...... ......   ...
    ...... ......


      如此纷繁复杂的返回值,如此含义多变的返回值,使得大家在学习和使用的过程中,增加了额外的困难。好了,COM 的设计规范终于对他们进行了统一。组件API及接口指针中,除了IUnknown::AddRef()和IUnknown::Release()两个函数外,其它所有的函数,都以 HRESULT 作为返回值。大家想象一个组件的接口函数比如叫Add(),完成2个整数的加法运算,在C语言中,我们可以如下定义:

    long Add( long n1, long n2 )
       {
         return n1 + n2;
       }
      还记得刚才我们说的原则吗?COM 组件是运行在分布式环境中的。也就是说,这个函数可能运行在“地球另一边”的计算机上,既然运行在那么遥远的地方,就有可能出现服务器关机、网络掉线、运行超时、对方不在服务区......等异常。于是,这个加法函数,除了需要返回运算结果以外,还应该返回一个值------函数是否被正常执行了。

    HRESULT Add( long n1, long n2, long *pSum )
       {
         *pSum = n1 + n2;
         return S_OK;
       }
      如果函数正常执行,则返回 S_OK,同时真正的函数运行结果则通过参数指针返回。如果遇到了异常情况,则COM系统经过判断,会返回相应的错误值。常见的返回值有:

       HRESULT 值 含义
    S_OK 0x00000000 成功
    S_FALSE 0x00000001 函数成功执行完成,但返回时出现错误
    E_INVALIDARG 0x80070057 参数有错误
    E_OUTOFMEMORY 0x8007000E 内存申请错误
    E_UNEXPECTED 0x8000FFFF 未知的异常
    E_NOTIMPL 0x80004001 未实现功能
    E_FAIL 0x80004005 没有详细说明的错误。一般需要取得 Rich Error 错误信息(注1)
    E_POINTER 0x80004003 无效的指针
    E_HANDLE 0x80070006 无效的句柄
    E_ABORT 0x80004004 终止操作
    E_ACCESSDENIED 0x80070005 访问被拒绝
    E_NOINTERFACE 0x80004002 不支持接口


    按此在新窗口浏览图片

      图一、HRESULT 的结构

      HRESULT 其实是一个双字节的值,其最高位(bit)如果是0表示成功,1表示错误。具体参见 MSDN 之"Structure of COM Error Codes"说明。我们在程序中如果需要判断返回值,则可以使用比较运算符号;switch开关语句;也可以使用VC提供的宏:HRESULT hr = 调用组件函数;
       if( SUCCEEDED( hr ) ){...} // 如果成功
       ......
       if( FAILED( hr ) ){...} // 如果失败
       ......

      三、UNICODE

      计算机发明后,为了在计算机中表示字符,人们制定了一种编码,叫ASCII码。ASCII码由一个字节中的7位(bit)表示,范围是0x00 - 0x7F 共128个字符。他们以为这128个数字就足够表示abcd....ABCD....1234 这些字符了。

      咳......说英语的人就是“笨”!后来他们突然发现,如果需要按照表格方式打印这些字符的时候,缺少了“制表符”。于是又扩展了ASCII的定义,使用一个字节的全部8位(bit)来表示字符了,这就叫扩展ASCII码。范围是0x00 - 0xFF 共256个字符。

      咳......说中文的人就是聪明!中国人利用连续2个扩展ASCII码的扩展区域(0xA0以后)来表示一个汉字,该方法的标准叫GB-2312。后来,日文、韩文、阿拉伯文、台湾繁体BIG-5)......都使用类似的方法扩展了本地字符集的定义,现在统一称为 MBCS 字符集(多字节字符集)。这个方法是有缺陷的,因为各个国家地区定义的字符集有交集,因此使用GB-2312的软件,就不能在BIG-5的环境下运行(显示乱码),反之亦然。

      咳......说英语的人终于变“聪明”一些了。为了把全世界人民所有的所有的文字符号都统一进行编码,于是制定了UNICODE标准字符集。UNICODE 使用2个字节表示一个字符(unsigned shor int、WCHAR、_wchar_t、OLECHAR)。这下终于好啦,全世界任何一个地区的软件,可以不用修改地就能在另一个地区运行了。虽然我用 IE 浏览日本网站,显示出我不认识的日文文字,但至少不会是乱码了。UNICODE 的范围是 0x0000 - 0xFFFF 共6万多个字符,其中光汉字就占用了4万多个。嘿嘿,中国人赚大发了:0)

      在程序中使用各种字符集的方法:

    const char * p = "Hello"; // 使用 ASCII 字符集
       const char * p = "你好"; // 使用 MBCS 字符集,由于 MBCS 完全兼容 ASCII,多数情况下,我们并不严格区分他们
       LPCSTR p = "Hello,你好"; // 意义同上
       
       const WCHAR * p = L"Hello,你好"; // 使用 UNICODE 字符集
       LPCOLESTR p = L"Hello,你好"; // 意义同上
       
       // 如果预定义了_UNICODE,则表示使用UNICODE字符集;如果定义了_MBCS,则表示使用 MBCS
       const TCHAR * p = _T("Hello,你好");
       LPCTSTR p = _T("Hello,你好"); // 意义同上  在上面的例子中,T是非常有意思的一个符号(TCHAR、LPCTSTR、LPTSTR、_T()、_TEXT()...),它表示使用一种中间类型,既不明确表示使用 MBCS,也不明确表示使用 UNICODE。那到底使用哪种字符集那?嘿嘿......编译的时候决定吧。设置条件编译的方式是:VC6中,"Project\Settings...\C/C++卡片 Preprocessor definitions" 中添加或修改 _MBCS、_UNICODE;VC.NET中,"项目\属性\配置属性\常规\字符集"然后用组合窗进行选择。使用 T 类型,是非常好的习惯,严重推荐!

      四、BSTR

      COM 中除了使用一些简单标准的数据类型外(注2),字符串类型需要特别重点地说明一下。还记得原则吗?COM 组件是运行在分布式环境中的。通俗地说,你不能直接把一个内存指针直接作为参数传递给COM函数。你想想,系统需要把这块内存的内容传递到“地球另一 边”的计算机上,因此,我至少需要知道你这块内存的尺寸吧?不然让我如何传递呀?传递多少字节呀?!而字符串又是非常常用的一种类型,因此 COM 设计者引入了 BASIC 中字符串类型的表示方式---BSTR。BSTR 其实是一个指针类型,它的内存结构是:(输入程序片段 BSTR p = ::SysAllocString(L"Hello,你好");断点执行,然后观察p的内存)

    按此在新窗口浏览图片
      图二、BSTR 内存结构

      BSTR 是一个指向 UNICODE 字符串的指针,且 BSTR 向前的4个字节中,使用DWORD保存着这个字符串的字节长度( 没有含字符串的结束符)。因此系统就能够正确处理并传送这个字符串到“地球另一 边”了。特别需要注意的是,由于BSTR的指针就是指向 UNICODE 串,因此 BSTR 和 LPOLESTR 可以在一定程度上混用,但一定要注意:

      有函数 fun(LPCOLESTR lp),则你调用 BSTR p=...; fun(p); 正确

      有函数 fun(const BSTR bstr),则你调用 LPCOLESTR p=...; fun(p); 错误!!!

    有关 BSTR 的处理函数:
      API 函数 说明
    SysAllocString() 申请一个 BSTR 指针,并初始化为一个字符串
    SysFreeString() 释放 BSTR 内存
    SysAllocStringLen() 申请一个指定字符长度的 BSTR 指针,并初始化为一个字符串
    SysAllocStringByteLen() 申请一个指定字节长度的 BSTR 指针,并初始化为一个字符串
    SysReAllocStringLen() 重新申请 BSTR 指针
    CString 函数
    说明

    AllocSysString() 从 CString 得到 BSTR
    SetSysString() 重新申请 BSTR 指针,并复制到 CString 中
    CComBSTR 函数

    ATL 的 BSTR 包装类。在 atlbase.h 中定义

    Append()、AppendBSTR()、AppendBytes()、ArrayToBSTR()、BSTRToArray()、AssignBSTR()、Attach()、Detach()、Copy()、CopyTo()、Empty()、Length()、ByteLength()、ReadFromStream()、WriteToStream()、LoadString()、ToLower()、ToUpper()
    运算符重载:!,!=,==,<,>,&,+=,+,=,BSTR     太多了,但从函数名称不能看出其基本功能。详细资料,查看MSDN 吧。另外,左侧函数,有很多是 ATL 7.0 提供的,VC6.0 下所带的 ATL 3.0 不支持。
        由于我们将来主要用 ATL 开发组件程序,因此使用 ATL 的 CComBSTR 为主。VC也提供了其它的包装类 _bstr_t。


    五、各种字符串类型之间的转换
      1、函数 WideCharToMultiByte(),转换 UNICODE 到 MBCS。使用范例:

          LPCOLESTR lpw = L"Hello,你好";
          size_t wLen = wcslen( lpw ) + 1;  // 宽字符字符长度,+1表示包含字符串结束符
          
          int aLen=WideCharToMultiByte(  // 第一次调用,计算所需 MBCS 字符串字节长度
      CP_ACP,
      0,
      lpw,  // 宽字符串指针
      wLen, // 字符长度
      NULL,
      0,  // 参数0表示计算转换后的字符空间
      NULL,
      NULL);
     
          LPSTR lpa = new char [aLen];
     
          WideCharToMultiByte(
      CP_ACP,
      0,
      lpw,
      wLen,
      lpa,  // 转换后的字符串指针
      aLen, // 给出空间大小
      NULL,
      NULL);

          // 此时,lpa 中保存着转换后的 MBCS 字符串
          ... ... ... ...
          delete [] lpa;

        2、函数 MultiByteToWideChar(),转换 MBCS 到 UNICODE。使用范例:
          LPCSTR lpa = "Hello,你好";
          size_t aLen = strlen( lpa ) + 1;
          
          int wLen = MultiByteToWideChar(
      CP_ACP,
      0,
      lpa,
      aLen,
      NULL,
      0);
          
          LPOLESTR lpw = new WCHAR [wLen];
          MultiByteToWideChar(
      CP_ACP,
      0,
      lpa,
      aLen,
      lpw,
      wLen);
          ... ... ... ...
          delete [] lpw;

        3、使用 ATL 提供的转换宏。
     

    A2BSTR OLE2A T2A W2A
    A2COLE OLE2BSTR T2BSTR W2BSTR
    A2CT OLE2CA T2CA W2CA
    A2CW OLE2CT T2COLE W2COLE
    A2OLE OLE2CW T2CW W2CT
    A2T OLE2T T2OLE W2OLE
    A2W OLE2W T2W W2T


    上表中的宏函数,其实非常容易记忆: 2 好搞笑的缩写,to 的发音和 2 一样,所以借用来表示“转换为、转换到”的含义。
    A ANSI 字符串,也就是 MBCS。
    W、OLE 宽字符串,也就是 UNICODE。
    T 中间类型T。如果定义了 _UNICODE,则T表示W;如果定义了 _MBCS,则T表示A
    C const 的缩写

    使用范例:

          #include <atlconv.h>
          
          void fun()
          {
              USES_CONVERSION;  // 只需要调用一次,就可以在函数中进行多次转换
              
              LPCTSTR lp = OLE2CT( L"Hello,你好") );
              ... ... ... ...
              // 不用显式释放 lp 的内存,因为
              // 由于 ATL 转换宏使用栈作为临时空间,函数结束后会自动释放栈空间。
          }
      使用 ATL 转换宏,由于不用释放临时空间,所以使用起来非常方便。但是考虑到栈空间的尺寸(VC 默认2M),使用时要注意几点:
        1、只适合于进行短字符串的转换;
        2、不要试图在一个次数比较多的循环体内进行转换;
        3、不要试图对字符型文件内容进行转换,因为文件尺寸一般情况下是比较大的;
        4、对情况 2 和 3,要使用 MultiByteToWideChar() 和 WideCharToMultiByte();
        
    六、VARIANT
      C++、BASIC、Java、Pascal、Script......计算机语言多种多样,而它们各自又都有自己的数据类型,COM 产生目的,其中之一就是要跨语言(注3)。而 VARIANT 数据类型就具有跨语言的特性,同时它可以表示(存储)任意类型的数据。从C语言的角度来讲,VARIANT 其实是一个结构,结构中用一个域(vt)表示------该变量到底表示的是什么类型数据,同时真正的数据则存贮在 union 空间中。结构的定义太长了(虽然长,但其实很简单)大家去看 MSDN 的描述吧,这里给出如何使用的简单示例:

    学生:我想用 VARIANT 表示一个4字节长的整数,如何做?
    老师:VARIANT v; v.vt=VT_I4; v.lVal=100;

    学生:我想用 VARIANT 表示布尔值“真”,如何做?
    老师:VARIANT v; v.vt=VT_BOOL; v.boolVal=VARIANT_TRUE;
    学生:这么麻烦?我能不能 v.boolVal=true; 这样写?
    老师:不可以!因为
      类型 字节长度 假值 真值
    bool 1(char) 0(false) 1(true)
    BOOL 4(int) 0(FALSE) 1(TRUE)
    VT_BOOL 2(short int) 0(VARIANT_FALSE) -1(VARIANT_TRUE)

      所以如果你 v.boolVal=true 这样赋值,那么将来 if(VARIANT_TRUE==v.boolVal) 的时候会出问题(-1 != 1)。但是你注意观察,任何布尔类型的“假”都是0,因此作为一个好习惯,在做布尔判断的时候,不要和“真值”相比较,而要与“假值”做比较。
    学生:谢谢老师,你太牛了。我对老师的敬仰如滔滔江水,连绵不绝......

    学生:我想用 VARIANT 保存字符串,如何做?
    老师:VARIANT v; v.vt=VT_BSTR; v.bstrVal=SysAllocString(L"Hello,你好");

    学生:哦......我明白了。可是这么操作真够麻烦的,有没有简单一些的方法?
    老师:有呀,你可以使用现成的包装类 CComVariant、COleVariant、_variant_t。比如上面三个问题就可以这样书写:CComVariant v1(100),v2(true),v3("Hello,你好"); 简单了吧?!(注4)

    学生:老师,我再问最后一个问题,我如何用 VARIANT 保存一个数组?
    老师:这个问题很复杂,我现在不能告诉你,我现在告诉你怕你印象不深......(注5)
    学生:~!@#$%^&*()......晕!

    七、小结
        以上所介绍的内容,是基本功,必须熟练掌握。先到这里吧,休息一会儿......更多精彩内容,敬请关注《COM 组件设计与应用(四)》



    --------------------------------------------------------------------------------

    注1:在后续的 ISupportErrorInfo 接口中介绍。
    注2:常见的数据类型,请参考 IDL 文件的说明。(别着急,还没写那......嘿嘿)
    注3:跨语言就是各种语言中都能使用COM组件。但啥时候能跨平台呢?
    注4:CComVariant/COlevariant/_variant_t 请参看 MSDN。
    注5:关于安全数组 SafeArray 的使用,在后续的文章中讨论。
     

    一、前言
      上回书介绍了GUID、CLSID、IID和接口的概念。本回的重点是介绍 COM 中的数据类型。咋还不介绍组件程序的设计步骤呀?咳......别着急,别着急!孔子曰:“饭要一口一口地吃”;老子语:“心急吃不了热豆腐”,孙子云:“走一步看一步吧” ...... 先掌握必要的知识,将来写起程序来才会得心应手也:-)
      走入正题之前,请大家牢牢记住一条原则:COM 组件是运行在分布式环境中的。比如,你写了一个组件程序(DLL或EXE),那么使用者可能是在本机的某个进程内加载组件(INPROC_SERVER);也可能是从另一个进程中调用组件的进程(LOCAL_SERVER);也可能是在这台计算机上调用地球那边计算机上的组件(REMOTE_SERVER)。所以在理解和设计的时候,要时时刻刻想起这句话。快!拿出小本本,记下来!

    二、HRESULT 函数返回值
      每个人在做程序设计的时候,都有他们各自的哲学思想。拿函数返回值来说,就有好多种形式。


    [此贴子已经被作者于2007-10-22 9:35:06编辑过]

       收藏   分享  
    顶(0)
      




    ----------------------------------------------
    事业是国家的,荣誉是单位的,成绩是领导的,工资是老婆的,财产是孩子的,错误是自己的。

    点击查看用户来源及管理<br>发贴IP:*.*.*.* 2007/10/19 8:31:00
     
     卷积内核 帅哥哟,离线,有人找我吗?
      
      
      威望:8
      头衔:总统
      等级:博士二年级(版主)
      文章:3942
      积分:27590
      门派:XML.ORG.CN
      注册:2004/7/21

    姓名:(无权查看)
    城市:(无权查看)
    院校:(无权查看)
    给卷积内核发送一个短消息 把卷积内核加入好友 查看卷积内核的个人资料 搜索卷积内核在『 C/C++编程思想 』的所有贴子 访问卷积内核的主页 引用回复这个贴子 回复这个贴子 查看卷积内核的博客2
    发贴心情 
    COM 组件设计与应用(四)——简单调用组件
    二、组件的启动和释放
      在第三回中,大家用“小本本”记录了一个原则:COM 组件是运行在分布式环境中的 。于是,如何启动组件立刻就遇到了严重的问题,大家看这段代码:

    p = new 对象;
       p->对象函数();
       delete p;
      这样的代码再熟悉不过了,在本地进程中运行是不会有问题的。但是你想想,如果这个对象是在“地球另一边”的计算机上,结果会如何?嘿嘿,C++ 在设计 new 的时候,可没有考虑远程的实现呀(计算机语言当然不会,也没必要去设计)。因此启动组件、调用接口的功能,当然就由 COM 系统来实现了。

    按此在新窗口浏览图片
      图一 组件调用机制

      由上图可以看出,当调用组件的时候,其实是依靠代理(运行在本地)和存根(运行在远端)之间的通讯完成的。具体来说,当客户程序通过 CoCreateInstance() 函数启动组件,则代理接管该调用,它和存根通讯,存根则它所在的本地(相对于客户程序来说就是远程了)执行 new 操作加载对象。对于初学者,你可以不用理它,代理和存根对我们来说是透明的。只要大约知道是怎么一回事就一切OK了。

      问题又来了,这个远程的对象什么时候消灭呢?在第二回介绍接口概念的时候,当时我们特意忽略了两个函数,就是IUnknown::AddRef()和IUnknown::Release(),从函数名就能猜到了,一个是对内部引用记数器(Ref)加1,一个是释放(减1),当记数器减为0的时候,就是释放的机会啦。看起来很复杂,没办法,因为这是在介绍原理。其实在我们写程序的时候到比较简单,请大家遵守几个原则:

      1、启动组件得到一个接口指针(Interface)后,不要调用AddRef()。因为系统知道你得到了一个指针,所以它已经帮你调用了AddRef()函数;

      2、通过QueryInterface()得到另一个接口指针后,不要调用AddRef()。因为......和上面的道理一样;

      3、当你把接口指针赋值给(保存到)另一个变量中的时候,请调用AddRef();

      4、当不需要再使用接口指针的时候,务必执行Release()释放;

      5、当使用智能指针的时候,可以省略指针的维护工作;(注1)

      三、内存分配和释放

      自从学习了C语言,老师就教导我们说:对于动态内存的申请和释放,一定要遵守“谁申请,谁释放”的原则。在此原则的指导下,不仅是我、不仅是你,就连特级大师都设计了这样怪怪的函数:

       函数 说明 评论
    GetWindowText(HWND,LPTSTR,int) 取得窗口标题。需要在参数中给出保存标题所使用的内存指针,和这块内存的尺寸。 晕!我又不知道窗口标题的长度,居然还要我提供尺寸?!没办法,只能估摸着给一个大一些的尺寸吧。
    sprintf(char *,const char *,...) 格式化一个字符串。这个函数不用给出缓冲区的长度啦。 恩,虽然不用给出长度了,但你敢给个小尺寸吗?哼!
    int CListBox::GetTextLen(int)

      CListBox::GetText(int,LPTSTR)
    取得列表窗中子项目的标题。需要调用两个函数,先取得长度,然后分配内存,再实际取得标题内容。 真烦!


      说实在的,不但函数调用者感觉别扭,就连函数设计者心情也不会爽的,而这一切都是为了满足所谓“谁申请,谁释放”的原则。 解决这个问题最好的方式就是:函数内部根据实际需要动态申请内存,而调用者负责释放。这虽然违背了上述原则,但 COM 从方便性和效率出发,确实是这么设计的。

         C语言 C++语言 Windows 平台 COM IMalloc 接口 BSTR
    申请 malloc() new GlobalAlloc() CoTaskMemAlloc() Alloc() SysAllocString()
    重新申请 realloc()   GlobalReAlloc() CoTaskRealloc() Realloc() SysReAllocString()
    释放 free() delete GlobalFree() CoTaskMemFree() Free() SysFreeString()


      以上这些函数必须要按类型配合使用(比如:new 申请的内存,则必须用 delete 释放)。在 COM 内部,当然你可以随便使用任何类型的内存分配释放函数,但组件如果需要与客户进行内存的交互,则必须使用上表中的后三类函数族。

      1、BSTR 内存在上回书中,已经有比较丰富的介绍了,不再重复;

      2、CoTaskXXX()函数族,其本质上就是调用C语言的函数(malloc...);

      3、IMalloc 接口又是对 CoTaskXXX() 函数族的一个包装。包装后,同时增强了一些功能,比如:IMalloc::GetSize()可以取得尺寸,使用 IMallocSpy 可以监视内存的使用;

      四、参数传递方向

      在C语言的函数声明中,尤其当参数为指针的时候,你是看不出它传递方向的。比如:

      void fun(char * p1, int * p2); 请问,p1、p2 哪个是入参?哪个是出参?甚或都是入参或都是出参?由于牵扯到内存分配和释放等问题,COM 需要明确标注参数方向。以后我们写程序,就类似下面的样子:

    HRESULT Add([in] long n1, [in] long n2, [out] long *pnSum); // IDL文件(注2)
       STDMETHOD(Add)(/*[in]*/ long n1, /*[in]*/ long n2, /*[out]*/ long *pnSum); // .h文件
      如果参数是动态分配的内存指针,那么遵守如下的规定:

       方向 申请人 释放人 提示
    [in] 调用者 调用者 组件接收指针后,不能重新分配内存
    [out] 组件 调用者 组件返回指针后,调用者“爱咋咋地”(注3)
    [in,out] 调用者 调用者 组件可以重新分配内存


      五、示例程序

      示例一、由 CLSID 得到 ProgID。(程序以 word 为例子。如果运行不正确,嘿嘿,你没有安装 word 吧?)

    ::CoInitialize( NULL );
      HRESULT hr;
      // {000209FF-0000-0000-C000-000000000046} = word.application.9
      CLSID clsid = {0x209ff,0,0,{0xc0,0,0,0,0,0,0,0x46}};
      LPOLESTR lpwProgID = NULL;
      
      hr = ::ProgIDFromCLSID( clsid, &lpwProgID );
      if ( SUCCEEDED(hr) )
      {
        ::MessageBoxW( NULL, lpwProgID, L"ProgID", MB_OK );
        IMalloc * pMalloc = NULL;
        hr = ::CoGetMalloc( 1, &pMalloc ); // 取得 IMalloc
        if ( SUCCEEDED(hr) )
        {
          pMalloc->Free( lpwProgID ); // 释放ProgID内存
          pMalloc->Release();     // 释放IMalloc
        }
      }
      ::CoUninitialize();
      示例二、如何使用“浏览文件夹”选择对话窗。

    CString BrowseFolder(HWND hWnd, LPCTSTR lpTitle)
    {
      // 调用 SHBrowseForFolder 取得目录(文件夹)名称
      // 参数 hWnd: 父窗口句柄
      // 参数 lpTitle: 窗口标题
      
      char szPath[MAX_PATH]={0};
      BROWSEINFO m_bi;
      m_bi.ulFlags = BIF_RETURNONLYFSDIRS | BIF_STATUSTEXT;
      m_bi.hwndOwner = hWnd;
      m_bi.pidlRoot = NULL;
      m_bi.lpszTitle = lpTitle;
      m_bi.lpfn = NULL;
      m_bi.lParam = NULL;
      m_bi.pszDisplayName = szPath;
      LPITEMIDLIST pidl = ::SHBrowseForFolder( &m_bi );
      if ( pidl )
      {
        if( !::SHGetPathFromIDList ( pidl, szPath ) ) szPath[0]=0;
        IMalloc * pMalloc = NULL;
        if ( SUCCEEDED ( ::SHGetMalloc( &pMalloc ) ) ) // 取得IMalloc分配器接口
        {
          pMalloc->Free( pidl );  // 释放内存
          pMalloc->Release();    // 释放接口
        }
      }
      return szPath;
    }
      示例三、在窗口中显示一幅 JPG 图象。

    void CxxxView::OnDraw(CDC* pDC)
    {
      ::CoInitialize(NULL); // COM 初始化
      HRESULT hr;
      CFile file;
      
      file.Open( "c:\\aa.jpg", CFile::modeRead | CFile::shareDenyNone ); // 读入文件内容
      DWORD dwSize = file.GetLength();
      HGLOBAL hMem = ::GlobalAlloc( GMEM_MOVEABLE, dwSize );
      LPVOID lpBuf = ::GlobalLock( hMem );
      file.ReadHuge( lpBuf, dwSize );
      file.Close();
      ::GlobalUnlock( hMem );
      IStream * pStream = NULL;
      IPicture * pPicture = NULL;
      
      // 由 HGLOBAL 得到 IStream,参数 TRUE 表示释放 IStream 的同时,释放内存
      hr = ::CreateStreamOnHGlobal( hMem, TRUE, &pStream );
      ASSERT ( SUCCEEDED(hr) );
      
      hr = ::OleLoadPicture( pStream, dwSize, TRUE, IID_IP

    ----------------------------------------------
    事业是国家的,荣誉是单位的,成绩是领导的,工资是老婆的,财产是孩子的,错误是自己的。

    点击查看用户来源及管理<br>发贴IP:*.*.*.* 2007/10/19 8:35:00
     
     卷积内核 帅哥哟,离线,有人找我吗?
      
      
      威望:8
      头衔:总统
      等级:博士二年级(版主)
      文章:3942
      积分:27590
      门派:XML.ORG.CN
      注册:2004/7/21

    姓名:(无权查看)
    城市:(无权查看)
    院校:(无权查看)
    给卷积内核发送一个短消息 把卷积内核加入好友 查看卷积内核的个人资料 搜索卷积内核在『 C/C++编程思想 』的所有贴子 访问卷积内核的主页 引用回复这个贴子 回复这个贴子 查看卷积内核的博客3
    发贴心情 
    ASSERT(hr==S_OK);

    long nWidth,nHeight;  // 宽高,MM_HIMETRIC 模式,单位是0.01毫米
    pPicture->get_Width( &nWidth );    // 宽
    pPicture->get_Height( &nHeight );  // 高

    ////////原大显示//////
    CSize sz( nWidth, nHeight );
    pDC->HIMETRICtoDP( &sz );  // 转换 MM_HIMETRIC 模式单位为 MM_TEXT 像素单位
    pPicture->Render(pDC->m_hDC,0,0,sz.cx,sz.cy,
    0,nHeight,nWidth,-nHeight,NULL);

    ////////按窗口尺寸显示////////
    // CRect rect; GetClientRect(&rect);
    // pPicture->Render(pDC->m_hDC,0,0,rect.Width(),rect.Height(),
    // 0,nHeight,nWidth,-nHeight,NULL);

    if ( pPicture ) pPicture->Release();// 释放 IPicture 指针
    if ( pStream ) pStream->Release();  // 释放 IStream 指针,同时释放了 hMem

    ::CoUninitialize();
    }
    示例四、在桌面建立快捷方式
        在阅读代码之前,先看一下关于“快捷方式”组件的结构示意图。

    按此在新窗口浏览图片
    图二、快捷方式组件的接口结构示意图

      从结构图中可以看出,“快捷方式”组件(CLSID_ShellLink),有3个(其实不止)接口,每个接口完成一组相关功能的函数。IShellLink 接口(IID_IShellLink)提供快捷方式的参数读写功能(见图三),IPersistFile 接口(IID_IPersistFile)提供快捷方式持续性文件的读写功能。对象的持续性(注5),是一个非常常用,并且功能强大的接口家族。但今天,我们只要了解其中两函数,就可以了:IPersistFile::Save()和IPersistFile:Load()。(注6)

    按此在新窗口浏览图片
    图三、快捷方式中的各种属性
    #include < atlconv.h >
    void CreateShortcut(LPCTSTR lpszExe, LPCTSTR lpszLnk)
    {
     // 建立块捷方式
     // 参数 lpszExe: EXE 文件全路径名
     // 参数 lpszLnk: 快捷方式文件全路径名
     
     ::CoInitialize( NULL );

     IShellLink * psl = NULL;
     IPersistFile * ppf = NULL;

     HRESULT hr = ::CoCreateInstance(  // 启动组件
      CLSID_ShellLink,      // 快捷方式 CLSID
      NULL,                 // 聚合用(注4)
      CLSCTX_INPROC_SERVER, // 进程内(Shell32.dll)服务
      IID_IShellLink,       // IShellLink 的 IID
      (LPVOID *)&psl );     // 得到接口指针

     if ( SUCCEEDED(hr) )
     {
      psl->SetPath( lpszExe );  // 全路径程序名
    //  psl->SetArguments();      // 命令行参数
    //  psl->SetDescription();    // 备注
    //  psl->SetHotkey();         // 快捷键
    //  psl->SetIconLocation();   // 图标
    //  psl->SetShowCmd();        // 窗口尺寸
      
      // 根据 EXE 的文件名,得到目录名
      TCHAR szWorkPath[ MAX_PATH ];
      ::lstrcpy( szWorkPath, lpszExe );
      LPTSTR lp = szWorkPath;
      while( *lp )    lp++;
      while( ''\\'' != *lp )    lp--;
      *lp=0;

      // 设置 EXE 程序的默认工作目录
      psl->SetWorkingDirectory( szWorkPath );

      hr = psl->QueryInterface(  // 查找持续性文件接口指针
       IID_IPersistFile,      // 持续性接口 IID
       (LPVOID *)&ppf );      // 得到接口指针

      if ( SUCCEEDED(hr) )
      {
       USES_CONVERSION;       // 转换为 UNICODE 字符串
       ppf->Save( T2COLE( lpszLnk ), TRUE );  // 保存
      }
     }
     if ( ppf ) ppf->Release();
     if ( psl ) psl->Release();

     ::CoUninitialize();
    }

    void OnXXX()
    {
     CreateShortcut(
      _T("c:\\winnt\\notepad.exe"),  // 记事本程序。注意,你的系统是否也是这个目录?
      _T("c:\\Documents and Settings\\Administrator\\桌面\\我的记事本.lnk")
     );
     // 桌面上建立快捷方式(lnk)文件的全路径名。注意,你的系统是否也是这个目录?
     // 如果用程序实现寻找桌面的路径,则可以查注册表
     // HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders
    }


    七、小结

      本回介绍的内容比较实用。大家不要只抄袭代码,而一定要理解它。结合 MSDN 的说明去思索代码、理解其含义。好了,想方设法把代码忘掉!三天后(如过你还没有忘记,那就再过三天),你在不参考示例代码,但可以随便翻阅 MSDN 的情况下,自己能独立地再次完成这四个例程,那么恭喜你,你已经入门了:0) 从下回开始,我们要用 ATL 做 COM 的开发工作啦,您老人家准备好了吗?


    作业,留作业啦......
      1、你已经学会如何建立快捷方式了,那么你知道怎么读取它的属性吗?(如果写不出这个程序,那么你就不用继续学习了。因为......动点脑筋呀!我还没有见过象你这么笨的学生呢!)
      2、示例程序三中使用了 IPicture 接口显示一个 JPG 图象。那么你现在去完成一个功能,把 JPG 文件转换为 BMP 文件。

    --------------------------------------------------------------------------------
    注1:智能指针的概念和用法,后续介绍。
    注2:IDL 文件,下回就要介绍啦。
    注3:东北话,想干什么都可以,反正我不管啦。
    注4:聚合,也许在第30回中介绍吧:-)
    注5:持续性,IPersistXXXXXX是一个非常强大的接口家族,后续介绍。
    注6:想知道 IShellLink、IPersistFile接口的所有函数吗?别愣着,快去看MSDN呀......

    ----------------------------------------------
    事业是国家的,荣誉是单位的,成绩是领导的,工资是老婆的,财产是孩子的,错误是自己的。

    点击查看用户来源及管理<br>发贴IP:*.*.*.* 2007/10/22 9:37:00
     
     oldnwind 帅哥哟,离线,有人找我吗?
      
      
      等级:大一新生
      文章:8
      积分:94
      门派:XML.ORG.CN
      注册:2006/4/5

    姓名:(无权查看)
    城市:(无权查看)
    院校:(无权查看)
    给oldnwind发送一个短消息 把oldnwind加入好友 查看oldnwind的个人资料 搜索oldnwind在『 C/C++编程思想 』的所有贴子 访问oldnwind的主页 引用回复这个贴子 回复这个贴子 查看oldnwind的博客4
    发贴心情 
    二三楼之间调用OleLoadPicture中,参数有遗缺。
    查MSDN,大概应为 hr = ::OleLoadPicture( pStream, dwSize, TRUE, IID_IPicture, &pPicture);

    不知对否?

    点击查看用户来源及管理<br>发贴IP:*.*.*.* 2009/2/1 16:53:00
     
     GoogleAdSense
      
      
      等级:大一新生
      文章:1
      积分:50
      门派:无门无派
      院校:未填写
      注册:2007-01-01
    给Google AdSense发送一个短消息 把Google AdSense加入好友 查看Google AdSense的个人资料 搜索Google AdSense在『 C/C++编程思想 』的所有贴子 访问Google AdSense的主页 引用回复这个贴子 回复这个贴子 查看Google AdSense的博客广告
    2024/5/1 4:50:08

    本主题贴数4,分页: [1]

    管理选项修改tag | 锁定 | 解锁 | 提升 | 删除 | 移动 | 固顶 | 总固顶 | 奖励 | 惩罚 | 发布公告
    W3C Contributing Supporter! W 3 C h i n a ( since 2003 ) 旗 下 站 点
    苏ICP备05006046号《全国人大常委会关于维护互联网安全的决定》《计算机信息网络国际联网安全保护管理办法》
    93.750ms