COM 组件设计与应用

目录

COM 设计用(一) 起源及合文件... 1

COM设计用(二) GUID 接口... 10

COM设计用(三) 数据... 20

COM设计用(四) 简单调... 31

 

COM 设计用(一)
起源及合文件

作者:

一、前言

  公元一九九五年某个夜黑高的上,我的一位老跟我呀,以后写程序就和搭木一啦。你赶快学一些OLE的技......”,当我心里就 玩笑?搭木方式写程序?再100年吧......”,但作一名听的好学生,我始在店里踅摸(注1)有OLE籍(注2)。功夫不有心人,到了我的第一本COMOLE2 级编程技》,800的大布了我1/5的月工......于是始日夜耕.....
功夫不有心人,我完了全部著作,感想是:,在么呐

功夫不有心人,我又完了一遍大布,感想是:咳~~~,没懂!
功夫不有心人,我再,我再,我再 ... 感想是:哦~~~懂了一点点啦,哈哈哈。
...... ......
功夫不有心人,我于,我于懂了。
800
书对现在的我来,其也就10有用。到这时候才体会出什越薄的道理了。到后来,能到的也多了,上网也更方便更便宜了......

  VCKBASE上的朋友,不再经历我曾的痛苦、不再重蹈我头苍蝇般探索的辛、VCKBASE的蓬勃展、了中国件事腾飞(糟糕,吹的太也高了)......我打算节约一些在 BBS 分的时间,写个系列文,就叫“COM设计吧。今天是第一部分——起源。


二、文件的存

  传说350年前,牛被苹果到了,于是发现了万有引力。但到了二十一世在,任何一个技明和展,已不再依靠圣人灵光的一。技进步转而是被社会的需求、商的利益、争的力、行透等推的。微Windows平台上的件技也不例外,它的明,有其必然因素。什个因素那?答案是——文件的存
  打开记事本程序,入了一篇文章后,保存。——这样的文件叫构化文件
  打开电子表格程序,入一个班的学生姓名和考,保存。——这样的文件叫构化文件
  在我写的程序中,需要把特定的数据按照一定的构和序写到文件中保存。——这样的文件叫自定义结构化文件;(比如 *.bmp 文件)
  以上三种类型的文件,大家都的多了。那文件存就依靠上述的方式能足所有的用需求?恩~~~,至少从算机明后的50多年来,一直是用的了。嘿嘿,下面看看商利益的推作用,文件 的存形式生了什么变化吧。30以上的朋友,我估以前都使用以下几个著名的件:WordStar(独霸DOS下的英文编辑软件),WPS( 裘伯 君写的中文编辑软件,据当年的市占有率高达90%,各种计算机培班的必修程),LOTUS-123花公司出品的子表格件)......
在成功地推出 Windows 3.1 后,始垂涎桌面公自域。微 OFFICE 开发,各小独立地开发 WORD EXCEL 件,并采用自定义结方式,文件行存。在激烈的市场竞争下,了打败竞手,微自然地生了一个念------如果我能在 WORD 程序中嵌入 EXCEL,那购买了我 WORD 件的情况下,不就没有必要再 LOTUS-123 ?!(中国微的同志看到了,不要激,我是加了引号的呀)的生后,他们开始了施工作,就是 COM 的前身 OLE 的起源(注3)。但立刻就遇到了一个重的技术问题:需要把 WORD 生的 DOC 文件和 EXCEL 生的 XLS 文件保存在一起。

 

方案

缺点

建立一个子目,把 DOCXLS 同一个子目中。

数据隔离性好,WORD 不用了解 EXCEL 的存储结构;容易展。

构太松散,容易造成数据的坏或失。
不易携

修改文件存储结构,在DOC构基展出包容 XLS 构。

密,容易携一管理。

WORD 开发需要通 EXCEL 的存格式;缺少展性,不能新加一个型就展一下构吧?!

