如何使用 MSVC 在 C++ 中定义外部 C 结构返回函数?

Posted

技术标签:

【中文标题】如何使用 MSVC 在 C++ 中定义外部 C 结构返回函数?【英文标题】:How to define an extern, C struct returning function in C++ using MSVC? 【发布时间】:2010-04-27 04:59:38 【问题描述】:

使用 MSVC 编译器 (v15.00.30729.01) 无法编译以下源文件:

/* stest.c */
#ifdef __cplusplus
extern "C" 
#endif

struct Test;

/* NB: This may be extern when imported by another module. */
struct Test make_Test(int x);

struct Test  int x; ;

struct Test make_Test(int x)

    struct Test r;
    r.x = x;
    return r;


#ifdef __cplusplus

#endif

使用cl /c /Tpstest.c 编译会产生以下错误:

stest.c(8) : error C2526: 'make_Test' : C linkage function cannot return C++ class 'Test'
        stest.c(6) : see declaration of 'Test'

在没有/Tp 的情况下编译(告诉@​​987654325@ 将文件视为C++)可以正常工作。该文件还可以在 C 和 C++ 模式下在 DigitalMars C 和 GCC(来自 mingw)中正常编译。我还在 GCC 中使用了-ansi -pedantic -Wall,它没有任何抱怨。

由于我将在下面介绍的原因,我们需要将此文件编译为 MSVC 的 C++(而不是其他文件),但函数被编译为 C。本质上,我们想要一个普通的 C 编译器...除了大约六行。是否有一个开关或属性或我可以添加的东西来让它工作?


有问题的代码(虽然不是上面的;这只是一个简化的示例)是由代码生成器生成的。

作为其中的一部分,我们需要能够将浮点 nan 和无穷大生成为常量(长话短说),这意味着我们必须在 C++ 模式下使用 MSVC 编译才能真正做到这一点.我们只找到了一种可行的解决方案,它在 C++ 模式下工作。

我们将代码包装在extern "C" ... 中,因为我们想要控制修改和调用约定,以便我们可以与现有的 C 代码交互。 ...也因为我信任 C++ 编译器,就我可以扔一个小百货商店。我还尝试在extern "C++" ... 中包装just reinterpret_cast 行,但这当然行不通。可惜。

我发现了一个潜在的解决方案,它需要对声明进行重新排序,以便完整的结构定义出现在 函数 foward decl. 之前,但由于 codegen 的执行方式,这非常不方便,所以如果可以的话,我真的想避免走那条路。

【问题讨论】:

你知道,在你问 C++ 问题的整个过程中都在抨击 C++ 不会让观看 [c++] 的人有那么多帮助...... 我唯一的负面评论是关于 C++ 编译器。我有相当多的莫名其妙的差异和奇怪的行为,对它们非常警惕。如果有帮助,我也不相信函数式语言、脚本语言或任何比批处理文件更复杂的构建系统。我喜欢认为我平等地不信任所有技术。 :) 【参考方案1】:

这是一个有点棘手的错误消息,但调用者需要知道结构的大小才能进行调用。它需要在堆栈上为返回值预留空间。或者如果结构足够小,则期望寄存器中的返回值。

您必须在标题中声明结构。当你这样做时,错误消失了:

#ifdef __cplusplus
extern "C" 
#endif

struct Test  int x; ;
struct Test make_Test(int x);

struct Test make_Test(int x)

    struct Test r;
    r.x = x;
    return r;


#ifdef __cplusplus

#endif

【讨论】:

我发现令人困惑的是代码在编译为C时可以与MSVC一起使用。这是 C 模式下的 DMC、GCC 和 MSVC 更宽松,还是 C++ 模式下的 MSVC 要求更高? 这里的代码与可能是关键的原始代码之间存在差异......原始代码在定义之前先声明了 Test 和 make_test 。您已删除 Test 的不必要的前向声明,并将其 full 定义放在顶部。编译器现在知道 Test 是一个简单的 POD 类,并且可以将其用作返回值。我不认为头文件与它有任何关系。【参考方案2】:

这是一个有趣的问题。正如您所说,将代码编译为 C 代码正确不会产生错误。而且似乎只有 MSVC 在编译为 C++ 代码时会遇到问题。

由于其他 C++ 编译器的代码没有问题,这可能是 MSVC 中的一个错误,但我可以看到 MSVC 可能有这个错误的基本原理。当 C++ 编译器命中该行时:

struct Test;

这是 struct Test 的不完整声明 - 编译器不知道 struct Test 的完整定义是否包含 C++ 特定项(虚拟函数、继承等)。请注意,extern "C" 块中的类型仍然可以使用所有 C++ 工具; extern "C" 语言链接规范仅适用于“由声明引入的所有函数声明符、函数名和变量名的函数类型”(7.5/4“链接规范”)。

所以我可以看到当 MSVC 的 C++ 编译器遇到一个返回不完整类型的 extern "C" 函数时,它可能会决定此时需要返回一个错误,以防该类型不是纯 C -style POD 类型。

C++ 标准确实说(7.5/9“链接规范”):

从 C++ 到用其他语言定义的对象以及从其他语言到用 C++ 定义的对象的链接是实现定义和语言相关的。只有在两种语言实现的对象布局策略足够相似的情况下,才能实现这样的联动。

因此,如果 MSVC 有理由不允许 extern "C" 函数返回非 POD 对象,那么它可能有一些余地(标准方面),尽管我不确定为什么 MSVC 在其他 Windows 编译器不支持时会出现问题不。如果有人知道详细信息(或者如果他们知道我在这里只是单纯的外行),我将不胜感激。

这并不是说这对你有任何帮助 - 这只是我对基本原理的猜测。

如果不了解更多关于您的代码生成过程以及您可能如何影响它,我不确定您可能有哪些不错的选择 - 可能是对生成的文件进行后处理以分离出 的内容需要 编译为 C(或重新排列声明)。但我可以想象,这对于开始工作,尤其是在维护方面可能是一场噩梦。

【讨论】:

感谢您的详细解答。我决定在实际查阅 C++ 规范以解释发生了什么的基础上接受你的。并不是说汉斯的回答没有用。 :)【参考方案3】:

为什么在

中有extern
extern struct Test make_Test(int x)

    struct Test r;
    r.x = x;
    return r;

它不是外部的,你正在那里定义它。

【讨论】:

是的,我可能应该删除它。这就是目前生成代码的方式。删除 extern 后问题肯定仍然存在,所以我将其删除。

以上是关于如何使用 MSVC 在 C++ 中定义外部 C 结构返回函数?的主要内容,如果未能解决你的问题,请参考以下文章

如何验证编译时在 msvc 2010 c++ 项目中是不是正确设置了编译器选项?

如何使用system()函数(MSVC)编译c ++代码?

C++ Visual Studio Windows:如何在 dll 中声明但不定义外部函数

带有链接时代码生成的 MSVC 能否跨 C 和 C++ 进行优化?

在 CMake 项目中从 C++ 调用 C 代码。未定义的符号。有外部 C

我们可以将 c++ 标准编译与 MSVC 混合使用吗?