从C中调用C++方法

7

我已经有一段时间没有使用纯C了,现在我正在一个C++项目中开发一个API。这些方法大部分都是C方法,所有的返回值都是C结构体,除了一个需要返回vector<string>的方法。现在我的问题来了,C++的方法/库/其他能否从C中调用?我问这个问题是因为我不知道使用API的人会使用C还是C++编写代码,我觉得我应该只返回C结构体。那么这就要求我返回一个char**,对吗?

如果上面的内容不太清楚,请看以下简短版:

简短版 - 如果一个C++方法返回一个C结构体,我能从C中调用它吗?如果可以,vector<string>最好(唯一?)等价的返回值是什么?char**吗?

更新:这些C++方法只是全局方法,没有类或面向对象的东西。除了我的vector问题之外,唯一与C++相关的东西是一些stringstreams

6个回答

6

不可以,C语言不能使用C++语言中不可用的功能。但是,C代码可以间接地使用C++代码。例如,您可以使用C++实现一个C函数,并在接口中使用不透明类型,以便签名使用void*,但实现使用C++类。

C语言中与vector<string>相当的可能更接近于:

 typedef const char* c_string_type;
 typedef struct c_string_array {
     c_string_type* c_strings;
     int c_strings_count;
  } c_string_array_t;

使用不透明类型,你将拥有如下所示的东西:
 typedef void* c_string_array_t;
 int c_string_array_length(c_string_array_t array);
 const char* c_string_array_get(c_string_array_t array, int index);

在C++实现中,您可以将std::vector*秘密地转换为void*。


为什么不使用结构体来保存字符串的长度呢?这样可以避免C用户在已知长度时对每个字符串进行strlen操作。 - Puppy
@DeadMG:不要假定知道用户会如何使用他的数据。你可能会无缘无故地招致性能惩罚。它们甚至可能不是文本字符串。 - Matt Joiner
@Michael:不要使用 _t,这是 POSIX 保留的。 - Matt Joiner
@Matt Joiner:如果你有一个指向数组的指针,没有大小信息,它里面的内容就毫无意义。 - Puppy
char *blah[] = {"are", "you", "sure?", NULL}; return blah; - Matt Joiner
@Matt,我了解。然而,在实践中,只要你在类型前缀加上库的名称(例如“mylibrary_cstring_type_t”),那么你就不会遇到命名冲突的问题。 - Michael Aaron Safyan

6
只要给出C可见的函数名(原型等在ABI级别被忽略),您可以从C中调用任何东西。当然,如果C不能按预期方式生成参数,则不能期望正确的结果。通常,显而易见的解决方案是将界面简化到C级别。char **是与vector<string>具有最大公约数的绝佳选择。不仅如此,如果您知道打算使用它做什么,可能更快(并且更清晰)。关于C的可见性:函数名不能与任何其他C可见函数共享。如果您希望从C中调用C++函数,则这可能是一个很好的原型示例:
extern "C" char **lots_of_strings();

如果参数签名不同,C++会允许您重载仅对C++可见的函数,并使它们与C版本共存。
vector<string> lots_of_strings(int);
extern "C" char **lots_of_strings();

如果您想提供多种适用于不同编程语言的调用方式,可以尝试以下方法(忽略延迟初始化的弊端和C语言中存在的bool类型):

bool lots_of_strings(vector<string> &);
extern "C" int lots_of_strings(char ***);
Whatever lots_of_strings(SomeArrayType &);

请注意,C++在每种情况下都会选择与调用站点最匹配的定义,而C将采取它所能获得的任何内容(通常是一个具有匹配名称的函数)。

您会发现通过将#ifdef与宏__cplusplus结合使用,可以将C++特性隐藏在C中。


即使示例接口不是我的首选,但为C和C++提供不同的接口也值得加1分。 - David Rodríguez - dribeas
我担心你的第一个选择会是什么。根据OP的要求,除了vector<string>char **之外的任何东西都是立即过度设计。 - Matt Joiner

5
请参考这个FAQ。基本上,您不能调用C++方法(成员函数),但是如果它们使用extern C声明,则可以调用独立函数。char **不是唯一的选择,但它可能是最直接的。您可以返回动态分配的char *数组。您将不得不使用输出参数向调用者提供长度(您可以将其NULL终止,但这可能不理想)。例如:
char **get_string_list(size_t *len)
{
  char **array;
  size_t actual_len;
  // ...
  *len = actual_len;
  array = (char **) malloc(sizeof(char *) * actual_len);
  // ...
  return array;
}

你必须释放内存,可以提供一个函数或者详细说明调用者如何做到这一点。别忘了如果字符串是动态分配的,则需要释放它们。


我猜在相关的问题上,您是否可以释放在C++中使用new分配的内存? - Falmarri
8
简单的情况可能在某些编译器上巧合地起作用(因为“new”通常是用“malloc”实现的),但这肯定是错误的。 - Matthew Flaschen

0
如果您有可能修改所调用的代码,我建议将该函数从返回向量更改为类似以下方式的某个内容:

unsigned int MyFunction(char* buff, unsigned int numLines, unsigned int stride)

其中numLines是分配字符串的数量,stride是字符串的大小。这样你的函数就不需要分配任何你后来需要担心的内存。一切都由调用者处理。函数的返回值是使用的字符串数量。


0

我之前读过一些关于这个的内容,如果我没记错的话,你可以使用能够在 C 编译的 c++ 代码/结构。但是有一些关于静态初始化的事情,如果我理解正确的话,你应该使用 c++ 编写主函数来保证这个静态初始化被完成,C 的主函数并不会完成这个初始化。


我们不会在主函数或类似的地方编写任何内容。我们正在编写一个API来与我们的套接字服务器进行交互。 - Falmarri

0

如果您打算为C和C++提供相同的功能,我建议尝试提供两个入口点,以便您可以将一个适应另一个。请注意,虽然您可以使用可从C和C++中使用的接口,但在大多数情况下,C接口不适合于C++使用(使用char **可能是C的好解决方案,但用户需要将其转换回C++类型并执行清理可能会使用户代码混乱),反之亦然。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接