以上两个方案,都有重的缺陷,怎解决那?如果能有一个新方案,能合并前两个方案的点,消缺点,多好呀......是作磁操作系起家的,于是很自然地他提出了一个非常完美的设计方案,那就是把磁文件的管理方式移植到文件中了------合文件,俗称文件中的文件系当年都没有想到,就这么一个简单的想法,居然最后就演出了 COM 件程序设计的方法。可以合文件是 COM 的基石。下是磁文件组织方式与合文件组织方式的

一、左表示一个磁下的文件组织方式,右表示一个合文件内部的数据组织方式。

三、合文件的特点

  1. 合文件的内部是使用指构造的一棵树进行管理的。写程序的候要注意,由于使用的是向指,因此当做定位操作的候,向后定位比向前定位要快;
  2. 合文件中的,是真正保存数据的空。它的存储单512。也就是,即使你在流中只保存了一个字的数据,它也要占据512的文件空。啊~~~也太浪了呀?不浪!因文件保存在磁上,即使一个字要占用一个的空那;
  3. 不同的程,或同一个程的不同线程可以同时访问一个合文件的不同部分而互不干
  4. 大家都有这样的体会,当需要往一个文件中插入一个字,需要整个文件行操作,非常烦琐并且效率低下。而合文件提供了非常方便的增量访问能力;
  5. 繁地除文件,制文件后,磁的很零碎,需要使用磁整理工具行重新整合。和磁管理非常相似,合文件也会问题,在适当的候也需要整理,但比较简单,只要用一个函数就可以完成了。

四、浏览复合文件

  VC6.0 了一个工具合文件浏览,文件名是“vc/Common/Tools/DFView.exe”了方便使用程序,可以把它加到工具(tools)中。方法是:Tools/Customize.../Tools卡片中增加新的目。运行 DFView.exe,就可以打一个合文件察了(注4)。但奇怪的是,在 Microsoft Visual Studio .NET 2003 中,我反而找不到个工具程序了,汗!不过这恰好提供大家一个练习的机会,在你阅读完本篇文章并掌握了程方法后,自己写一个合文件浏览编辑程序,又手了,用的价

五、合文件函数

  合文件的函数和磁文件的操作非常似。所有些函数,被分3种类型:WIN API 全局函数,存 IStorage 接口函数,流 IStream 接口函数。什是接口?什是接口函数?以后的文章中再陆续里大家只要把接口看成是完成一操作功能的函数集合就可以了。
 

WIN API 函数

功能

StgCreateDocfile()

建立一个合文件,得到根存储对

StgOpenStorage()

一个合文件,得到根存储对

StgIsStorageFile()

判断一个文件是否是合文件

 

IStorage 函数

功能

CreateStorage()

在当前存中建立新存,得到子存储对

CreateStream()

在当前存中建立新流,得到流

OpenStorage()

子存,得到子存储对

OpenStream()

流,得到流

CopyTo()

制存下的所有象到目中,函数可以实现整理文件,放碎片空的功能

MoveElementTo()

动对象到目

DestoryElement()

RenameElement()

重命名

EnumElements()

当前存中所有的

SetElementTimes()

修改象的时间

SetClass()

