Don Box - Essential COM:当删除运算符是成员函数时,为啥要将创建运算符定义为非成员函数?

Posted

技术标签:

【中文标题】Don Box - Essential COM:当删除运算符是成员函数时,为啥要将创建运算符定义为非成员函数?【英文标题】:Don Box - Essential COM: Why define the creation operator as a non-member function when the deletion operator is a member function?Don Box - Essential COM:当删除运算符是成员函数时,为什么要将创建运算符定义为非成员函数? 【发布时间】:2021-12-03 23:41:32 【问题描述】:

基本 COM,Don Box

第 1 章,作为二进制接口的抽象基,第 18 页

如果您没有这本书的副本,并且对组件对象模型 (COM) 感到好奇,请向下滚动到底部,我会在此处提供更多背景信息。

IFastString接口类定义为

// ifaststring.h
class IFastString

    public:
    virtual void Delete() = 0;
    virtual int Length() const = 0;
    virtual int Find(const char*) const = 0;

;

extern "C"
IFastString* CreateIFastString(const char* psz);

FastString 类定义为

// faststring.h
#include "ifaststring.h"

class FastString : public IFastString

    private:
    const int m_ch;
    char *m_psz;

    public:
    FastString(const char *psz);
    ~FastString();
    void Delete();
    int Length() const;
    int Find(const char *psz) const;
;

最后,这里是FastString的实现

// faststring.cpp
#include <string.h>
#include <faststring.h>

IFastString* CreateFastString(const char *psz)

    return new FastString(psz);


FastString::FastString(const char *psz)
    : m_ch(strlen(psz))
    , m_psz(new char[m_ch + 1])

    strcpy(m_psz, psz);


FastString::~FastString()

    delete [] m_psz;


void FastString::Delete()

    delete this;


int FastString::Length() const

    return m_ch;


int FastString::Find(const char *psz) const

    // find algorithm implementation goes here

当我第一次看到这段代码时,我觉得它有点“奇怪”。我很困惑为什么没有以与删除函数类似的方式定义创建函数。

在我看来,此代码有两种可能的修改。据我所知,以下两种选择都可以。

1:创建函数可以成为成员函数。 (?)

我最初认为这是可能的,但我现在认为这是不可能的,因为 C++ 类成员函数不能用extern "C" 链接定义。

因此,将创建函数定义为非成员的选择并不是完全随意的。

2:删除函数可以变成非成员函数。然后它将与创建功能更紧密地匹配。

// h (interface)
extern "C" // is this needed?
void DeleteIFastString(IFastString *s);

// cpp
void DeleteIFastString(IFastString *s)

    delete s;


// remove the virtual void Delete() function from both
// IFastString and FastString classes

我认为没有做出这个决定的唯一原因是它用另一个非成员函数污染了全局命名空间。然而,另一方面,可以说有一个与“创建者”函数在语法上相似的“删除器”是可取的。

删除器被实现为成员函数还有其他原因吗?

更多背景信息 (COM)

上面例子中ideom的目的是为了解决两个问题:

    动态库的设计应具有不改变的已定义接口。

这样就可以在不需要修改客户端代码的情况下更改实现。有人可以更新“FastString”动态库,保持接口不变,以便客户端代码可以链接到新版本的动态库,而无需更改客户端源代码。

    动态库应设计为具有不变的公开对象大小。

这样可以将更改后的动态库发送到客户端,并且编译后的客户端代码可以加载动态库代码,并且函数将访问正确偏移处的数据。

请考虑以下情况:如果接口类的成员变量发生更改,那么这些数据在内存中存储的偏移量或位置将发生更改。如果接口类暴露给客户端代码,那么该接口类的不同编译版本将是二进制不兼容的。

    如果客户端代码使用与用于编译动态库代码的编译器不同的编译器进行编译,则动态库应可由客户端在运行时加载并按预期工作。

这可能不会发生的原因是编译器以不兼容的方式使用编译代码。例如,虚函数有多种可能的实现方式,因为 C++ 标准没有定义应该如何实现多态性和虚函数。不同的编译器可能会产生不同的不兼容的二进制代码。

Don Box 在几页中比我在这里更准确地描述了这一点。希望我提供了足够的信息以便理解。

