如何将ANSI C结构转换为C ++类,但保持ANSI C友好?

时间:2017-04-12 10:39:44

标签: c++ c class data-structures

从外部设备读出的帧存储在共享内存中(在结构中),供主(C ++)应用程序和ANSI C库使用。

由于原因有点过于宽泛而无法在此解释,库必须保持纯ANSI C,并且必须保持对其“纯ANSI C”形式的结构的访问。但主要的应用程序在很多地方使用数据,以“ANSI C”方式执行此操作是一件麻烦事,将其视为一个愚蠢的数据容器。如果它是一个类会更好 - 如果我可以添加构造函数,复制构造函数,比较运算符,neater'是有效的'方法(当前检查为在其中一个struct字段中没有幻数),通常很多C ++类可以做的事情,但ANSI C结构不能。除非我用共享内存中的类替换struct,否则会破坏与库的兼容性。

实现这一目标的巧妙方法是什么?创建一个继承自struct的类?通过构图继承?一个单独的类,有一组转换方法?还有什么别的我没想过的?一些透明的方法可以保持数据对C不变,但是增强了C ++的类功能?

注意:C ++和C都在结构的相同实例上运行。主app从设备中读出帧,写入共享内存中的结构,然后调用库函数在帧上做他们的魔术(可能,但不一定修改它;业务逻辑),然后对它执行自己的操作(显示,记录,在其他媒体上重播等。

我可以完全控制C ++代码,但C代码主要是不受控制;我可以创建一个结构的本地副本,如果这是有益的,但我的副本和'业务逻辑实例'应该保持同步,或者至少在每个库函数调用之前和之后同步(在我的控制下操作,但是时间决定按系统要求。)

编辑: 根据要求提供一些额外的细节:

  • “业务逻辑”在C库中实现,由用户输入的外部应用程序(用于PC)生成的自定义C代码(用于绘制逻辑的图形界面;认为“框图”),各不相同每台设备(许多用户,甚至更多设备)。该设备需要交叉编译;只有ANSI C交叉编译器才能以易于与PC应用程序捆绑在一起的形式提供; C ++交叉编译器仅在系统开发人员(我的)PC上可用;它的安装过程和许可证使得无法捆绑(已售出)的发电机应用程序。

  • 设备上的库和C ++应用程序使用共享内存作为所有输入和输出数据的存储,原因有两个:

    • 主要是因为该数据的数量和种类使得将其作为函数调用中的参数提供极其困难(设备可与之协作的各种外部系统超过20个,每个系统都有自己的通信协议,每个都提供输入和/或接受可以在业务逻辑中使用的输出)。 C ++应用程序处理所有通信并在各种接口之间来回转换数据和存储在共享存储器中的“易于消化”的数据格式,以供库(根据需要的特定业务逻辑实例)使用。
    • 但是设备上还运行着其他应用程序 - 一个WWW服务器,一个调试应用程序等 - 它们也可以同步到共享内存,显示当前状态,允许实时参数调整等。而这样的“集中存储” “/”superglobal“可能被认为是反模式,考虑到系统之间的各种各样的交互(内部和外部,C ++作为连接它们的中心枢纽),并且使得结构比什么样的更清晰如果我试图直接将每个数据提供者与每个数据使用者连接起来,那么拜占庭网就会出现。
  • 主应用程序处理从共享内存中读取所有重要接口的同步(定时,锁定);其他人可以直接进入共享内存并随时选择他们需要的东西(只读);由此产生的竞争条件错误将导致一个完全可接受的瞬间故障,将在接下来的“滴答”中得到纠正。所有写入都是同步的。
  • 因为C库是共享内存的主要,最重要的使用者和提供者,共享内存中的结构必须保持C兼容。

4 个答案:

答案 0 :(得分:2)

创建一个派生自C-struct的类,但要确保内存布局保持不变,即不使用虚方法(这些将添加vtable)或添加成员变量。在C ++ 11术语中,这将被称为标准布局类。有关详细信息,请参阅此处:

What are Aggregates and PODs and how/why are they special?

遵守此规则,您可以安全地在C struct和C ++类之间进行转换,并使用适当的成员函数。

注意:关于分配数据结构,您需要使用与分配相同的一组函数,即如果已使用malloc()分配,则必须使用free()释放它,如果已分配使用new时必须使用delete发布。因此,如果要从C和C ++代码中分配对象,则限制为malloc / free,因为C中没有新的/ delete。

答案 1 :(得分:1)

如果你以某种方式将其子类化,那么基础结构将需要一个vtable。您可以更改结构以包含这些元素,然后将其转换为/从类/结构中。您需要使用reinterpret_cast<>()。

然而,这是一个糟糕的主意。请不要这样做。

相反,在包含结构的类中实现业务逻辑。这样您就不需要支持在两个代码库之间编组结构,您可以保留结构的一个副本。

但是请记住将结构标记为volatile,如果它需要被C ++代码不知道的任何形式的后台线程更改。

答案 2 :(得分:0)

简单,将你的c结构移动到c ++ PIMPL

// A.h
class A
{
 //...
private:
 struct impl;
 static std::unique_ptr<impl> p_impl;
}

// A.cpp
std::unique_ptr<A::impl>  A::p_impl;

struct A::impl
{
  // c code here
}

答案 3 :(得分:0)

我的理解是C ++管理代码分配一块共享内存,而C和C ++代码协调将在哪里写入哪种类型的信息。 C ++管理器定期检查内存中的位置,知道它希望找到什么类型的数据。我想那时C ++代码有一个void*指向your_c_struct*指针的表来检查。在前一种情况下,可以通过reinterpret_cast<your_c_struct*>(void_ptr)随时转换这些内容。因此,我假设C ++代码有一个指向共享内存中C结构的指针表。在这种情况下,我认为解决方案是创建一个伪RAII类,它可以指向共享内存拥有的非拥有位置,并在堆上分配/解除分配内存。这看起来像是:

class Wrapper {
public:
    Wrapper() : owned(true), data(new your_c_struct{}) {}
    Wrapper(your_c_struct* _data) : owned(false), data(_data) {}
    ~Wrapper() {
        if (owned)
            delete data;
    }

    // copy constructors
    // overloaded comparison operators

private:
    bool owned;
    your_c_struct* data;
};

构造此类有两种主要方法:在堆上创建新对象或向其传递指向它不拥有的共享内存的指针。我已经成功地将这种技术用于GSL库,其中共享内存中的C结构的等价物是由GSL数值算法分配并返回的C结构。我进一步提出了第二个构造函数,并提供了一个命名构造函数Wrapper Wrapper::SoftWrap(your_c_struct* _data)