在当前存中建立一个特殊的流象,用来保存CLSID(注5

Stat()

取得当前存中的系信息

Release()

关闭储对

 

IStream 函数

功能

Read()

从流中取数据

Write()

向流中写入数据

Seek()

定位写位置

SetSize()

置流尺寸。如果先知道大小,那个函数,可以提高性能

CopyTo()

制流数据到另一个流象中

Stat()

取得当前流中的系信息

Clone()

克隆一个流象,方便程序中的不同模操作同一个流

Release()

关闭

 

WIN API 充函数

功能

WriteClassStg()

CLSID到存中,同IStorage::SetClass()

ReadClassStg()

WriteClassStg()写入的CLSID,相当于IStorage::Stat()

WriteClassStm()

CLSID到流的始位置

ReadClassStm()

WriteClassStm()写入的CLSID

WriteFmtUserTypeStg()

写入用指定的剪板格式和名称到存

ReadFmtUserTypeStg()

WriteFmtUserTypeStg()写入的信息。方便用程序快速判断是否是它需要的格式数据。

CreateStreamOnHGlobal()

内存句柄 HGLOBAL 转换为

GetHGlobalFromStream()

取得CreateStreamOnHGlobal()用中使用的内存句柄

大家快速地浏览和掌握基本方法,上面所列表的函数并不是全部,我省略了函数和未实现函数部分。更全面的介请阅读 MSDN
下面程序片段,演示了一些基本函数功能和用方法。
示例一:建立一个合文件,并在其下建立一个子存,在子存中再建立一个流,写入数据。

void SampleCreateDoc()

{

       ::CoInitialize(NULL);      // COM 初始化

                           // 如果是MFC程序,可以使用AfxOleInit()替代

 

       HRESULT hr;         // 函数行返回

       IStorage *pStg = NULL;     // 根存接口指

       IStorage *pSub = NULL;     // 子存接口指

       IStream *pStm = NULL;      // 流接口指

 

       hr = ::StgCreateDocfile(   // 建立合文件

              L"c://a.stg", // 文件名称

              STGM_CREATE | STGM_WRITE | STGM_SHARE_EXCLUSIVE,      // 方式

              0,            // 保留参数

              &pStg);             // 取得根存接口指

       ASSERT( SUCCEEDED(hr) );   // 了突出重点,化程序构,所以使用了断言。

                           // 实际的程序中要使用条件判断和异常

 

       hr = pStg->CreateStorage(  // 建立子存

              L"SubStg",    // 子存名称

              STGM_CREATE | STGM_WRITE | STGM_SHARE_EXCLUSIVE,

              0,0,

              &pSub);             // 取得子存接口指

       ASSERT( SUCCEEDED(hr) );

 

       hr = pSub->CreateStream(   // 建立流

              L"Stm",             // 流名称

              STGM_CREATE | STGM_WRITE | STGM_SHARE_EXCLUSIVE,

              0,0,

              &pStm);             // 取得流接口指

       ASSERT( SUCCEEDED(hr) );

 

       hr = pStm->Write(          // 向流中写入数据

              "Hello",            // 数据地址

              5,            // 节长(注意,没有写入字符串尾的/0)

              NULL);        // 不需要得到实际写入的字节长

       ASSERT( SUCCEEDED(hr) );

 

       if( pStm )    pStm->Release();// 放流指

       if( pSub )    pSub->Release();// 放子存

       if( pStg )    pStg->Release();// 放根存

 

       ::CoUninitialize()         // COM

                           // 如果使用 AfxOleInit(),函数

}


二、运行示例程序一后,使用 DFView.exe 开观合文件的效果

示例二:打一个合文件,枚其根存下的所有象。

#include <atlconv.h> // ANSIMBCSUNICODE 转换

 

void SampleEnum()

{      // 你已 COM 初始化了

 

       LPCTSTR lpFileName = _T( "c://a.stg" );

       HRESULT hr;

       IStorage *pStg = NULL;

      

       USES_CONVERSION;                         // (注6

       LPCOLESTR lpwFileName = T2COLE( lpFileName );   // 转换T为宽字符

       hr = ::StgIsStorageFile( lpwFileName );  // 合文件

       if( FAILED(hr) )    return;

 

       hr = ::StgOpenStorage(                   // 开复合文件

              lpwFileName,               // 文件名称

              NULL,

              STGM_READ | STGM_SHARE_DENY_WRITE,

              0,

              0,

              &pStg);                           // 得到根存接口指

 

       IEnumSTATSTG *pEnum=NULL;  //

       hr = pStg->EnumElements( 0, NULL, 0, &pEnum );

       ASSERT( SUCCEEDED(hr) );

 

       STATSTG statstg;

       while( NOERROR == pEnum->Next( 1, &statstg, NULL) )

       {

              // statstg.type 保存着 STGTY_STREAM STGTY_STORAGE

              // statstg.pwcsName 保存着象名称

              // ...... 时间度等很多信息。请查 MSDN

 

              ::CoTaskMemFree( statstg.pwcsName );     // 放名称所使用的内存(注6

       }

      

       if( pEnum )   pEnum->Release();

       if( pStg )    pStg->Release();

}

六、小

  合文件,构化存,是微软组件思想的起源,在此基继续发展出了持性、命名、ActiveX象嵌入、现场激活......一系列的新技、新概念。因此理解和掌握 合文件是非常重要的,即使在你的程序中并没有全面使用件技合文件技也是可以独被用的。祝大家学社会主义软件事:-)