COM 通过定义一个没有成员变量的接口类和一个实现类(其数据成员和函数完全不能被客户端代码访问)来解决上述三个问题。除此之外,所有涉及实现类成员数据的函数都使用同一个编译器进行编译。 (用于编译动态库的编译器。)这意味着函数和数据由同一个编译器编译,因此是二进制兼容的。最后,用户可访问的函数使用外部 C 链接定义,以便它们兼容并且可以与使用不同编译器编译的客户端代码链接。

值得注意的是,以上假设架构是相同的。例如,COM 没有解决 x86 代码与 ARM 系统不兼容的问题。 (可能很明显,但值得一提以避免混淆。)

【问题讨论】:

Delete 在接口上被调用,即你必须已经有一个对象来调用delete。对于创建,没有接口(或现有对象);如果有的话,你已经有了一个对象,不需要调用 create。 一个extern "C" 工厂函数没有对应的相同语言链接的删除器对我来说似乎很奇怪。清理已经只能从 C++ 中实现了……如果语言链接确实没有实际意义并且没有其他目的,那它就变成了一个哲学问题。使用extern "C++" 工厂,它也可以是静态成员函数。 这是一个教导您 COM 设计原则的教学示例。总是有一个单独的工厂函数,CoCreateInstance() 是规范的。释放对象总是使用接口成员函数 IUnknown::Release() 来完成。避免考虑 C++,客户端程序员可能使用任何语言,而 C++ 不直接支持接口,尽管可以将 C++ 类转换为合理的近似值。 COM 可以被 C(和其他与 C 兼容的语言 - VB、C# 等)访问,所以你不能用 C++ 术语来理解 COM。 我会在其他“COM 不是 C++”的 cmets 中添加,事实上,是的,COM 确实解决了“x86 代码与 ARM 系统不兼容的问题”的问题。 COM 定义了如何通过 RPC 在两个进程和两台机器之间进行通信 docs.microsoft.com/en-us/windows/win32/com/… 这已使用多年,以允许 Windows 上的 x86 x64 COM 通信 【参考方案1】:

COM 不公开 C++ 类构造函数或静态类成员函数以创建实例。 COM 函数是CoCreateInstance,但这不是唯一的可能性。

一些 COM 接口是通过 IClassFactory 上的方法构造的,但您仍然需要一种机制来获取 IClassFactory (CoGetClassObject)

COM 没有使用Delete 方法,相关的成员函数是Release。所有 COM 接口都继承自 IUnknown

如果对它的所有引用都被释放,暴露接口的对象有责任知道如何处理自己。

您可能正在运行的代码访问具有不同大小的不同对象的相同接口的不同实现, 并在不同的 DLL 中实现。你会调用哪个静态函数来删除它?这不是你的问题。 这就是通过接口的意义——你不关心实现是什么。

可以用 C++ 以外的语言构造 COM 接口,而调用者不知道。

上面的第 2 点是无关紧要的;对象的大小与调用者无关。 COM 公开接口,而不是对象。重要的是接口的二进制布局,它(在实践中)被定义为“MS 编译器对这个 C++ 定义做了什么”。

【讨论】:

澄清第2点:这就是接口必须没有成员数据的原因。如果它确实有成员数据并且成员数据可能会根据实现版本而改变,那么它将不起作用,因为接口的内存对齐方式会改变。这就是我所说的第 2 点的意思。 (根据定义,它不再是“接口”,但这是一个循环论证。) 接口的消费者从不直接访问成员数据,即使接口包含成员数据,添加或删除成员数据也不会影响 vtable 的布局。 来自书中的示例:不要将 FastString 与 IFastString 混淆。 IFastString 是暴露的接口,FastString 是一个实现 IFastString 的类。 IUnknown 在第 2 章中。

以上是关于Don Box - Essential COM:当删除运算符是成员函数时,为啥要将创建运算符定义为非成员函数?的主要内容,如果未能解决你的问题,请参考以下文章

当外部加载swf时,removechild不工作

AndroidStudio打开报错Missing essential plugins: com android. tools. design org. jetbrains. ... 导致无法进入AS

Essential of Programming Languages学习注意事项

Missing essential plugin: org.jetbrains.androidPlease reinstall Android Studio from scratch.

不要执行我的脚本,在刀片中隐藏引导模式

MySQL essential与MySQL