-
COM
编程入门
<
/p>
本文的目的是为刚刚接触
COM
的程序员
提供编程指南,并帮助他们理解
COM
的基本概念。
内容包括
COM
规范简介,重要的
COM
术语以及如何重用现有的
COM
组件。本文不包括如
何编写自己的
COM
对象和接口。
< br>COM
即组件对象模型,
是
Co
mponent Object Model
取前三个字母的缩写,
< br>这三个字
母在当今
Windows
的世界中随处可见。
随时涌现出来的大把大把的新技术都以
C
OM
为基础。
各种文档中也充斥着诸如
COM
对象、接口、服务器之类的术语。因此,对于一个程序员来
说,不仅要掌握使用
COM
的方法,而且还要彻底熟悉
COM
的所有一切。
本文由浅入深描述
COM
p>
的内在运行机制,教你如何使用第三方提供的
COM
对象(以
Windows
外壳组件
< br>Shell
为例)。读完本文后,你就能掌握如何使用
W
indows
操作系统中
内建的组件和第三方提供的
COM
对象。
本文假设你精通
C++
语言。在例子代
码中使用了一点
MFC
和
ATL
,如果你不熟悉
MFC
和
ATL
也没关系,本文会对这些代码进行完全透彻的解释。
本文包括以下几个部分:
COM
p>
——到底是什么?
——
COM
标准的要点介绍,它被设计用来解决什么问题?
基
本元素的定义
——
COM
术语以及这些
术语的含义。
使用和处理
COM
p>
对象
——如何创建、使用和销毁
COM
p>
对象。
基本接口
——描述
IUnknown
基本接口及其方法。
掌握串的处理
——在
COM
代码中如何处理串。
应用
COM
技术——例子代码
,举例说明本文所讨论的
所有概念。
处理
HRESULT
p>
——
HRESULT
类型描述,如何监测错
误及成功代码。
COM
——
到底是什么?
简单地说,
COM
是一种跨应用和语言
共享二进制代码的方法。与
C++
不同,它提倡源
代码重用。
ATL
便是一个很好的例证。源码级重用
虽然好,但只能用于
C++
。它还带来了
名字冲突的可能性,更不用说不断拷贝重用代码而导致工程膨胀和臃肿。
Windows
使用
p>
DLLs
在二进制级共享代码。这也是
Wi
ndows
程序运行的关键
——
重用<
/p>
,
等。但
DL
Ls
是针对
C
接口而写的,它们只能被
C
或理解
C
调
用规
范的语言使用。由编程语言来负责实现共享代码,而不是由
DLLs
本身。这样的话
DLLs
的<
/p>
使用受到限制。
MFC
引入了另外一种
MFC
扩展
D
LLs
二进制共享机制。
但它的使用仍受限制
< br>——
只能在
MFC
程序中使用。
COM
通过定义二进制标准解决了这些问题,即
COM
明确
指出二进制模块(
DLLs
和
EXEs
)必须被编译成与指定的结构匹配。这个标准也确切规定了在内存中如何组织
COM
对
象。
COM
p>
定义的二进制标准还必须独立于任何编程语言(如
C++
中的命名修饰)。一旦满
足了这些条件,
就可以轻
松地从任何编程语言中存取这些模块。
由编译器负责所产生的二进
制代码与标准兼容。这样使后来的人就能更容易地使用这些二进制代码。
在内存中,
COM
对象的这种标准形式在
C++
虚函数中偶尔用到,
所以这就是为什么许
多
COM
代码使用
C++
的原因。
但是记住,
编写模块所用的语言是无关的,
因为结果二进制
代
码为所有语言可用。
此外,
COM
不是
Win32
特有的。从理论上讲,它可以被移植到
Unix
或其它
操作系统。
但是我好像还从来没有在
Windows
以外的地方听说过
COM
。
基本元素的定义
我们从下往上看。接口只不过是一组函数。这些函数被
称为方法。接口名字以大写的
I
开头,例如
C++
中的
IShellLink
,接口被设计成一个抽象基类,其中只有纯粹的虚拟函数。
接口可以从其它接口继承,这里所说的继承的原理就好像
C++
中的单继承。接口是不允
许多继承的。
coclass
(简称
组件对象类
——
component object clas
s
)
被包含在
DLL
< br>或
EXE
中,
并且
包含着一个或者多个接口的代码。组件对象类(
coclasss
)实现这些接口。
COM
对象在内存
中表现为组件对象类(
coclasss
)的一个实
例。注意
COM―
类
‖
和
C++―
类
‖
是不相同的,尽管
常常
COM
类实现的就是一个
C++
类。
COM
服务器是包含了一个
或多个
coclass
的二进制(
DL
L
或
EXE
)。
注册(
Registration
)是创建注册表入口的一个过程,告诉
Windows
操作系
统
COM
服务器
放在什么位置。取消注
册(
Unregistration
)则相反
< br>——
从注册表删除这些注册入口。
GUID
(谐音为
―fluid‖
,意思是全球唯一标示符
——
p>
globally
unique
ide
ntifier
)是个
128
位的数字
。
它是一种独立于
COM
编程语言的标
示方法。
每一个接口和
coclass
有一个
GUID
。
因为每一个
GUID
都是全球唯一的,所以避免了名字冲突(只要你用
COM API
创建它们)。
有时你还会碰到另一个
术语
UUID
(意思也是全球唯一标示符
——
universally
unique
identifier
)。
UUIDs
和
GUIDs
在实际使用时的用途是一样的。
类
ID<
/p>
或者
CLSID
是命名
< br>coclass
的
GUID
。接
口
ID
或者
IID
是命名接口的
GUID
。
在
COM
中广泛地使用
GUID
有两个理由:
1
、
GUIDs
只是简单的数字,任何编程语言都
可以对之进行处理。
2
、
GUIDs
可以在任何机器上被任何人创建,一旦完成创建,它就是唯一的
。因此,
COM
开
发人员可以创建自己
特有的
GUIDs
而不会与其它开发人员所创建的
GUIDs
有冲突。这样就
消除了集中授权发布
p>
GUIDs
的必要。
HRESULT
是
COM
用来返回
错误和成功代码的整型数字。除此之外,别无它意,虽然以
H
作
前缀,但没有句柄之意。下文会对它有更多的讨论。
最后,<
/p>
COM
库是在你使用
COM
时与你交互的操作系统的一部分,它常常指的就是
COM
本
身。但是为了避免混淆才分开描述的。
使用和处理
COM
对象
每一种语言都有其自己处理对象的方式。例如,
C++
是在栈中创建对象,或者用
new
p>
动
态分配。
因为
C
OM
必须独立于语言,
所以
COM
p>
库为自己提供对象管理例程。
下面是对
CO
M
对象管理和
C++
对象管理所做的一
个比较:
创建一个新对象
C++
中,用
new
操作符,或者在栈中创建对象。
COM
中,调用
COM
库中的
API
。
p>
删除对象
C++
中,用
delete
操作符,或将栈对象踢出。
COM
中,所有的对象保持它们自己的引用计数。调用者必须通知对象什么
时候用完这个
对象。当引用计数为零时,
COM
对象将自己从内存中释放。
由此可见,对象处理的两个阶段:创建和销毁,缺一不可。当创建
COM
对象时要通知
COM
库使用哪一个接口。如果
这个对象创建成功,
COM
库返回所请求接口的指针。然后通<
/p>
过这个指针调用方法,就像使用常规
C++
对象指针一样。
创建
COM
对象
<
/p>
为了创建
COM
对象并从这个对象获得接
口,必须调用
COM
库的
API
函数,
CoCreateInstance()
。其原型如下:
HRESULT
CoCreateInstance (
REFCLSID
rclsid,
LPUNKNOWN pUnkOuter,
DWORD dwClsContext,
REFIID riid,
LPVOID* ppv );
以下是参数解释:
rclsid
coclass
的
CLSID
,例如,可以传递
CLSID_ShellLink
创建一个
COM
对象来建立快捷方式。
pUnkOuter
这个参数只用于
COM
对象的聚合,利用它向现有的
coclass<
/p>
添加新方法。参数值为
null
表
示不使用聚合。
dwClsContext
表示所使用
COM
服务器的种类。本文
使用的是最简单的
COM
服务器,一个进程内
< br>(
in-process
)
DL
L
,
所以传递的参数值为
CLSCTX
_INPROC_SERVER
。
注意这里不要随意使
用
CLSCTX_ALL
(在
< br>ATL
中,它是个缺省值),因为在没有安装
DCOM<
/p>
的
Windows95
系统
上会导致失败。
riid
请求接口的
IID
。例如,可以传递
IID_IShellLink
获得
IShellLink<
/p>
接口指针。
ppv
接口指针的地址。
COM
库通过这个参数返回请求的接
口。
当你调用
CoCreateIn
stance()
时,它负责在注册表中查找
COM
服务器的位置,将服务器加
载到内存,并创建你所请求的
< br>coclass
实例。
以下是
一个调用的例子,创建一个
CLSID_ShellLink
对
象的实例并请求指向这个对象
IShellLink
接口指针。
HRESULT hr;
IShellLink* pISL;
hr = CoCreateInstance (
CLSID_ShellLink, // coclass
的
CLSID
NULL, //
不是用聚合
CLSCTX_INPROC_SERVER, //
服务器类型
IID_IShellLink, //
接口的
IID
(void**) &pISL ); //
指向接口的指针
if ( SUCCEEDED ( hr ) )
{
//
用
pIS
L
调用方法
}
else
{
//
不能创建
CO
M
对象,
hr
为出错代码
} <
/p>
首先声明一个接受
CoCreateInstance()
返回值的
HRESULT
和
< br>IShellLink
指针。调用
CoCreateIn
stance()
来创建新的
COM
对
象。如果
hr
接受到一个表示成功的代码,则
< br>SUCCEEDED
宏返回
TRUE
,
否则返回
FALSE
。
FAILED
是一个与
SUCCEEDED
p>
对应的宏用来
检查失败代码。
删除
COM
对象
前面说过,你不用释放
COM
对象,只要告诉它们你已经用完对象。
IUnknown
是每一个
COM
对象必须实现的接口,它有一
个方法,
Release()
。调用这个方法通知
COM
对象你不
再需要对象。一旦调用了这个方法之
后,就不能再次使用这个接口,因为这个
COM
对象可
能从此就从内存中消失了。
如果你的应用程序使用许多不同的
COM
对象,
因此在用完某个接口后调用
Release()
就
显得非常重要。如果你不释放接口,这个
COM
对象
(包含代码的
DLLs
)将保留在内存中,
这会增加不必要的开销。
如果你的应用程序要长时间运行,
就应该在应用程序处于空闲期间
调用
CoFreeUnused
Libraries() API
。这个
API
将卸载任何没有明显引用的
COM
服务器,因
此这也降低了应用程序使用的内存开销。
继续用上面的例子来说明如何使用
Release()
:
//
像上面一样创建
COM
对象,
然后,
if ( SUCCEEDED ( hr ) )
{
//
用
pISL<
/p>
调用方法
//
通知
COM
对象不再使用它
pISL->Release();
}
接
下来将详细讨论
IUnknown
接口。
基本接口
——
< br>IUnknown
每
一个
COM
接口都派生于
IUnkno
wn
。
这个名字有点误导人,
其中没有
未知
(
Unknown
)
接口的意思。它的原意是如果有一个指向某
COM
对
象的
IUnknown
指针,就不用知道潜在
< br>的对象是什么,因为每个
COM
对象都实现
IUnknown
。
IUnknown
有三个方法:
AddRef()
–
通知
CO
M
对象增加它的引用计数。
如果你进行了一次接口指针的拷贝,
就必须
调用一次这个方法,并且原始的值和拷贝的值两者都要用
到。在本文的例子中没有用到
AddRef()
方法。
Release()
–
通知
CO
M
对象减少它的引用计数。参见前面的
Release()
p>
示例代码段。
QueryInterface()
–
从
COM
对象请求一个接口指针。
当
cocla
ss
实现一个以上的接口时,
就
要用到
这个方法。
前
面
已
经
看
< br>到
了
Release()
的
p>
使
用
,
但
如
何
使
用
QueryInterface()
呢
?
当
你
用
CoCreate
Instance()
创建对象的时候,你得到一个返回的接口指针。如果这个
COM
对象实
现一个以上的接口(不包括
IUnknown
),你就必须用
Query
Interface()
方法来获得任何你
需要的附加的接口指
针。
QueryInterface()
的原型如下:
HRESULT
IUnknown::QueryInterface (
REFIID
iid,
void** ppv );
以下是参数解释:
iid
所请求的接口的
IID
。
ppv
接口指针的地址,
Q
ueryInterface()
通过这个参数在成功时返回这个接口。
让我们继续外壳链接的例子。它实现了
IShellLink
和
IPersistFile
接口。如
果你已经有一个
IShellLink
指针,
< br>pISL
,可以从
COM
对象请
求
IPersistFile
接口:
HRESULT hr;
IPersistFile*
pIPF;
hr = pISL->QueryInterface (
IID_IPersistFile, (void**) &pIPF );
然后使用
SUCCEEDED
宏检查
hr
的值以确定
QueryInterface()
的调用情况,如果成功的
< br>话
你
就
可
以
象
使
用
其
它
接
口
指
p>
针
那
样
使
用
新
的
接
口
指
针
,
< br>pIPF
。
但
必
须
记
住
调
用
pIPF->Release()
通知
< br>COM
对象已经用完这个接口。
仔细做好串处理
这一部分将花点时间来讨论如何在
COM
代码中处
理串。如果你熟悉
Unicode
和
ANSI
,
并知道如何对它们进行转换的话,
< br>你就可以跳过这一部分,
否则还是读一下这一部分的内容。
不管什么时候,只要
CO
M
方法返回一个串,这个串都是
Unicode
串(这里指的是写入
COM
规范的所有方法)。
Unicode
是一种字符编码集,类似
AS
CII
,但用两个字节表示一个
字符。如果你想更好地控制或操
作串的话,应该将它转换成
TCHAR
类型串。
TCHAR
和以
_t
开头的函数
(如
< br>_tcscpy()
)
被设计用来让你用相同的源代码处
理
Unicode
和
ANSI
串。在大多数情况下编写的代码都是用来处理
ANSI
< br>串和
ANSI
WindowsAPIs
,所
以在下文中,
除非另外说明,
我所说的字符
/
串都是指
TCHA
R
类型。
你应该熟练掌握
TCHAR<
/p>
类型,尤其是当你阅读其他人写的有关代码时,要特别注意
TCH
AR
类型。
当你从某个
COM
方法返回得到一个
< br>Unicode
串时,
可以用下列几种方法之一将它转换
成
char
类型串:
1
、调用
WideCharToMultiByte()
API
。
2
、调用
CRT
函数
wcstombs()
。
3
、使用
CString
构造器或赋值操作
(
仅用于
MFC )
。
4
、使用
ATL
串转换宏。
WideCharToMultiByte()
你可以用
WideCharT
oMultiByte()
将一个
Unicode
串转换成一个
ANSI
串。此函数的原型
如下:
int
WideCharToMultiByte (
UINT
CodePage,
DWORD dwFlags,
LPCWSTR lpWideCharStr,
int cchWideChar,
LPSTR
lpMultiByteStr,
int
cbMultiByte,
LPCSTR lpDefaultChar,
LPBOOL lpUsedDefaultChar );
以下是参数解释:
CodePage
Unicode
字符转换成的代码页。你可以传递
CP_ACP
来使用当前的
ANSI
代码页。代码页是
256
个字符集。字符
0
——
127
与
ANSI
编码一样。字符
128
< br>——
255
与
ANSI
字符不同,它
可以包含图形字符或者读音符号。
每一种语言或地区都有其自己的代码页,
所以使用正确的
代码页
对于正确地显示重音字符很重要。
dwFlags
dwFlags
确定
Windows
如何处理
―
复合
‖ Unicode
字符,它是一种后面带读音符号的字符。
如
è
就是一个复合字符。如果这些字符在
CodePage
参数指定的代码页中,不会出什么事。
否则
,
Windows
必须对之进行转换。
传递
WC_COMPOSITECHECK
使得这个
API
检查非映射复合字符。
传递
WC_SEPCHARS
使
得
Windows
将字符分为两段,即字符加读音,如
e`
。
传递
WC_DISCARDNS
使得
Windows
丢弃读音符号。
传递
WC_DEFAULTCHAR
使得
Windows<
/p>
用
lpDefaultChar
参数中说
明的缺省字符替代复
合字符。
缺省行
为是
WC_SEPCHARS
。
lpWideCharStr
要转换的
Unicode
串。
cchWideChar
lpWideCharStr
p>
在
Unicode
字符中的长度。通常传
递
-1
,表示这个串是以
0x00
p>
结尾。
lpMultiByteStr
接受转换的串的字符缓冲
cbMultiByte
lpMultiByteStr<
/p>
的字节大小。
lpDefaultChar
可选
——
当
dwFlags
包含
WC_COMPOSITECHECK
|
WC
_DEFAULTCHAR
并且某个
Unicode
字符不能被映射到同等的
ANSI
串时所传递的一
个单字符
ANSI
串,包含被插入的
―
缺省
‖
字
符。
可以传递
NULL
,让
API
使用系统缺省字符(一种写法是一个问号)。
lpUsedDefaultChar
可选
——
指向
BOOL
类型的一个
指针,设置它来表示是否缺省字符曾被插入
ANSI
串。可以<
/p>
传递
NULL
来忽略这个参数。
我自己都有点晕菜了
……!
< br>,万事开头难啊
……
,不搞清楚这些东西就很难搞清楚<
/p>
COM
的串
处理。
何况文档中列出的比实际应用的要复杂得多。
下面就给出了如何使用这个
API
的例子:
//
假设已经有了一个
Unicode
串
wszSomeString...
char szANSIString [MAX_PATH];
WideCharToMultiByte (
CP_ACP, // ANSI
代码页
WC_COMPOSITECHECK, //
检查重音字符
wszSomeString, //
原
Unicode
串
-1, // -1
意思是串以
0x00
结尾
szANSIString, //
目的
char
字符串
sizeof(szANSIString), //
缓冲大小
NULL, //
肥缺省字符串
NULL ); //
忽略这个参数
调用这个函数后,
p>
szANSIString
将包含
Unic
ode
串的
ANSI
版本。
wcstombs()
这个
CRT
< br>函数
wcstombs()
是个简化版,但它终结了
p>
WideCharToMultiByte()
的调用,所
以最终结果是一样的。其原型如下:
size_t wcstombs (
char*
mbstr,
const wchar_t* wcstr,
size_t count );
以下是参数解释:
mbstr <
/p>
接受结果
ANSI
串的字符(
char
)缓冲。
wcstr
要转换的
Unicod
e
串。
count
mbstr
参数所指的缓冲大小。
wcstombs()
在它对
WideCharToMultiByte()
的调用中使用
WC_COMPOSITECHECK |
WC_SEPCHARS<
/p>
标志。用
wcstombs()
转换前面
例子中的
Unicode
串,结果一样:
wcstombs ( szANSIString,
wszSomeString, sizeof(szANSIString) );
CString
MFC
中的
CString
包含有构造函数和接受
Unicode
串的赋值操作,
p>
所以你可以用
CString
来实现转换。
例如:
//
假设有一个
Unicode
串
ws
zSomeString...
CString str1
( wszSomeString ); //
用构造器转换
CString str2;
str2 = wszSomeString; //
用赋值操作转换
ATL
宏
ATL
有一组很方便的宏用于串的转换。
W2A()
用于将
Unicode
串
转换为
ANSI
串
(记忆
方法是
―wide to ANSI‖——
宽字符到
ANSI
)。实际上使用
OLE2A(
)
更精确,
―OLE‖
表示的意
思是
COM
串或者
OLE
串。下面是使用这些宏的例子:
#include
//
还是假设有一个
Unicode
串
wszSomeString..
.
{
char
szANSIString [MAX_PATH];
USES_CONVERSION; //
声明这个宏要使用的局部变量
lstrcpy ( szANSIString,
OLE2A(wszSomeString) );
}
<
/p>
OLE2A()
宏
―
返回
‖
转换的串的指针,但转换的串被存储在某个临时栈变
量中,所以要用
lstrcpy()
来获得自己的拷贝。其它的
几个宏是
W2T()
(
Unicode
到
TCHAR
)以及
W2CT()
(
Unicod
e
到常量
TCHAR
串)。
有个宏是
OLE2CA()
< br>(
Unicode
到常量
cha
r
串),可以被用到上面的例子中,
OLE2CA()
实际
上是个更正宏,因为
lstrcpy()<
/p>
的第二个参数是一个常量
char*
,关
于这个问题本文将在以后
作详细讨论。
另一方面,
如果你不想做以上复杂的串处理,
尽管让它还保持
为
Unicode
串,
如果编写的是<
/p>
控制台应用程序,输出
/
显示
Unicode
串时应该用全程变量
std::wcout
,如:
wcout << wszSomeString;
但是要记住,
std::wcout
只认
Unicode
,所以你要是
p>
―
正常
‖
串的话,
还得用
std::cout
输出
/
p>
显示。对于
Unicode
串文字量,要使
用前缀
L
标示,如:
wcout << L
如果保持串为
Unicode
,编程时有两个限制:
——
必须使用
wcsXXX() Unicode
< br>串处理函数,如
wcslen()
。
——
在
Windows
9x
环境中不能在
Windows API
中传递
Unicode
串。要想编写能在
< br>9x
和
NT
< br>上都能运行的应用,必须使用
TCHAR
类型,详情请参
考
MSDN
。
用例子代码总结上述内容
下面用两个例子演示本文所讲的
COM
概念。代码中还包含了本文的例子工程。
使用单接口
COM
对象
第一个例子展示的是单接口
C
OM
对象。
这可能是你碰到得最简单的例子。
< br>它使用外壳中
的活动桌面组件对象类(
CLSID_ActiveDesktop
)来获得当前桌面墙纸的文件名。请
确认系
统中安装了活动桌面(
Active
Desktop
)。
以下是编程步骤:
初始化
COM
库。
(
Initialize
)
创建一个与活动桌面交互的
COM
对象,并取得
IActiveDesktop
接口。
调用
COM
对象的<
/p>
GetWallpaper()
方法。
如果
GetWallpaper()
成
功,则输出
/
显示墙纸文件名。
p>
释放接口(
Release()
)。
收回
COM
库(<
/p>
Uninitialize
)。
WCHAR wszWallpaper
[MAX_PATH];
CString strPath;
HRESULT hr;
IActiveDesktop*
pIAD;
// 1.
初始化
COM
库(让
Windows<
/p>
加载
DLLs
)。通常是在程序的
InitInstance()
中调用
// CoInitialize ( NULL )
或其它启动代码。
MFC
程序使用
Af
xOleInit()
。
CoInitialize ( NULL );
// 2.
使用外壳提供的活动桌面组件对象类创建
COM
对象。
//
第四个参数通知
COM
需要什么接口
(
这里是
IActiveDesktop).
hr
= CoCreateInstance ( CLSID_ActiveDesktop,
NULL,
CLSCTX_INPROC_SERVER,
IID_IActiveDesktop,
(void**) &pIAD );
if (
SUCCEEDED(hr) )
{
// 3.
如果
COM
对象被创建成
功,则调用这个对象的
GetWallpaper()
方法。
hr =
pIAD->GetWallpaper ( wszWallpaper, MAX_PATH, 0 );
if ( SUCCEEDED(hr)
)
{
// 4.
如果
GetWallpaper()
成功,则输出它返回的文件名字。
//
注意这里使用
wcout
来显示
Unicode
串
wszWallpaper. wcout
是
//
Unicode
专用,功能与
cout.
相同。
wcout <<
L
endl;
}
else
{
cout << _T(
}
// 5.
释放接口。
pIAD->Release();
}
else
{
cout
<< _T(
}
// 6.
收回
COM
库。
MFC
程序不用这一步,它自动完成。
CoUninitialize();
在这个例子中,输出<
/p>
/
显示
Unicode
串
wszWallpaper
用的是
std::wcout
。
使用多接口的
COM<
/p>
对象
第二个例子展示了如何使用一个提供单接口的
COM
对象
QueryInterface()
函数。其中的
代码用外壳的
Shell Link
组件对象类创建我们在
第一个例子中获得的墙纸文件的快捷方式
以下是编程步骤:
初始化
COM
库。
创建一个用于建立快捷方式的
COM
对象并取得
IShellLink
接口。
调用
IShellLink
接口的<
/p>
SetPath()
方法
调用对象的
QueryInterface()
函数
并取得
IPersistFile
接口。
调用
IPersistFile
接口的
Save()
方法。
释放接口
收回
COM
库
CString sWallpaper =
wszWallpaper; //
将墙纸路径转换为
ANSI
IShellLink* pISL;
IPersistFile* pIPF;
// 1.
初始化
COM<
/p>
库
(
让
Wind
ows
加载
DLLs).
通常在<
/p>
InitInstance()
中调用
// CoInitialize ( NULL
)
或其它启动代码。
MFC
程序使用
AfxOleInit()
。
CoInitialize ( NULL );
// 2.
使用外壳提供的
Shell Link
组件对象类创建
COM
对象。
.
//
第四个参数通知
COM
需要什么接口
(
这里是
IShellLink)
p>
。
hr = CoCreateInstance ( CLSID_ShellLink,
NULL,
CLSCTX_INPROC_SERVER,
IID_IShellLink,
(void**) &pISL );
if (
SUCCEEDED(hr) )
{
// 3.
设置快捷方式目标
(
墙纸
文件
)
的路径。
hr = pISL->SetPath ( sWallpaper
);
if (
SUCCEEDED(hr) )
{
// 4.
获取这个对象的第二个
接口
(IPersistFile)
。
hr = pISL->QueryInterface (
IID_IPersistFile, (void**) &pIPF );
if ( SUCCEEDED(hr) )
{
// 5.
调用
Save()
方法保存某个文件得快捷方式。第一个参数是
// Unicode
串。
hr = pIPF->Save ( L
// 6a.
释放
IPersistFile
接口。
pIPF->Release();
}
}
// 6.
释放
IShellLink
接口。
pISL->Release();
}
//
输出错误信息部分这里省略。
// 7.
收回
COM
库。
MFC
程序不用这一步,它自动完成。
CoUninitialize();
处理
HRESULT
这一部分准备用
SUCCEEDED
和
FAILED
宏进行一些简单的出错处理。主要是深入研究
从
COM
p>
方法返回的
HRESULT
,以便达到完全
理解和熟练应用。
HRESULT
是个
32
位符号整数,
其非负值表示成功,负值表示失败。
HRESULT
有三
个域:程度位(表示成功或失败),功能码和状态码。功能码表示
HR
ESULT
来自什么组件
或程序。微软给不同的组件多赋予功能
码,如:
COM
、任务调度程序等都有功能码。功能
码是个
16
位的值,仅此而已,没有其它内在含义
;它在数字和意义之间是随意关联的;类
似
GetLastEr
ror()
返回的值。
p>
如果你在
winerror.h
头文件中查
找错误代码,
会看到许多按照
[
功能<
/p>
]_[
程度
]_[
描述
]
命名
规范列出的
HRESULT
值,由组件返回的通用的
HRESU
LT
(类似
E_OUTOFMEMORY
)在名
字中没有功能码。如,
REGDB_E_READREGDB:
功能码
=
REGDB,
指
―
< br>注册表数据库(
registry
database<
/p>
)
‖
;程
度
p>
= E
意思是错误(
< br>error
);描述
=
READREGDB
是对错误的描述(意思是不能读注册
表数
据库)。
S_OK:
没有功能码
——
通用(
generic
)
HRESULT
;程度
=S
;表示成功(
success
);
OK
是
状
态描述表示一切都好(
everything's
OK
)。
好在有一种比察看
winerror.h
文件更容易的
方法来确定
HRESULT
的意思。使用
VC
提
供的错误查找工具(
Erro
r Lookup
)可以轻松查到为
HRESULT
内建功能码。例如,假设你
在
CoCreateI
nstance()
之前忘了调用
CoInitialize(
)
。
CoCreateInstance()
< br>返回的值是
0x800401F0
。你只要将这个值输入
到错误查找工具按
―Look Up‖
按钮,便可以看到错误信
息
描述
―
尚未调用
CoInitialize‖
如下图所示:
另外一种查找
HRESULT
描述的方法是在调试器中。假设有一个
HRESULT
变量是
hres
。在
Watc
h
窗口的左边框中输入
―hres,hr‖
,表示想要看的值,
―hr‖
便会通知
VC
显示
HRESULT
所描述的
值。如下图所示:
通过以上的讨论,想必你对
COM
编程有了初步
的认识,本文第二部分将探讨
COM
的内
部机制。教你如何用
C++
编写自己的接口。