留作
......
1:写个小用程序,从 MSWORD doc 文件中,提取出附加信息(作者、公司......)。

2:写个全功能的合文件浏览编辑

1:踅摸(xuemo)动词,北方方言,找搜索的意思。
2不上网查资料学
答:玩笑!在那遥1995年代,我的500,不吃不喝正好100Internet网。
3OLE象的接与嵌入。
4:可以用 DFView.exe MSWORD DOC 文件合文件的浏览。但是程序并没有实现化,不能打中文文件名的合文件,因此需要改名后才能浏览
5CLSID,在后的文章中介
6 COM 中内存使用的问题,在后的文章中介

 

 

COM设计用(二)
GUID
接口

作者:

一、前言

  接上回话说 doc(Word) 合文件中,已解决了保存 xls(Excel) 数据的问题了。那,接下来又要解决另一个问题:当 WORD 程序合文件,遇到了 xls 数据的候,它如何启 Excel 呢?启后,又如何 Excel 自己去入、解析、 xls 数据呢?

二、CLSID 概念

  有一个非常简单的解决方案,那就是象数据的前面,保存有个数据的程序名。(左上)

一、CLSID 的概念

  的确是一个简单的方法,但同时问题也很重。在算机上,Excel 的路径是:"c:/office/Excel.exe",如果把 doc 文件制到李四算机上使用,而李四 Excel 的路径是:
"d:/Program files/Microsoft Office/Office/Excel.exe"
,完蛋了:-(
  于是,微想出了一个解决方案,那就是不使用直接的路径表示方法,而使用一个叫 CLSID(注1)的方式接描述象数据的理程序路径。CLSID 就是一个号,或者是一个16的数。察注册表(上),在HKCR/CLSID/{......}下,LocalServer32DLL件使用InprocServer32 中保存着程序路径名称。CLSID 构定如下:

typedef struct _GUID {

       DWORD Data1;  // 随机数

       WORD Data2;   // 时间

       WORD Data3;   // 时间

       BYTE Data4[8];      // 和网卡MAC

} GUID;

 

typedef GUID CLSID;  // ID

typedef GUID IID;    // 接口ID

#define REFCLSID const CLSID &

 

// 的声明和赋值方法

CLSID CLSID_Excel = {0x00024500,0x0000,0x0000,{0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x46}};

struct __declspec(uuid("00024500-0000-0000-C000-000000000046")) CLSID_Excel;

class DECLSPEC_UUID("00024500-0000-0000-C000-000000000046") CLSID_Excel;

// 注册表中的表示方法

{00024500-0000-0000-C000-000000000046}

  用一个号码间接表示程序名,的确是个 Good idea实现件位置的透明性,并方便地展出 DCOM件)。但,但,但,但.....CLSID 16个字128位二制数,干这么长的数字呀?遥想当年......在上幼儿园的候,人们设计 socket,用 TCP/IP 协议进行网个参与通算机都有一个4 IP 表示号地址,范 0,0,0,0 ~ 255,255,255,255 42亿个地址。可是没想到啊,没想到,自从 Internet 选择TCP/IP 协议后,42亿个地址就不全世界的劳动人民分配啦。除了劳动人民,有冰箱、彩电饭锅、手机、手提电脑......些都需要网呀。在公室通络开电饭锅给焖饭,下班回家后就能吃成的啦,多幸福呀?!(注:在我家老婆是领导,所以是我做。咳......
由于前,微软这设计 CLSID/IID 就使用了GUID概念的16个字下好啦,全世界60亿人口,个人分配10亿个号,那需要分配1800亿年。反正等到地球没有了都不会使用完的:-)

