1. COM是一个更好的C++

2023-05-16

看COM本质论做的总结

1.1 软件分发和C++

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 <string.h>

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三个应用程序;

1.2 动态链接和C++

解决上面的问题,就是将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

1.3 C++和可移植性

到此,以上用dll的形式,你将面临C++的基本弱点: C++缺少二进制一级标准。

由于FastString 引入库lib和引出符号dll使用了创建该dll的编译器(比如Gnu C++)的名字改编方案,所以使用其它编译器(比如Borland C++)产生的客户将无法与引入库lib成功链接。

消除名字改编现象的经典技术是使用 extern “C”, 但是这项技术对于FastString这种情况并没有用,因为它引出的是成员函数,而非全局函数。

有一项技术能够减轻这个问题,就是在客户的链接器上,使用模块定义文件(Module Definition File, 通称为DEF文件)

1.4 封装性和C++

假设此时你需要优化一下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中引出来”这种方案并不能提供合理的二进制组件结构。

1.5 把接口从实现中分离出来

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中所存在的问题。

但这样有个坏处,就是如果公有函数比较多的大型类库,光编写这些传递过程就可能非常冗长,也增加出错的可能性,也增加开销。

1.6 抽象基类作为二进制接口 (接口与实现分离)

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外,其它所以入口函数都是虚函数。

1.7 运行时多态

到此其实我们已经进入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,没用到就不会被装载。

1.8 对象的扩展性

假设我们需要扩展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<IExtensibleObject*>(this); 有二义性,因为IFastString和IPersistentObject都是从IExtensibleObject继承过来的。
如果IExtensibleObject是IFastString和IPersistentObject的虚基类,那么这个转换就不会有二义性的问题,所以这条语句能够正确编译。
然而,引入虚基类将导致在结果对象中加入不必要的运行时复杂性,而且也会带来编译器相信性。这是因为虚基类是另一项“编译器厂商可以选择自己的实现方法”的c++语言特性。
*/

1.9 资源管理

考虑下面的客户代码:

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();
	}
}

1.10 我们走到哪了?

其实不知不觉,我们刚刚一步步设计了组件对象模型(COM, Component Object Model)

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

