如何在C代码中模拟c++名称空间特性

How can one emulate the C++ namespace feature in a C code?

本文关键字:空间 c++ 模拟 代码      更新时间:2023-10-16

我正在开发一个软件,这是在c++中,但通过包含通信协议的共享头文件与C应用程序通信。由于C比c++"更基本",我总是需要用C代码编写头文件(因此c++也得到了它);否则,它将无法在第二个应用程序中工作。

问题是我需要使用作用域限定符,如c++ namespace s -这在C中不存在。

在C中模拟namespace特性的所有选项是什么?

到目前为止,我所看到的唯一可能性是在这个问题中显示的,但不幸的是,答案不够清楚,我当然想知道是否有其他选择。我还试图使用struct s来完成这项工作,但没有成功(至少考虑到enum操作器的struct)。

您可以在定义级别使用static隐藏从模块导出的所有导出函数,这样全局空间中就不会放置任何名称,而是将它们放在模块唯一提供的结构体中。

。foo:

struct ns_t {
    int (*a)(int, int);
    void (*b)(float, float);
};
extern struct ns_t ns;

foo.c:

#include "foo.h"
static int a(int x, int y) {
    ...
}
static void b(float x, float y) {
    ...
}
struct ns_t ns = { .a = a, .b = b };

bar.c:

#include "foo.h"
....
ns.b(4.5, 6.8);
....

完全不要模仿C中的命名空间,用C的方式:

  • 使用前缀代替命名空间
  • 使用后缀代替重载。
    • 可以选择使用_Generic宏来模拟参数类型的重载。

你的include文件应该在c++的内部细节命名空间中定义这些C函数(由于C链接,不改变函数的实际身份),然后你从c++的C函数中去掉前缀和后缀。
它看起来像这样:

#ifndef MY_HEADER_GUARD_unique_suffix
#define MY_HEADER_GUARD_unique_suffix
#ifdef __cplusplus
namespace my_module {
namespace detail {
extern "C" {
#endif
// Defines for common structs and functions here
// Also inline functions written in the common intersection of C and C++
#ifdef __cplusplus
}
}
using init = detail::my_module_init;
using close = detail::my_module_close;
}
#endif
#endif

您可能还希望在某些C结构中为c++接口添加成员函数,这些成员函数可能是委托给共享函数的内联函数。

首先,用名称空间为所有导出的符号(包括预处理器定义和枚举成员的名称)加上前缀。例如,可以有一个函数声明

void foo_bar_baz(void);

在c++端,这些需要在extern "C" { … }中包装,然后应该用正确的命名空间注册。假设使用c++ 11,对于函数,这应该像

一样简单。
namespace foo {
    namespace bar {
        constexpr auto baz = foo_bar_baz;
    }
}

在C端,您可以定义像

这样的缩写名
#define baz foo_bar_baz

使用特定于编译器的属性来注册别名或添加常量声明

static void (*const baz)(void) = foo_bar_baz;

这样做很好,因为调用函数实际上使用了函数指针(而不是指示符)。

你可以把所有的东西放在一个单独的头文件中,适当的#ifdef,或者你可以提供一个小的包装器,比如foo/bar.hxx (c++)和foo/bar-import.h(缩短的C名称),除了foo/bar.h(包含实际的前缀声明,将包括在其他头文件中)。

我一直在研究一个非常依赖于名称空间(即构建类型插入的名称空间)的系统,从我对C的观察来看,您可以不完美地模拟C++名称空间(无论如何在Linux上),方法是收集您导出的用于静态链接的符号,用objcopy作为前缀,然后宏翻译您的头代码(不包括包含)以匹配前缀符号集。

这样做的问题是宏不尊重作用域,所以假设你的库导出void foo()(或简称符号foo) -并且你将其前缀为mylib_foo,那么将foo转换为mylib_foo的宏也会不加区分地翻译名为foo的结构体成员,即使这些成员不应该被翻译。

我相信做正确的事情实际上需要破解编译器(请有人来做!):))。