三、 CLSID

 

  1. 如果使用开发环件程序,IDE会自帮你 CLSID
  2. 你可以手工写 CLSID,但千万不要和人家已生成的 CLSID 呀,所以重地不推荐;(可是微CLSID都是手工写的,州官放火,不百姓点灯
  3. 程序中,可以用函数 CoCreateGuid() CLSID
  4. 使用工具 GUID(注2);

vc6.0版本运行:"vc/Common/Tools/GuidGen.exe"程序(你可以参照上回文章中介的方法,把个工具程序加到开发环境中,方便用)。vc.net版本,在菜工具/GUID”中,就可以行了。

四、ProgID 概念

  一个COM件都需要指定一个 CLSID,并且不能重名。它之所以使用16个字,就是要从概率上保不可能的。但是,(世界上就怕但是二字)微软为了使用方便,也支持另一个字符串名称方式,叫 ProgID(3)。注册表的ProgID 内容(注4)。由于 CLSID ProgID 是一个概念的两个不同的表示形式,所以我在程序中可以随便使用任何一。(有些人就是讨厌说话不算数。明明 GUID 的目的就是禁止重,但居然又允使用 ProgID?!ProgID 是一个字符串的名字,重的可能性就太大了呀。赶明儿我也写个程序,我打算个程序的 ProgID “Excel.Application”,嘿嘿)下面介一下 CLSID ProgID 转换方法和相的函数:

函数

功能

CLSIDFromProgID()CLSIDFromProgIDEx()

ProgID 得到 CLSID。没什的,你自己都可以写,注册表

ProgIDFromCLSID()

CLSID 得到 ProgID用者使用完成后要 ProgID 的内存(5)

CoCreateGuid()

随机生成一个 GUID

IsEqualGUID()IsEqualCLSID()IsEqualIID()

2ID是否相等

StringFromCLSID()StringFromGUID2()StringFromIID()

CLSID,IID 得到注册表中CLSID式的字符串,注意放内存

五、接口(Interface)的来

  到此,我知道了 CLSID ProgID 唯一地表示一个件服程序,那根据ID,就可以加运行件,并端程序提供服了。(启动组件程序的方法,会陆续)。接下来先讨论如何件提供的函数?-----接口。
  作端程序,它希望或者他要求:我的程序只写一次,然后不做任何修改就可以用任意一个件。例来

  1. 你可以在 Word 中嵌入 Excel,也可以嵌入 Picture,也可以嵌入任何第三方表的 ActiveX 文档......也就是 Word 自己都不知道使用它的人将会在 doc 里面插入什么东东
  2. 你可以在 HTML 文件中插入一个 ActiveX,也可以插入一个程序脚本Script......你自己写的插件也可以插入到 IE 境中。了完成你的功能, 绝对也不会去修改IE吧?!

  个要求在有点度,Office 开发停滞了。巧,一天老O(Office 目的工程)和小B(VB 目的工程)一起喝酒,老O向小B倾诉了他的烦恼
O:怎我写的程序C,可以用其它人写的程序S中的函数?(C表示客程序,S表示提供服的程序)
B:你是不是喝糊涂了?S作成 DLL,你去 LoadLibrary()GetProcAddress()...FreeLibrary()?!

O废话!要是这么简单就好了。问题是,我都不知道S程序是干什的?能干什?我怎么调用呀?
B:哦......个比,但我在不能告你,因我怕你印象不深。
O~·#%……—*......
B:是这样的,在VB中,我制定了一个准,准允任何一个VB开发者,把他自己写的某个功能的小程序放在VB的工具上,这样就好象他展了 VB 的功能一

O:哦?就是那个叫什 VBX 玩意儿?
B:我...... VBX 西不起眼儿,的确我也没看上它。但你猜怎着?在有成千上万的 VB 程序好者把他写的各式各功能的 VBX 小程序,放到网上,大家共享那。
O:哦~~~,那你 VBX 准是什
B:嘿嘿......简单,就是在 VBX 中必须实现7个函数,7个函数名称和功能必是:初始化、放、示、消息......,而至于它内部想干什,我也管不着。我只是在需要的用我需要的7个函数。
O:哦~~~这样......了,我有个急事,我先走了。88,你付......
B:喂!喂喂...... 这么急干什包都掉了
:-)
  老O包,仍然兴奋地冲回公室,他始了思考......