1. COM是一个更好的C++ 的相关文章

  • 在 Win 10 中调试卸载 DLL 时发生崩溃,但在 Win 7 中则不然

    不完全确定我已经解决了这个问题 但这就是我所看到的和我所看到的think正在进行 我有一个主要用 C 编写的 Win32 程序 用于加载 C DLL 该 DLL 通过 COM 对象 可能由 DLL 本身实例化的对象 将数据从 C 程序传递到
  • IRunningObjectTable.Register 始终将 pdwRegister 设置为 65536,这是一个无效值

    我在用着IRunningObjectTable Register and IRunningObjectTable Revoke如图所示this http www codeproject com KB COM ROTStuff aspx ms
  • 如何引用所有正在运行的 Excel 应用程序实例(包括隐藏的和没有工作簿的实例)的 COM 对象?

    如何获取每个正在运行的 Excel 应用程序实例的完整引用列表 无论其工作簿数量和可见性状态如何 我知道我可以使用 Windows API 来查找每个 Excel 工作簿窗口 其窗口类名称EXCEL7 让他们的句柄与AccessibleOb
  • VBA COM 库中的这些 _B_var_Xxxxx 和 _B_str_Xxxxx 成员到底是什么?

    想象一下以下函数调用 foo UCase bar 我正在解析这段代码 并确定UCase是一个函数调用 现在我想将该函数调用解析为定义它的 COM 库中函数的声明 这个想法是实现一个代码检查来确定何时Variant当使用内置函数时String
  • C# - 检索 COM+ 组件的属性?

    我的服务器 Windows Server 2003 上有一个 COM 组件 有什么方法可以以编程方式检索该组件的属性 例如使用的构造函数字符串 当我转到管理工具 gt 组件服务 gt COM 应用程序并右键单击我的组件时 这些是我希望能够检
  • tlb 文件是否具有关联架构?

    我有一个 32 位 DLL 旨在通过 com 模型和关联的 tlb 文件进行访问 该 DLL 似乎是 x86 有没有办法从 x64 程序访问这种 DLL tlb 文件与 x86 x64 无关吗 我问这个问题是因为有些功能似乎可以工作 其他功
  • 从delphi应用程序调用.net4.0 com服务器后出现错误异常

    我们正在将代码库从 BDS2006 迁移到 Rad Studio XE 我们发现了一些非常奇怪的行为 如果我们在从 Net4 0 中实现的 COM 服务器创建一些对象后进行无效的浮点运算 即除以零 我们不会没有得到正常异常 即 EDivis
  • .NET WebBrowser 控件可以使用 IE9 吗?

    我意识到这是一个早期版本并且不稳定 我不会梦想在任何其他项目中将默认的 Web 浏览器控件替换为 IE9 但在这种情况下 我特别需要 IE9 与其他版本进行比较 我想让 NET WebBrowser 控件使用 IE9 而不是机器上默认版本的
  • 如何查找给定接口 GUID 的 COM 接口定义?

    我有一个 COM 接口 GUID 但我不知道该接口定义 我不知道它有什么方法 有什么参数等等 我怎样才能获得这些信息 一般情况下可以吗 实际问题是获取 中定义的少数 COM 接口的接口定义actxprxy dll 例如IFileDialog
  • VB6 ActiveX exe - 正确的注册顺序是什么?

    我最近更新了一个 Visual Basic 6 应用程序 它是一个 ActiveX exe 在 Windows XP 上运行 我有几个此应用程序的测试人员 他们已收到 exe 的副本并正在尝试运行它 但是 他们收到一条错误消息 Unexpe
  • UI 线程正在阻塞调用 COM 对象的后台线程

    我正在开发一个通过第三方 COM 库与外部设备通信的应用程序 我试图让与设备的所有通信都通过后台线程 以防止通信问题搞砸我的应用程序 并消除在 UI 线程中进行通信所引入的一些其他复杂性 问题是 每当发生导致主 UI 线程阻塞的情况 即调用
  • 使用 VS2012 中的 C++ 中的 ATL 创建 COM

    尝试在 Visual Studio 2012 中使用 ATL 创建简单的 COM 库 我愿意 New ATL Project Welcome to the ATL Project Wizard Next Application Settin
  • PowerShell 中的 COM 接口包装?

    我在 C 中有以下代码 我尝试将其移植到 PowerShell 但我不知道如何移植这个演员 ISkypeEvents Event skype CallStatus CallStatusHandler 如果我只是在 PowerShell 控制
  • 使用 std::unique_ptr 管理 COM 对象

    我正在尝试使用智能指针来保存我的类中的 COM 对象 同时避免使用 ComPtr 是否可以使用 unique ptr 来达到此目的 我对智能指针很陌生 到目前为止我有点困惑 请考虑以下简化代码 class Texture private s
  • 从 C# 访问 COM vtable

    C 中有没有办法访问 COM 对象的虚拟方法表以获取函数的地址 经过大量搜索和拼凑不同的部分解决方案后 我弄清楚了如何做到这一点 首先 您需要为您尝试访问的对象定义 COM 组件类 ComImport Guid InterfaceType
  • C++ Microsoft:如何将 uuid/guid 与模板专业化相关联

    我想将 uuid guid 与模板专业化相关联 以下代码可用于将 uuid 与非模板接口 类 结构 关联 interface declspec uuid CECA446F 2BE6 4AAC A117 E395F27DF1F8 ITest
  • C#:Regasm 为我的 COM DLL 中的每个类生成注册表项?

    我正在用 C 编写一个类库 IE BHO 目前正在处理大量我认为来自 REGASM 生成的注册表项的垃圾输出 简短的版本是这样的 我只想向 IE 以及 COM 的其余部分 公开少数类 当前 一个类 只有一个类设置了 ClassInterfa
  • 如何在 IE9 中检测用户禁用了某个加载项?

    IE9 用户可以通过单击齿轮按钮并选择 管理加载项 来禁用加载项 例如浏览器帮助程序对象 我需要使用 JavaScript 检测给定的附加组件是否已以这种方式被禁用 我似乎无法找到错误处理程序的方法
  • WinApi:获取 COM 表单的控件名称

    我想用 Net 框架替换我当前的 UI 自动化工具 QTP 我需要测试 VB6 COM 应用程序 框架的基础之一是使用表单名称 到目前为止 我未能找到使用 Win API 获取这些数据的方法 该解决方案只有一个约束 即该解决方案必须依赖 N
  • 为什么我的 COM 对象不显示组件服务中的方法?

    我正在尝试创建一个 COM 对象并将其注册到 COM 下 一切似乎都很顺利 但是当我查看组件服务并深入了解时 控制台根目录 组件服务 电脑 我的电脑 COM 应用程序 测试通讯 组件 TestCom Com MyCom 接口 MyCom 方

