2015-10-22 10:24
在第八章学习Aggregation的时候搞晕了,所以就建了一个Workspace名为"COMAggregationDemo"来调试一下,
使用的源码是书光盘目录SETUP\CODE\CHAP08\AGGREGATE
下载地址:http://pan.baidu.com/s/1kTgvBgV
在COMAggregationDemo中我创建了四个Project,分别是"Client","Cmpnt1","Cmpnt2","Common"
其中Client是Win32 EXE项目,Cmpnt1和Cmpnt2是Win32 DLL项目,Common是Win32 LIB项目
配置好项目依赖关系:
Client < Cmpnt1, Cmpnt2, Common
Cmpnt1 < Common
Cmpnt2 < Common
把Client配置为启动项目。
注意查看Client,Cmpnt1,Cmpnt2的项目属性,因为它们都需要Common项目中的头文件以及生成的LIB文件。
具体的配置过程就不赘述。
Common生成的LIB文件包含两个COM组件的GUID的定义。以及若干辅助函数用于COM组件的注册。
大概情况就是这样。下面来说我通过调试COMAggregationDemo得到的对Aggregation的理解。
----------------------------------------------------------
为了方便后面讨论,这里给出 Aggregation 一个形式化的定义:
设组件 CA 支持接口集 S = {I1, I2, ..., In | n > 1} , 其中 T = { I1, I2, ..., Ik | (0<k<n) }
是由 CA 未借助其他组件实现的,任何属于集合 T 的接口都可以通过 CA::QueryInterface 直接查询到并返回
给用户。
若用户通过 CA 查询接口集 S - T 中的接口,则 CA::QueryInterface 把查询转发给 CA 内部创建的 CB 组件
并把结果返回给用户。
若用户通过 CA 查询接口集 S 以外的接口, CA::QueryInterface 返回 E_NOINTERFACE。
则称 CA 为聚合组件, CB 为被聚合组件。
聚合的目标:不能让用户知道 CB 的存在。
----------------------------------------------------------
要实现Aggregation,不是简单的让 CA::QueryInterface 把所有对集合 T 中接口的查询转发给 CB 就可以的。
会出现一个问题
设想,如果 S = { IX, IY, IZ }, T = { IX } , 此时我们通过 CA::QueryInterface 查询 IID_IY,
CA::QueryInterface 调用 CB::QueryInterface 得到 IY 的指针 pIY ,
然后调用 pIY->QueryInterface(IID_IX, (void**)&pIX) 你会发现查询不到 IX , 即 pIX 会为 NULL ,
原因很简单,因为 pIY->QueryInterface 是由 CB 实现的,而 CB 只实现了 IY 和 IZ , 当然查不到 IX 。
这违背了COM标准对QueryInterface的定义之一:设组件C实现了接口集{I1,I2,...,In},则通过
任意Ix(x属于[1,n])可以查询到任意Iy(y属于[1,n])。
另外, 假设 CB 实现的集合为 W = { IY, IZ, IA } ,
则 pIY->QueryInterface(IID_IA, (void**)&pIA) 能够查询到IA,
但是 pIX->QueryInterface(IID_IA, (void**)&pIA) 却查询不到 IA (因为调用的是CA::QueryInterface),
这又违背了上面所述的定义。
导致违背 QueryInterface 定义的原因在哪里? 原因就在于这里有2个实现不同的 QueryInterface 造成了麻烦。
一个是 CA::QueryInterface ,另一个是 CB::QueryInterface。 如果用户拿到的是 IX ,
则使用的就是 CA::QueryInterface , 用户能查询到的接口就有 { IX, IY, IZ } ;
若用户拿到的是 IY 或者 IZ , 则使用的就是 CB::QueryInterface , 用户能查询到
的接口就有 { IY, IZ, IA } 。 要知道 CA::QueryInterface 如果查询 IID_IA 是返回
E_NOINTERFACE , 但用户若拿着 IY, IZ 调用 QueryInterface 查询 IID_IA , 返回的
就是 S_OK 。虽然我们作为 CA 的开发者知道 IY, IZ 调用的并不是 CA::QueryInterface ,
但用户不是我们,用户不需要知道这些细节,用户所看见的就是:组件 CA 某些接口(IY, IZ)能
查询到 IA , 但某些接口(IX)查询不到 IA 。这就违反了 QueryInterface 另一层定义:
若能够从组件C某一个接口I获取接口Ix,则从组件C的任何接口都能获取接口Ix
----------------------------------------------------------
因此,假设组件 CX 支持 Aggregation,那它就要确保上述问题不会出现。不论组件 CX 是否支持 Aggregation ,
它都必须通过其类厂的 IClassFactory::CreateInstance 明确的告诉 Client "支持" 或 "不支持" 或者 "失败"。注意
这里 "失败" 和 "不支持" 是两码事,"失败" 的隐含含义是 "我支持 Aggregation ,但是调用者没有按正确
的方式创建我", 也就是下面要说到的 "Aggregation 约定"
COM定义IClassFactory::CreateInstance的第一个参数是 pUnknownOuter ,如果 pUnknownOuter 不为 NULL
则说明调用者希望利用此组件做 Aggregation 。如果组件 CA 利用组件 CB 做 Aggregation ,
那么 CA 唯一会用到的 CB 实现的接口就是 CB::IUnknown , CA 把接口集 T 的查询请求通过 CB::IUnknown
转发给 CB 。
所以,聚合组件(此处是 CA )和被聚合组件( 此处是 CB )之间对 Aggregation 有一个约定:
当且仅当传给 IClassFactory::CreateInstance 的 pUnknownOuter != NULL 且 IID == IID_IUnknown 时,
认为调用者希望利用被创建的组件做 Aggregation 。
----------------------------------------------------------
通过调试COMAggregationDemo可以看出CB的实现是如何解决上面所说的问题的。
下面是我创建的所有Breakpoints:
Client.cpp
_tmain
CMPNT1.cpp
DllGetClassObject
CFactory::CreateInstance
CA::QueryInterface
CA::Init
CMPNT2.cpp
DllGetClassObject
CFactory::CreateInstance
CB::NondelegatingQueryInterface
CB::QueryInterface
可以看出,这些Breakpoints的目的就是查看组件CA和组件CB的创建到查询获取interface的过程。
我对Client还有Cmpnt2的源码进行了修改从而更全面的反映Aggregation是如何实现的。
首先,我让CB实现了IZ,其次,我让Client增加了通过IY对IZ的查询。
----------------------------------------------------------
CB 对 Aggregation 的支持:
1.
CB 有一个成员 m_pUnknownOuter , 在 CB 没有被聚合的时候, m_pUnknownOuter 指向的就是
CB 自身。 否则 m_pUnknownOuter 指向的就是聚合的组件 CA 。
2.
CB 有 2 个版本的 QueryInterface , 一个就叫 QueryInterface ,另一个叫 NondelegatingQueryInterface
QueryInterface 直接调用 m_pUnknownOuter->QueryInterface ,
而 NondelegatingQueryInterface 就是走的正常的查询流程,可以查询到 IUnknown, IY, IZ 。
但是在查询 IUnknown 的时候 NondelegatingQueryInterface 通过
*ppv = static_cast<INondelegatingUnknown*>(this) 把 CA 实例转为 INondelegatingUnknown 实例并
存到 CA::Init 里的 IUnknown 指针 pUnknownInner 中 , 注意 INondelegatingUnknown 是没有继承 IUnknown 的,
这样做的结果就是, pUnknownInner 指向的 vtbl 如下
[PTR1]
[PTR2]
[PTR3]
...
其中 PTR1 是 NondelegatingQueryInterface 的地址
PTR2 是 NondelegatingAddRef 的地址
PTR3 是 NondelegatingRelease 的地址
要知道, COM 是二进制标准 , COM 的目的是二进制组件的重用, 所以 COM 中, 对 interface 函数的调用
依赖的不是函数的名称, 而是函数地址在 vtbl 中的位置。 在 COM 标准中, vtbl 就是一个内存块,里面
装有某个 interface 的函数的地址。
因此在这种情况下, 你使用 pUnknownInner->QueryInterface 调用到的实际上是 NondelegatingQueryInterface
也就是说,在COM标准下,编译器看到的 m_pUnknownInner ->QueryInterface实际上的含义是
"因为m_pIUnknownInner的类型是IUnknown*,根据IUnknown的结构,因为QueryInterface是其vtbl的第一项,
因此使用vtbl第一项的值作为函数指针,进行函数调用",
但是前面我们说过,因为CB构造函数把this指针强制转换为INondelegatingUnknown *之后,
再传给 m_pUnknownInner ,所以这里的vtbl实际上是 INondelegatingUnknown的vtbl了,
因此会调用到NondelegatingQueryInterface
3.
CB::CB 也利用了同样的方式使得 m_pUnknownOuter 在没有聚合的情况下指向 INondelegatingUnknown 的 vtbl
4.
QueryInterface 调用 m_pUnknownOuter->QueryInterface 即 CA 的 QueryInterface .
对 AddRef 和 Release 也是类似的情况 , 对应有 NondelegatingAddRef 和 NondelegatingRelease .
CA 对 Aggregation 的支持
1.
CA 有一个成员 m_pUnknownInner , 用来保存 CB 传给他的 IUnknown , 当用户请求接口集 T
中的接口的时候 , CA 就用 m_pUnknownInner 来查询 . 否则查询就由 CA 自己的 QueryInterface
完成 .
2.
CA::Init 调用 CB 的 IClassFactory::CreateInstance 获取 INondelegatingUnknown 的 vtbl ,
通过传给 CreateInstance IID_IUnknown 以及 pUnknownOuter 实现 .
好, 现在我们给出三个情景, 看看上面 CA 和 CB 如何协调工作的:
CA 支持 IX, IY. CB 支持 IY, IZ. 即 S = { IX, IY } T = { IX }
1. CB 没被 CA 聚合, 请求 IY, 用 IY 请求 IZ, 再用 IZ 请求 IY
调用栈如下
(
方便起见使用描述性的伪代码, 因为两个组件内部用了相同的类名, 会引起歧义,
所以有歧义的地方用文件名作前缀来区分, 另外省略了一些非关键性的代码
)
1 Client::CoCreateInstance(CLSID_Component2, IID_IY, pIY) 2 CMPNT2::DllGetClassObject(CLSID_Component2, IID_IY, pIY) 3 CMPNT2::CFactory::CreateInstance(pUnknownOuter == NULL, IID_IY, pIY) 4 CB::CB(pUnknownOuter == NULL) 5 m_pUnknownOuter = this 6 CB::NondelegatingQueryInterface(IID_IY, pIY) 7 让pIY指向IY实例 8 return 9 return 10 return 11 return 12 return 13 Client::pIY->QueryInterface(IID_IZ, pIZ) 14 CB::QueryInterface(IID_IZ, pIZ) 15 m_pUnknownOuter->QueryInterface(IID_IZ, pIZ) 16 CB::NondelegatingQueryInterface(IID_IZ, pIZ) // 看"CB对Aggregation的支持第3段" 17 让pIZ指向IZ实例 18 return 19 return 20 return 21 return 22 Client::pIZ->QueryInterface(IID_IY, pIY2) 23 CB::QueryInterface(IID_IY, pIY2) 24 m_pUnknownOuter->QueryInterface(IID_IY, pIY2) 25 CB::NondelegatingQueryInterface(IID_IY, pIY2) // 看"CB对Aggregation的支持第3段" 26 让pIY2指向IY实例 27 return 28 return 29 return 30 return 31 Client::pIY->Release() 32 Client::pIZ->Release() 33 Client::pIY2->Release()
2. CB 被 CA 聚合 , 通过 CA 请求 IX, 通过 IX 请求 IY , 通过 IY 请求 IX, 通过 IY 请求 IZ, 通过 IX 请求 IZ
调用栈如下:
1 Client::CoCreateInstance(CLSID_Component1, IID_IX, pIX) 2 CMPNT1::DllGetClassObject(CLSID_Component1, IID_IX, pIX) 3 CMPNT1::CFactory::CreateInstance(IID_IX, pIX) 4 CA::Init 5 CoCreateInstance(CLSID_Component2, pUnknownOuter == this, IID_IUnknown, m_pUnknownInner) 6 CMPNT2::DllGetClassObject(CLSID_Component2, IID_IUnknown, m_pUnknownInner) 7 CMPNT2::CFactory::CreateInstance(pUnknownOuter != NULL, IID_IUnknown, m_pUnknownInner) 8 CB::CB(pUnknownOuter != NULL) 9 m_pUnknownOuter = pUnknownOuter (即CA实例) 10 CB::NondelegatingQueryInterface(IID_IUnknown, m_pUnknownInner) 11 让m_pUnknownInner指向INondelegatingUnknown的vtbl // 参考"CB对Aggregation的支持第2段" 12 return 13 return 14 return 15 return 16 return 17 CA::QueryInterface(IID_IX, pIX) 18 令pIX指向IX实例 19 return 20 return 21 return 22 return 23 return 24 Client::pIX->QueryInterface(IID_IY, pIY) 25 CA::QueryInterface(IID_IY, pIY) 26 m_pUnknownInner->QueryInterface(IID_IY, pIY) 27 CB::NondelegatingQueryInterface(IID_IY, pIY) // m_pUnknownInner指向INondelegatingUnknown的vtbl, 参考此调用栈的第11行 28 令pIY指向IY实例 29 return 30 return 31 return 32 return 33 Client::pIY->QueryInterface(IID_IX, pIX2) 34 CB::QueryInterface(IID_IX, pIX2) // 因为pIY指向IY的实例, 即CB 35 m_pUnknownOuter->QueryInterface(IID_IX, pIX2) 36 CA::QueryInterface(IID_IX, pIX2) // m_pUnknownOuter指向CA的实例, 参考此调用栈的第9行 37 令pIX2指向IX实例 38 return 39 return 40 return 41 return 42 Client::pIY->QueryInterface(IID_IZ, pIZ) 43 CB::QueryInterface(IID_IZ, pIZ) // 因为pIY指向IY的实例, 即CB 44 m_pUnknownOuter->QueryInterface(IID_IZ, pIZ) 45 CA::QueryInterface(IID_IZ, pIZ) // m_pUnknownOuter指向CA的实例, 参考此调用栈的第9行 46 return E_NOINTERFACE 47 return E_NOINTERFACE 48 return E_NOINTERFACE 49 return E_NOINTERFACE 50 Client::pIX->QueryInterface(IID_IZ, pIZ) 51 CA::QueryInterface(IID_IZ, pIZ) // 因为pIX指向IX的实例, 即CA 52 return E_NOINTERFACE 53 return E_NOINTERFACE
上面的调用过程看懂的话, 你应该比较清楚 Aggregation 是如何实现 QueryInterface 的了.
如果看到这里还是不懂的话, 是时候边调试 COMAggregationDemo 边看了.
写到这里总结一下 Aggregation 的时候 QueryInterface 的实现:
1. CB 实现 1 个 IUnknown, 1 个 INondelegatingUnknown, 结构相同, 所以 IUnknown 和 INondelegatingUnknown 的 vtbl 也是相同的.
当用户直接拥有 指向 CB 实例的接口的时候 (比如 IY, IZ), 用户使用的是 IUnknown
IUnknown 把所有调用转发给 m_pUnknownOuter , 也就是首先让 CA 把用户的查询请求过滤一遍, 这样用户就无法查询到
CA 不支持但是 CB实现了的接口(比如我们假设CB除了实现了IY,IZ,还实现了IB, 用户是查询不到IB的).
INondelegatingUnknown 是拿给 CA 用的, 它实现了 CB 所支持的所有接口的查询.
INondelegatingUnknown的vtbl 只能由CA拥有(m_pUnknownInner),
绝不能让用户直接使用 INondelegatingUnknown的vtbl.
2. CA 利用 m_pUnknownInner 指向 CB 的 INondelegatingUnknown 的 vtbl.
用户查询集合 T 中的接口时, CA 使用自己的 QueryInterface 完成
用户查询集合 S-T 中的接口, CA 利用 m_pUnknownInner 转发给 CB 的 INondelegatingUnknown 完成
用户查询其他接口时, 返回 E_NOINTERFACE
---------------------------------------------------------------------------------------------------------------------
2015-10-21 19:36
学习了Inside COM之后,一切都明了。不解释。可惜没有英文原版的PDF。
Essential COM适合当工具书。不适合当教程看,跟C++ Primer一个尿性。
现在我有一个问题。如果我开发一个COM组件,是不是必须也发布相关的头文件来包含interface的定义,并把头文件也一并发布?(否则客户怎么获取interface的定义?)有没有可能只发布DLL并注册到系统上就可以了?或许就是利用Typelib吧,但是目前看没看到那里。后面再说。
------------------------------------------------------------------------------------------------------------------
当前进度:https://msdn.microsoft.com/en-us/library/windows/desktop/ms221610(v=vs.85).aspx
http://stackoverflow.com/questions/954653/i-want-to-learn-com-how-should-i-proceed
/*************************************************************************************/
已经学习过的内容:Programming Windows with MFC关于COM那一章。讲了一些基本的概念,挺重要的。
教程:(在bing.com搜索component object model tutorial)
MSDN:https://msdn.microsoft.com/en-us/library/ms680573(v=VS.85).aspx
http://www.codeguru.com/cpp/com-tech/activex/tutorials/article.php/c5567/Step-by-Step-COM-Tutorial.htm#Intro
http://www.codeproject.com/Articles/633/Introduction-to-COM-What-It-Is-and-How-to-Use-It
首先,MSDN的内容很重要,起码要把一些概念性的内容看懂,对COM的框架有个整体性的了解。因为网络上的一些教程对COM标准本身并没有足够的解释,会让你知其然不知其所以然。
1.
在VS中如何编译IDL文件
右击IDL文件,选择“编译”,编译成功之后,会生成若干.h和.c文件,把你需要添加的文件添加到项目中即可
MSB3644 warning暂时不去管它,因为不是项目本身的问题,只是没安装SDK而已,而且目前用不到
2.
添加了IAdd.idl编译后生成的IAdd_h.h头文件,IAdd_i.c源文件
3.
项目打开了预编译头的选项并且把所有系统或者库头文件放到stdafx.h中,充分利用预编译头的功能加快编译速度
4.
教程错误,static_cast缺少参数,添加上,分别是IUnknown*和IAdd*
5.
创建CAddFactory的时候,VS自动添加的头文件是Unknwnbase.h,通过添加IAdd_h.h也可以
6.
CAddFactory::CreateInstance中
if (pUnknownOuter != NULL)
{
return CLASS_E_NOAGGREGATION ;
}
这里的语义是如果pUnknownOuter非NULL,则报错:不支持aggregation。我记得PW-MFC中COM那一章讲过aggregation就是“A key difference between containment and aggregation is that any object can be contained by another object, but only objects that specifically support aggregation can be aggregated.”
再看MSDN对CreateInstance的文档:
pUnkOuter [in]
If the object is being created as part of an aggregate, specify a pointer to the controlling IUnknown interface of the aggregate. Otherwise, this parameter must be NULL.
所以上面代码的语义其实就是说CAddObj不支持aggregate。
7.
实现CAddFactory的AddRef,Release,QueryInterface,基本上就模仿着CAddObj的实现方式
8.
在实现步骤8的时候,不知道CLSID_AddObject是从哪儿来的。在页面搜了一下clsid,发现评论里面回答是在AddObjGuid.h中,把作者的项目文件夹下载下来发现确实有这么一个头文件,打开内容如下
//
//AddObjGuid.h
//
#ifndef __AddObjGuid_h__
#define __AddObjGuid_h__// {92E7A9C2-F4CB-11d4-825D-00104B3646C0}
static const GUID CLSID_AddObject =
{ 0x92e7a9c2, 0xf4cb, 0x11d4, { 0x82, 0x5d, 0x0, 0x10, 0x4b, 0x36, 0x46, 0xc0 } };
#endif
然后我想起来PW-MFC的COM那一章讲过CLSID这些GUID都是最好用VS提供的工具生成。查了一下叫Guidgen.exe
打开方式:工具>创建GUID(或者打开VS2013 开发人员命令提示直接输入guidgen.exe)
参考:https://msdn.microsoft.com/en-us/library/ms241442(v=vs.80).aspx
生成GUID之后就创建了这么一个头文件
9.
打开:工具>创建GUID报错:
encountered an improper argument
搜了一下,解决方案是这个:
http://modery.net/fixing-erroneous-guid-creation-in-visual-studio-2010/
但问题是我打开C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\Bin
里面没有guidgen.exe
不明真相,换uuidgen.exe生成:
f8771b89-7971-4085-8d5f-88f7c140698b
即:
// {f8771b89-7971-4085-8d5f-88f7c140698b}
static const GUID CLSID_AddObject =
{ 0xf8771b89, 0x7971, 0x4085, { 0x8d, 0x5f, 0x88, 0xf7, 0xc1, 0x40, 0x69, 0x8b } };
10.
DllGetClassObject的函数原型是从MSDN上面copy下来的,没有用作者自己手打的,个人感觉这样做要好些,与标准统一
11.
实现了CAddFactory和CAddObj的默认构造函数及析构函数,把g_nComObjsInUse定义在Exports.cpp中,从而为实现步骤9做好准备工作
12.
查了一下资料(https://msdn.microsoft.com/en-us/library/hyx1zcd3.aspx)
Exports.def中之所以用PRIVATE关键字,意味着在生成的import library中不包含这几个函数的符号,而且也没有提供头文件定义这几个导出函数的原型。这就意味着使用该DLL只能靠LoadLibrary+自己声明函数原型+GetProcAddress。
13.
创建Global.h,把g_hModule和g_nComObjsInUse的声明放进去,二者的定义都放在dllmain.cpp,与作者的做法不一样
14.
创建Registry.h和Registry.cpp,实现DllRegisterServer和DllUnregisterServer
关于Windows Registry,参考:
https://msdn.microsoft.com/en-us/library/ms724871(v=VS.85).aspx
15.
生成解决方案,报错
1> 正在创建库 E:\vs_wksp\PW_MFC_2ND\COMDEMO\Release\ComAdd.lib 和对象 E:\vs_wksp\PW_MFC_2ND\COMDEMO\Release\ComAdd.exp
1>AddObj.obj : error LNK2001: 无法解析的外部符号 _IID_IAdd
1>E:\vs_wksp\PW_MFC_2ND\COMDEMO\Release\ComAdd.dll : fatal error LNK1120: 1 个无法解析的外部命令
根据步骤2,编译IAdd.idl得到的IAdd_i.c包含IID,因此把IAdd_i.c添加进项目(并包含stdafx.h),但是,生成项目时又报错
错误 1 error C1853: “Release\ComAdd.pch”预编译头文件来自编译器的早期版本,或者预编译头为 C++ 而在 C 中使用它(或相反) E:\vs_wksp\PW_MFC_2ND\COMDEMO\ComAdd\IAdd_i.c 1 1 ComAdd
把IAdd_i.c后缀名改为.cpp后,解决了这个错误。
然后令AddObj.cpp包含IAdd_i.cpp,解决了LNK2001问题,成功生成AddObj.dll
16.
把AddObj.dll实现的COM组件注册到注册表。
打开cmd,切换到项目生成AddObj.dll的目录
输入:
regsvr32
你会看到usage提示(你可以看到如何注销)
输入:
regsvr32 ComAdd.dll
可以看到注册成功,提示:
DllRegisterServer in ComAdd.dll succeeded.
17.
tlb文件的输出路径可以从:
项目属性>MIDL>输出
查看到,
我这里看到的默认为:
$(IntDir)$(ProjectName).tlb // 也就是项目路径\Debug或者\Release文件夹,即输出obj文件的地方,你可以看到ComAdd.tlb
这些宏的含义搜一下就知道,或者看这篇博客:http://www.cnblogs.com/lidabo/archive/2012/05/29/2524170.html
你可以通过修改该选项的“输出目录”,从而把tld,idl等文件输出到你指定的目录
tlb文件并没有输出到解决方案的输出目录,这是因为(根据该作者的说法,该tld的内容可以嵌入到dll中)所以你发布只需要发布dll即可,在使用ComAdd.dll的项目中不用:
#import "ComAdd.tlb"
只需要这样就可以了:
#import "ComAdd.dll"
18.
按教程写的DllRegisterServer不明所以。教程也没解释
查了一下MSDN
https://msdn.microsoft.com/en-us/library/ms690156(v=vs.85).aspx
里面Registering COM Applications讲了这方面的内容(借助这个把Registry.cpp中的代码看懂了)
着重提的 一点是,registry中的key是不区分大小写的
注册表里找不到CLSID怎么办?
操蛋的是,用regsvr32注册(64和32的都试过),提示成功,但是在注册表里找不到这个组件的CLSID,说明没注册成功。
又搜了一篇资料:http://wenku.baidu.com/view/c23f2e6ea8956bec0975e3f6.html
注册提示:
肯定哪里没对。换另一篇教程看看,实在不行就去搜教材。
http://www.codeproject.com/Articles/633/Introduction-to-COM-What-It-Is-and-How-to-Use-It
根据上面教程的作者推荐,下载了一本Essential COM,在107页开始讲怎么注册。看看
按照作者的教程把DllRegisterServer, DllUnregisterServer给修改了。还是注册不成功,发帖问了一下:
http://bbs.csdn.net/topics/391843315?page=1#post-400477268
调试了一下,发现RegSetValueExA返回错误码0x5,用FormatMessage查看了一下,是Access is denied.如图
查一下可能是什么问题导致的,难道是因为没有获取管理员权限?
原来是因为没有用管理员权限打开VS。用管理员权限打开VS调试就行了。
又找了一篇,看看这个作者怎么弄的
http://www.codeproject.com/Articles/338268/COM-in-C
有一个很奇怪的事情,打开OleView.exe>All Objects
能够看到如下,所有写进注册表的内容都在,ProgID什么的:
但是打开regedit却找不到,见了鬼了?如图(即便搜索也搜不到啊)
还好找到了这个:
https://social.msdn.microsoft.com/Forums/windowsdesktop/en-US/96cd6e6f-bf14-4980-b890-15c1fa5cb57e/how-to-locate-clsid-in-registry?forum=windowsgeneraldevelopmentissues
https://support.microsoft.com/en-us/kb/305097
因为我是64位系统,所以要去C:\Windows\SysWOW64打开regedit就行了!
又问了个问题,喵了个咪的,蛋疼(感谢热心的RLWA32!)
https://social.msdn.microsoft.com/Forums/vstudio/en-US/e9e5c590-99e4-4510-88db-4f490332d6c5/error-lnk2001-undefined-reference-to-iidiadd-when-doing-com-programming?forum=windowsgeneraldevelopmentissues
其实这里的问题在于,编译IAdd.idl的时候,还生成了一个IAdd_i.c(在C++项目中,把这个文件后缀改为.cpp即可),这里面有_IID_IAdd的定义。我可以把这个文件包含到CPPTest项目中去。就能解决这个问题,但是这不是个好的方法,因为IAdd_i.c是属于ComAdd项目的源码,你发布一个COM组件你还发布其源码?
当然,你可以从注册表获得IID,然后在CPPTest中定义一个:
const IID IID_IAdd = { 0x1221db62, 0xf3d8, 0x11d4, { 0x82, 0x5d, 0x00, 0x10, 0x4b, 0x36, 0x46, 0xc0 } };
然后你传给CoCreateInstance就可以用了,对CLSID也可以这样弄,但是这样太麻烦。
正确的做法是利用type library获取各种GUID。下面补充:
上面用OleView.exe可以发现没有把typelibrary注册到系统
怎么创建typelibrary:
http://edndoc.esri.com/arcobjects/9.1/ExtendingArcObjects/Ch02/TypeLibrariesAndIDL.htm
首先,如果你是用C/C++编写COM组件,那么VS在用MIDL编译器(Microsoft Interface Definition Language Compiler)去编译你COM组件的idl文件的时候,会在生成obj文件的文件夹中(Debug或者Release)生成该COM组件的tlb文件,用oleview打开这个文件就可以看到类似如下内容
怎么注册type library:
https://msdn.microsoft.com/en-us/library/windows/desktop/ms221610(v=vs.85).aspx
1. 使用.reg文件
https://msdn.microsoft.com/en-us/library/windows/desktop/ms220993(v=vs.85).aspx
After creating the type library, you can include it in the resource step of building your application, or leave it as a stand-alone file. In either case, be sure to specify the file name and path of the library in the application's registration (.reg) file, so Automation can find the type library when necessary. See the following section for information on registering the type library.
2. 用代码注册
直接把注册typelib的代码放到DllRegisterServer里面,模板参考(https://msdn.microsoft.com/en-us/library/windows/desktop/ms221610(v=vs.85).aspx)
实在不懂就打开regedit去HKCR\Typelib随便点开一个看一下就明白该怎么写了
Inside COM对Typelib的讲解也很少,但是讲了怎么用代码读取注册表中的类型库。没讲怎么注册,以及如何获取类型信息。
如何把tlb编译进dll?
定义资源文件把TLB文件包括进来:
https://msdn.microsoft.com/en-us/library/windows/desktop/ms221657(v=vs.85).aspx
A type library stores complete type information for all of an application's exposed objects. It may be included as a resource in a DLL or executable file, or remain as a stand-alone file (.tlb).
https://msdn.microsoft.com/en-us/library/windows/desktop/ms220993(v=vs.85).aspx
Optionally, use the Resource Compiler (RC) to bind the type library with the compiled project. The type library can bind with DLLs or executable files. For example, to bind a type library named output.tlb with a DLL, use the following statement in the .rc file for the DLL:
typelib output.tlb
首先为你的DLL项目创建资源文件
然后打开编辑其代码,在最后一行添加如下代码:
1 typelib Debug\ComAdd.tlb
因为项目每次生成的tlb文件都在Debug(或Release,取决于项目配置)下,所以你指定好文件位置即可,我指定的就是Debug\ComAdd.tlb,这里的“1”应该就只是个编号。然后生成DLL项目。再用OleView的View Typelib选项来打开这个DLL,就能看到Typelib的信息了。
如何知道dll中有没有tlb?
用oldview的View Typelib功能去打开一个dll,报错的话就没有tlb。正常打开的话就说明tlb编译进了dll
如何打开OleView:
开始->所有程序->Visual Studio 20XX->Visual Studio Tools->VS20XX开发人员命令提示->管理员权限打开->输入OleView
如何使用typelibrary:
http://www.codeproject.com/Articles/3699/Importing-Type-Libraries
https://msdn.microsoft.com/en-us/library/windows/desktop/aa367048(v=vs.85).aspx