1、我的程序C,要能用任何人写的程序B。那B要按照我事先的要求,提供我需要的函数F1(),F2(),F3(),K1(),K2()
2
BASIC 是解释执行,因此它的函数不用考虑书序,只要出函数名,解器就能找到。但我使用的是 C++......
3
C++编译后的代中没有函数名,只有函数地址,因此我必进为VTAB(虚函数表)表示函数入口:


二、VTAB

4
好,需要改一下,因所有的函数地址都放在一个表中会不灵活、不好修改、不易展。恩,有了!按照函数功能的行分

三、多个 VTAB

5
问题又来了,在有2 VTAB 虚函数表,那从一个表找到另一个表那?恩又有法了,我要求你必实现一个函数,并且个函数地址必放在所有表的开头(表中的第一个函数指),个函数就叫 QueryInterface()吧,完成从一个表找到另一个表的功能:(除了QueryInterface()函数,便也完成另外两个函数,叫 AddRef() Release()两个函数的功能以后再

四、COM 接口

6
了以后描述方便,不再使用上四)的方法了,而使用这样简洁式:

五、COM 接口构的简洁图


六、接口(Interface)概念

1、函数是通 VTAB 虚函数表提供其地址, 从另一个角度来看,不管用什么语开发编译生的代都能生成个表。这样实现件的制特性实现件的跨言要求。
2
、假有一个指量保存着 VTAB 的首地址,则这量就叫接口指”(6) 量命名的候,习惯上加上"I"开头。另外了区分不同的接口,个接口 也都要有一个名字,名字就和 CLSID ,使用 GUID 方式,叫 IID
3
、接口一经发表,就不能再修改了。不然就会出向前兼容的问题个性接口不
4
件中必3个函数,QueryInterfaceAddRefRelease,它3个函数也成一个接口,叫"IUnknown"(7)
5
、任何接口,其都包含了 IUnknown 接口。随着你接触到更多的接口就会了更体会解到接口的另一个性承性

6
、在任何接口上,用表中的第一个函数,就是 QueryInterface()函数,就得到你想要的另外一个接口指个性接口的传递
7
C/C++言中需要事先函数声明,那 会要求件也必提供C言的文件。不行!了能使COM具有跨言的能力,决定不再任何言提供对应的函数接口声明,而是独立地提供一个叫TLB)的声明。言的IDE境自己去根据TLB生成自己言需要的包装。个性接口声明的独立性(注8


七、客程序与件之

  回到我的上一个话题Word中嵌入一个件,那Word是如何商使用件的那?下面是容器和件之的一个模拟对话过程:
 

 

容器 商部分

答部分

1

根据CLSID动组
CoCreateInstance()

生成象,行构造函数,行初始化作。

2

你有IUnknown接口

有,你!

3

恩,太好了,那你有IPersistStorage接口(9)
IUnknown::QueryInterface(IID_IPersistStorage...)

没有!

4

真差连这个都没有。那你有IPersistStreamInit接口(10)
IUnknown::QueryInterface(IID_IPersistStreamInit...)

哈,个有,

5

好,好,这还差不多。你我初始化吧。
IPersistStreamInit::InitNew()

OK,初始化完成了。

6

完成了?好!在你数据去吧。
IPersistStreamInit::Load()

完啦。我根据数据,已在窗口中示出来了。

7

好,在咱各自理用的鼠键盘消息吧......

......

8

哎呀!用要保存退出程序了。你的数据被用修改了
IPersistStreamInit::IsDirty()

改了,用修改啦。

9

那好,那修改后,你的数据需要多大的存呀?
IPersistStreamInit::GetSizeMax()

恩,我算算呀......好了,共需要500KB

10

,你这么个小玩意居然占用这么大空?!......好了,你可以存了。
IPersistStreamInit::Save()

谢谢,我已存好了。

11

恩。拜拜了您那。(11)
IPersistStreamInit::Release()
IUnknown::Release()

行析构函数,象。

12

我自己也退出了......
PostQuitMessage()

 

  容器(或者端)就是这样对话用的。如果件甲实现 IA 接口,那容器就会使用它,如果件乙没有提供 IA 接口,但是它提供了 IB 接口,那容器就会 IB 接口的函数......如此,容器程序根本就不需要知道件到底是干什的,件到底是用什么语开发的,件的磁位置到底在哪里,它都可以正常运行。太奇妙了!太精彩了!怎一个字了得!

八、小

  第二回中,介了两个非常重要的概念:CLSID Interface。由于全篇都是概念描述而没有示例程序相配合,可能者的理解不太深入、不底。着急,我们马上就要入到件程序设计阶段了,到那个候,你根据具体的程序代,再回过头来再次阅读本回文章,没懂?哦......!慢慢地您老人家就懂了:-)

