看COM本质论做的总结
class FastString {
char* m_psz;
public:
FastString(const char *psz);
~FastString();
int Length(void) const;
int Find(const char *psz)const;
};
#include "faststring.h"
#include
FastString::FastString(const char *psz)
:m_psz(new char[strlen(psz) + 1])
{
strcpy(m_psz, psz);
}
FastString::~FastString()
{
delete[] m_psz;
}
int FastString::Length(void) const
{
return strlen(m_psz);
}
int FastString::Find(const char *psz) const
{
// O(1)
return 0;
}
假如一个faststring.obj 需要16MB的空间,有三个应用程序ABC都要用到这个类,的方法,那虚拟内存就需要48MB;
另一种情况是,一旦类库的厂商发现了FastString类中的缺陷,我们只能全部重新编译ABC三个应用程序;
解决上面的问题,就是将FastString独立为dll类
#ifndef EXPORT_IMPORT_API
#ifdef MAKING_LIBRARY
#define EXPORT_IMPORT_API __declspec(dllexport)
#else
#define EXPORT_IMPORT_API __declspec(dllimport)
#endif
#endif
class EXPORT_IMPORT_API FastString {
char* m_psz;
public:
FastString(const char *psz);
~FastString();
int Length(void) const;
int Find(const char *psz) const;
};
库的编译add_definitions(-DMAKING_LIBRARY)
应用程序调用不需要define MAKING_LIBRARY
到此,以上用dll的形式,你将面临C++的基本弱点: C++缺少二进制一级标准。
由于FastString 引入库lib和引出符号dll使用了创建该dll的编译器(比如Gnu C++)的名字改编方案,所以使用其它编译器(比如Borland C++)产生的客户将无法与引入库lib成功链接。
消除名字改编现象的经典技术是使用 extern “C”, 但是这项技术对于FastString这种情况并没有用,因为它引出的是成员函数,而非全局函数。
有一项技术能够减轻这个问题,就是在客户的链接器上,使用模块定义文件(Module Definition File, 通称为DEF文件)
假设此时你需要优化一下FastString类,增加了一个成员变量 int m_len(如下)
class EXPORT_IMPORT_API FastString {
const int m_len; //add
char* m_psz;
public:
FastString(const char *psz);
~FastString();
int Length(void) const;
int Find(const char *psz) const;
};
虽然类的公共接口没有变,但实际上sizeof(FastString)发生了变量,由原来的4变成了8。如果直接替换FastString.dll,这样新增的4字节内存就可能被应用程序其它地方占用,造成异常。
所以不得不重新用新的FastString.lib重新编译应用程序ABC。 由于这种耦合性,以及上一节的到的编译器和链接器的不兼容性,“简单地把C++类的定义从DLL中引出来”这种方案并不能提供合理的二进制组件结构。
faststringitf.h
class EXPORT_IMPORT_API FastStringItf {
class FastString;
FastString *m_pThis;
public:
FastStringItf(const char *psz);
~FastStringItf();
int Length(void) const;
int Find(const char *psz) const;
};
faststringitf.cpp
#include "faststring.h"
#include "faststringitf.h"
FastStringItf::FastStringItf(const char *psz)
:m_pThis(new FastString(psz))
{
assert(m_pThis != 0);
}
FastStringItf::~FastStringItf()
{
delete m_pThis;
}
int FastStringItf::Length(void) const
{
return m_pThis->Length();
}
int FastStringItf::Find(const char *psz) const
{
return m_pThis->Find(psz);
}
这样,客户只用包含FastStringItf类的头文件就行,FastStringItf构造函数中的new操作符的调用也要被重新编译,以确保总是分配足够的内存。而且客户永远不会包含实现类FastString类的定义,这使FastString实现者非常灵活。解决了1.4中所存在的问题。
但这样有个坏处,就是如果公有函数比较多的大型类库,光编写这些传递过程就可能非常冗长,也增加出错的可能性,也增加开销。
ifaststring.h
class IFastString {
public:
virtual int Length(void) const = 0;
virtual int Find(const char *psz) const = 0;
};
extern "C" IFastString* CreateFastString(const char* psz);
IFastString* CreateFastString(const char* psz)
{
return new FastString(psz);
}
class FastString : public IFastString{
const int m_len;
char* m_psz;
public:
FastString(const char *psz);
~FastString();
int Length(void) const;
int Find(const char *psz)const;
};
调用:
int f()
{
IFastString *pfs = CreateFastString("Liukang");
int n = pfs->Find("kang");
delete pfs;
return n;
}
由于接口类的析构函数并不是虚函数,这意味着delete的调用并不会动态找到派生类的析构函数,导致内存泄漏
class IFastString {
public:
virtual int Delete(void) const = 0; //add
virtual int Length(void) const = 0;
virtual int Find(const char *psz) const = 0;
};
在FastString实现类中增加Delete的实现,释放内存
int FastString::Delete(void)
{
delete this;
}
调用:
int f()
{
int n = -1;
IFastString *pfs = CreateFastString("Deface me");
if(pfs)
{
pfs->Find("ace me");
pfs->Delete();
}
return n;
}
在FastString.dll中,除了一个入口函数CreateFastString外,其它所以入口函数都是虚函数。
到此其实我们已经进入C++库调用的显式加载了
IFastString *CallCreateFastString(const char* psz){
static IFastString* (*pfn)(const char*) = 0;
if(pfn){
const TCHAR szDll[] = __TEXT("FastString.dll");
const char szFn[] = "CreateFastString";
HINSTANCE h = LoadLibrary(szDll);
if(h)
*(FARPROC*)&pfn = GetProcAddress(h, szFn);
}
return pfn ? pfn(psz) : 0;
}
这样的好处是,客户不需要连接dll的引入库lib,对dll没有依赖性。用到才装载dll,没用到就不会被装载。
假设我们需要扩展Load和Save的方法,可按下面的方式
class IExtensibleObject {
public:
virtual void *Dynamic_Cast(const char* pszType) = 0;
virtual void Delete(void) = 0;
};
class IFastString : public IExtensibleObject {
public:
virtual int Length() = 0;
virtual int Find(const char* psz) = 0;
};
class IPersistentObject : public IExtensibleObject {
public:
virtual bool Load(const char* pszFileName) = 0;
virtual bool Save(const char* pszFileName) = 0;
};
有了这样的类型层次之后,客户就可以利用编译器独立的结构,动态地查询对象是否实现了某个指定的接口
bool SaveString(IFastString* pfs, const char* pszFN)
{
bool bResult = false;
IPersistentObject *ppo = (IPersistentObject*)pfs->Dynamic_Cast("IPersistentObject");
if (ppo)
{
bResult = ppo->Save(pszFN);
}
return bResult;
}
FastString中的Dynamic_Cast的实现可以简单使用显式的静态类型转换功能:
void* FastString::Dynamic_Cast(const char* pszType) {
if (0 == strcmp(pszType, "IFastString"))
return static_cast<IFastString*>(this);
else if (0 == strcmp(pszType, "IPersistentObject"))
return static_cast<IPersistentObject*>(this);
else if (0 == strcmp(pszType, "IExtensibleObject")
return static_cast<IFastString*>(this);
return 0;
}
/*
注意,当客户请求公共的基接口IExtensibleObject 时,实现类将自己静态转换为IFastString, 是因为
return static_cast(this); 有二义性,因为IFastString和IPersistentObject都是从IExtensibleObject继承过来的。
如果IExtensibleObject是IFastString和IPersistentObject的虚基类,那么这个转换就不会有二义性的问题,所以这条语句能够正确编译。
然而,引入虚基类将导致在结果对象中加入不必要的运行时复杂性,而且也会带来编译器相信性。这是因为虚基类是另一项“编译器厂商可以选择自己的实现方法”的c++语言特性。
*/
考虑下面的客户代码:
void f(void)
{
IFastString* pfs = 0;
IPersistentObject* ppo = 0;
pfs = CreateFastString("Feed BOB");
if (pfs) {
ppo = (IPersistentObject*)pfs->Dynamic_Cast("IPersistentObject");
if (!ppo) {
pfs->Delete();
}
else {
ppo->Save("C:\\autoexec.bat");
ppo->Delete();
}
}
}
客户必须记录下哪个指针是与哪个对象联系在一起的,并且每个对象只能调用一次Delete方法。对于上面的简单代码,并不是繁重的负担,但如果在复杂的客户代码中,管理这些关系会变得非常复杂,并容易出错。为了简化,把管理对象生命周期的责任放在对象的实现部分。参考智能指针的实现方式。
在IExtensibleObject基类中增加两个接口
class IExtensibleObject {
public:
virtual void *Dynamic_Cast(const char* pszType) = 0;
virtual void DuplicatePointer(void) = 0; //add
virtual void DestroyPointer(void) = 0; //add
};
在FastString类中增加下面的实现
class FastString : public IFastString ,public IPersistentObject
{
int m_cPtrs;
public:
FastString(const char *psz) :m_cPtrs(0) {}
void DuplicatePointer(void) {
m_cPtrs++;
}
void DestroyPointer(void) {
if (--m_cPtrs == 0)
delete this;
}
};
在两个复制的地方增加指针的计数
//extern "C" 对外接口,new在堆中,复制到stack中
IFastString* CreateFastString(const char* psz)
{
IFastString* pFsResult = new FastString(psz);
if (pFsResult)
pFsResult->DuplicatePointer();
return pFsResult;
}
//有指针的复制
void* FastString::Dynamic_Cast(const char* pszType) {
void* pvResult = 0;
if (0 == strcmp(pszType, "IFastString"))
pvResult = static_cast<IFastString*>(this);
else if (0 == strcmp(pszType, "IPersistentObject"))
pvResult = static_cast<IPersistentObject*>(this);
else if (0 == strcmp(pszType, "IExtensibleObject")
pvResult = static_cast<IFastString*>(this);
//pvResult含有一个复制的指针,所以需要调用
((IExtensibleObject*)pvResult).DuplicatePointer();
return pvResult;
}
至此,客户的代码就可以写成:
void f(void)
{
IFastString* pfs = 0;
IPersistentObject* ppo = 0;
pfs = CreateFastString("Feed BOB");
if (pfs) {
ppo = (IPersistentObject*)pfs->Dynamic_Cast("IPersistentObject");
if (ppo) {
ppo->Save("C:\\autoexec.bat");
ppo->DestroyPointer();
}
pfs->DestroyPointer();
}
}
其实不知不觉,我们刚刚一步步设计了组件对象模型(COM, Component Object Model)