随机推荐

  • android studio maven 拉取代码出现 bad gateway 502

    一般都是gradle 配置的maven的仓库 问题 我这里是因为使用了 repositories google jcenter mavenCentral 新增 maven url 39 https www jitpack io 39 mav
  • android studio使用 maven push 插件上传私有maven - 已成功使用到项目中

    gradle 任务 下面是放在gradle 配置里面 比如 我的项目model 是 apm 那么 就把下面的代码放到 apm的 gradle 下面 plugins id 39 com android library 39 id 39 kot
  • 工作的三个层次,什么样的工作堪称自由

    工作的三个层次 xff0c 什么样的工作堪称自由 先说结论 只有工作的技艺人才是自由的 前段时间经常喜欢看建造类的视频 一个澳洲小哥 只用最原始的器械徒手打造东西 比如空手打造石斧 空手打造钻木取火套装等等 这类视频很有意思 我就特别爱看
  • android 创建Model 解决无法依赖传递问题 , 实现 sdk 依赖关系的传递

    介绍 我现在创建了一个应用A 一个库工程 B B 远程依赖了库工程C A远程依赖B工程 B 是通过Maven 发布成远程依赖库 B 作为一个通用的库工程 发布到了Maven上 就可以比较简单的被各个项目引入 如下图 问题 目前存在一个问题
  • 2021总结. 2022展望

    2021 收获了许多 技能上 学习了多个技能 自由泳自由倒立复刻拳王梅威瑟的跳绳训练单板滑雪 总结 技能上尽量是身体力行的 自从看过 囚徒健身 后 被作者的自传所影响 希望成为想他那样的人 认知上 认知上也有了提升 读了许多书 今年比较喜欢
  • 仿照爱时间app写的时钟 自定义view

    MyClockView MyClockView 仿照 爱时间app 写的自定义时间控件 爱时间的 控件 我写的控件 可以看到我写的在指针 刻度上面 是比他要精细一些的 后面的点击事件 还有中间文字的绘制 都是一些套路 我的时间也不够多 就不
  • dagger2简单使用与理解笔记

    文章目录 使用dagger2好处具体案例查看github 1 使用dagger2注入基本使用流程概念 2 dagger2中各种注解基本使用引入dagger20 写两个对象 用来实际操作的1 写module类 注解Module Provide
  • electron调用dll文件

    Electron 对系统层能力的使用可能比较弱 xff0c 此时需要求助 Python C 43 43 C 等语言 xff0c 通过 ffi napi 库可以让 Node js 使用 C 43 43 dll xff0c 通过 electro
  • 动态库和静态库的区别

    什么是库文件 一般来说 一个程序 通常都会包含目标文件和若干个库文件 经过汇编得到的目标文件再经过和库文件的链接 就能构成可执行文件 库文件像是一个代码仓库或代码组件的集合 为目标文件提供可直接使用的变量 函数 类等 库文件包含了静态链接库
  • reactor/proactor模型简介

    Reactor和preactor都是IO多路复用模式 xff0c 一般地 I O多路复用机制都依赖于一个事件多路分离器 Event Demultiplexer 分离器对象可将来自事件源的I O事件分离出来 xff0c 并分发到对应的read
  • c语言中的带参宏定义

    C语言允许宏带有参数 在宏定义中的参数称为形式参数 xff0c 在宏调用中的参数称为实际参数 xff0c 这点和函数有些类似 对带参数的宏 xff0c 宏展开和用实参替代形参 xff0c 发生在预处理阶段 示例1 xff1a define
  • ZCU102 Zynq MPSoC IP设置与说明

    目录 1 前言2 设置与说明2 1 PS UltraScale 43 Block Design2 2 I O Configuration2 2 1 Bank0 3电压 xff1a 2 2 2 Low SpeedQSPISD卡CANI2CPM
  • ROS下使用激光雷达RPLIDAR-A2进行SLAM完成地图的构建

    想要进行一个完整的地图建立离不开以下几个模块 xff1a 1 坐标 2 激光数据 3 绘图算法 ROS工程可以从我的GitHub上面下载 xff1a https github com LJianlin ROS SLAM Gmapping 下
  • C 语言Socket 实现http 带参数的POST请求

    本文叙述C语言中结合socket 如何实现http POST请求 xff0c 对于http协议相关内容可以查看HTTP协议详解 对于不带参数的post请求 xff0c 只需要按照http格式发送即可 下面以带参数的POST请求为例 1 C
  • 【学习C++】1.开始学习C++

    从今天开始学习C 43 43 xff0c 争取一年之内把 C 43 43 Primer Plus 看上两遍 xff0c 平均一周看一章 xff0c 并做课后对应习题 今天把 C 43 43 Primer Plus 的第二章看完了 xff0c
  • Ubuntu下安装make

    方法一 xff1a xff08 自动安装 xff09 1 进入root权限 xff1a su root 2 更新安装列表 xff1a apt get update 3 安装make xff1a apt get install ubuntu
  • nginx源码分析之http解码实现

    分析nginx是如何解析并且存储http请求的 对非法甚至恶意请求的识别能力和处理方式 可以发现nginx采用状态机来解析http协议 xff0c 有一定容错能力 xff0c 但并不全面 相关配置 跟解码有关的配置 merge slashe
  • 经纬高坐标系-ECEF坐标系-ENU坐标系

    无人机搭载的RTK获得的经纬高坐标要转换为东北天坐标 xff0c 才能用于局部的导航和定位 为了这个目的 xff0c 查阅资料 xff0c 越查越懵逼 xff0c 竟然这么多的坐标系 xff0c 略懂之后 xff0c 将学到的信息记录如下
  • Docker基础操作

    安装curl时报错 curl Depends libcurl3 gnutls 61 7 47 0 1ubuntu2 12 but 7 58 0 2ubuntu3 6 is to be installed sudo apt get purge
  • 1. COM是一个更好的C++

    看COM本质论做的总结 1 1 软件分发和C 43 43 class FastString span class token punctuation span span class token keyword char span span