留作
......
1
IDispatch 接口的 IID 是多少?(哎~~~ 笨笨,在源程序中,用鼠键执Go to definition 呀)

2
IPicture 接口有几个函数?功能是什?(玩了!你多大了?想不想在程序中 JPG 像呀,看 MSDN 去)
  想知道COM函数是返回 HRESULT ?想知道如何使用 BSTRVARIANT ?想知道 COM 应该如何使用内存?想知道如何使用 UNICODE ......~~~,我在不能告你,我在告你,怕你印象不深!且听下回分解......


1CLSID = Class ID 上回了把CLSID写入合文件的函数:WriteClassStg()IStorage::SetClass()
2GUID 全局唯一示符,CLSID/IID 是借用了GUID的概念。
3ProgID = Program ID,等价于 CLSID, 是用字符串表示的。
4:注册表子 ProgID VersionIndependentProgID 表示真正的 ProgID 和版本无 ProgID。比如在我算机上安装的 Excel,它的 ProgID = "Excel.Application.9",而 VersionIndependentProgID = "Excel.Application"
5COM 件的内存管理,的文章。
6Interface = 接口,以前微不叫它接口,而叫协议Protocol。其 认为这切一些。
7IUnknown 个名字起的好,居然叫我不知道”:-),它的 IID IID_IUnknown,如果用注册表式表示,那它的{00000000-0000-0000-C000-000000000046}
8TLB是由一个描述接口的文件 IDL 经过编译产生的。IDL 明,的文章吧。
9IPersistStorage 是用合文件的存(Storage)功能来保存/取数据用的一个接口。
10IPersistStreamInit 是用合文件的流(Stream)功能来保存/取数据用的一个接口。
11:拜拜了您那 = 北京,再

COM设计用(三)
数据

作者:

一、前言
  上回GUIDCLSIDIID和接口的概念。本回的重点是介 COM 中的数据型。咋不介绍组件程序的设计步骤呀?咳......着急,着急!孔子曰:要一口一口地吃;老子心急吃不了豆腐子云:走一看一” ...... 先掌握必要的知,将来写起程序来才会得心手也:-)
  走入正之前,大家牢牢住一条原COM 件是运行在分布式境中的。比如,你写了一个件程序(DLLEXE),那使用者可能是在本机的某个程内加载组件(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
  算机明后,了在算机中表示字符,人制定了一种编码,叫ASCIIASCII由一个字中的7(bit)表示,范0x00 - 0x 7F 128个字符。他为这128个数字就足表示abcd....ABCD....1234 些字符了。
  咳......的人就是!后来他突然发现,如果需要按照表格方式打印些字符的候,缺少了制表符。于是又展了ASCII的定,使用一个字的全部8(bit)来表示字符了,就叫ASCII。范0x00 - 0xFF 256个字符。
  咳......中文的人就是明!中国人利用连续2ASCII展区域(0xA0以后)来表示一个字,方法的准叫GB-2312。后来,日文、文、阿拉伯文、台湾繁体(BIG-5......都使用似的方法展了本地字符集的定一称 MBCS 字符集(多字字符集)。个方法是有缺陷的,因各个国家地区定的字符集有交集,因此使用GB-2312件,就不能在BIG-5境下运行(示乱),反之亦然。
  咳......的人一些了。了把全世界人民所有的所有的文字符号都编码,于是制定了UNICODE准字符集。UNICODE 使用2个字表示一个字符(unsigned shor intWCHAR_wchar_tOLECHAR)于好啦,全世界任何一个地区的件,可以不用修改地就能在另一个地区运行了。然我用 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是非常有意思的一个符号(TCHARLPCTSTRLPTSTR_T()_TEXT()...),它表示使用一间类型,既不明确表示使用 MBCS,也不明确表示使用 UNICODE。那到底使用哪字符集那?嘿嘿......编译候决定吧。置条件编译的方式是:VC6中,"Project/Settings.../C/C++卡片 Preprocessor definitions" 中添加或修改 _MBCS_UNICODEVC.NET中,"/属性/配置属性//字符集"然后用合窗选择。使用 T 型,是非常好的习惯重推荐!

四、BSTR
  COM 中除了使用一些简单标准的数据型外(注2),字符串型需要特重点地明一下。还记得原则吗COM 件是运行在分布式境中的。通俗地,你不能直接把一个内存指直接作参数传递给COM函数。你想想,系需要把这块内存的内容传递地球另一 算机上,因此,我至少需要知道你这块内存的尺寸吧?不然我如何传递呀?传递多少字呀?!而字符串又是非常常用的一种类型,因此 COM 设计者引入了 BASIC 中字符串型的表示方式---BSTRBSTR 是一个指针类型,它的内存构是:(入程序片段 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

OLE 2A

T 2A

W 2A

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

WOLE

字符串,也就是 UNICODE

T

间类T。如果定 _UNICODET表示W;如果定 _MBCST表示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++BASICJavaPascalScript......算机言多,而它各自又都有自己的数据型,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,你好");

学生:哦......我明白了。可是这么操作真的,有没有简单一些的方法?

:有呀,你可以使用成的包装 CComVariantCOleVariant_variant_t。比如上面三个问题就可以这样书写:CComVariant v1(100),v2(true),v3("Hello,你好"); 简单了吧?!(4)

学生:老,我再最后一个问题,我如何用 VARIANT 保存一个数

问题复杂,我在不能告你,我在告你怕你印象不深......(5)
学生:~!@#$%^&*()......


七、小
   
以上所介的内容,是基本功,必掌握。先到里吧,休息一会儿......更多精彩内容,敬请关注《COM 设计()


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

COM设计用(四)
简单调

作者:

一、前言

  同志、朋友、各位领导,大家好。
 

 

VCKBASE 不得了,

 

 

网友众多文章好。

 

 

设计学?

 

 

识库闷头找!

 

 

 

摘自---打油集

  在 VCKBASE 力支持下,在各位网友回帖的鼓励下,我才能利完成系列文的前三回。到本回,我们终始写代啦。写点那?恩,有了!咱先从如何成的简单始吧,同便介一些相的知


二、件的启

  在第三回中,大家用小本本记录了一个原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 内部,当然你可以随便使用任何型的内存分配放函数,但件如果需要与客户进行内存的交互,使用上表中的后三函数族。
  1BSTR 内存在上回中,已有比丰富的介了,不再重
  2CoTaskXXX()函数族,其本上就是C言的函数(malloc...);
  3IMalloc 接口又是 CoTaskXXX() 函数族的一个包装。包装后,同了一些功能,比如:IMalloc::GetSize()可以取得尺寸,使用 IMallocSpy 可以监视内存的使用;


四、参数传递方向

  在C言的函数声明中,尤其当参数候,你是看不出它传递方向的。比如:
void fun(char * p1, int * p2);
请问p1p2 哪个是入参?哪个是出参?甚或都是入参或都是出参?由于扯到内存分配和放等问题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_IPicture, ( LPVOID * )&pPicture );

       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:智能指的概念和用法,后
2IDL 文件,下回就要介啦。
3,想干什都可以,反正我不管啦。
4:聚合,也在第30回中介:-)
5:持性,IPersistXXXXXX是一个非常大的接口家族,后

6:想知道 IShellLinkIPersistFile接口的所有函数别愣着,快去看MSDN......

 

 

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值