12

我已经阅读了几种组合 C 和 C++ 代码的方法,但是,在我的情况下,我仍然对如何进行感到困惑。这是我的问题:

我有相对大量的 C 代码(由各种文件组成) .c.h用于对有限和离散元素中的实体进行建模。这段代码有一个相对较短和简单的主函数,其中有一个for循环,其中各种其他函数(来自其他文件)被顺序调用。此代码在 Unix(icc 编译器)和 Visual Studio 中编译时都可以正常工作。

我有其他解决分子动力学相互作用的 C++ 代码。该代码还包含各种文件,并且在 Unix(icpc 编译器)和 VS 中运行良好。两者都是具有自己的输入和输出文件集的独立程序。

我需要做的是以我的 C 程序在其主循环中“调用”C++ 代码的方式运行这两个程序。一些信息需要在两个代码之间双向传递,可能是数组(或指针)的形式。

最简单的方法是什么?

特别是,根据我阅读的建议,我有多个问题:

  1. 我应该用 包装我的 C 头文件extern "C" {}吗?
  2. 我应该extern "C"在我的 C 函数中使用吗?
  3. 还是应该extern "C"在我的 C++ 文件中使用?(标题?函数?全部?还是只有我需要从 C 程序调用的那些?)
  4. 在了解我不能有两个main功能。我可以简单地重命名我的 C++main函数吗?
  5. 在 unix 中编译时,我应该对不同的文件同时使用 C (icc) 和 C++ (icpc) 编译器吗?还是只是 C++ 编译器?
  6. 将我的main函数从 C 转换为 C++ 是否是一种选择(简化事情)?
  7. 如果我不需要在两个程序之间传递类信息,我需要对它们做些什么吗?
  8. 您建议按什么顺序解决这个问题?(例如,首先让我的 C 程序由 C++ 编译器编译;其次,将两个代码一起编译,不链接;第三,链接代码;第四,main在 C++ 中重命名并让我的 C 代码“调用”它;第五,实现信息?)
  9. 最后,每个程序中都有一些宏,它们是重复的(同名,同实现)。与此有冲突吗?我应该只保留一组宏吗?

很抱歉,很长的文字和多个问题。我对 C 比较陌生,甚至对 C++ 也比较陌生,所以即使我对这些程序的词汇量也很有限。

谢谢您的帮助。任何提示将不胜感激。如果您需要更多信息,请告诉我。

这是我的 C 代码的“主要”功能:

#include "Yproto.h"
void show_time_info(YDC ydc,CHR Ystage[3]);

main(argc, argv)
  INT argc; char **argv;
{ CHR c1name[300];         /* name of the problem i.e. input file */
  struct YD_struct yd;     /* Y database                          */
  YDC ydc=&(yd.ydc);       /* Y control database                  */
  YDE yde=&(yd.yde);       /* Y element database                  */
  YDI ydi=&(yd.ydi);       /* Y interaction database              */
  YDN ydn=&(yd.ydn);       /* Y node database                     */
  YDB ydb=&(yd.ydb);       /* Y borehole database                 */
  YDS yds=&(yd.yds);       /* Y source (inter. fluid) database    */
  YDO ydo=&(yd.ydo);       /* Y output database                   */
  YDPE ydpe=&(yd.ydpe);    /* Y property database  for elements   */
  YDPN ydpn=&(yd.ydpn);    /* Y property database  for nodes (BC) */
  YDPJ ydpj=&(yd.ydpj);    /* Y property database  for joints     */
  YDPM ydpm=&(yd.ydpm);    /* Y property database  for meshing    */
  INT Tctrlc, itimes=0;
  CHR *p=NULL;

  /* get name of the problem */
  if(argv[1]!=NULL)
  { CHRcpy(c1name,argv[1]);
  }
  else
  { CHRwcr(stdout);
    CHRw(stdout,"  please define input file names: "); CHRwcr(stdout);
    CHRw(stdout," >");
    fgets(c1name,sizeof(c1name),stdin);
    if((p=strrchr(c1name,'\n'))!=NULL) *p = '\0';
  }
  strcpy(ydc->cfiname, c1name);   ydc->cfiname[255]='\0';
  ydc->finp=FILENULL; ydc->fcheck=FILENULL;

  /* Process while any input */
  while(Yrd(c1name,&yd)>0)
  { itimes=itimes+1;
    CHRw(stdout,"NEW INPUT: "); CHRw(stdout, c1name); CHRwcr(stdout);
    if(Ycheck(&yd)<0) break; date_and_time(ydc->cruntime); timestamp();
    CHRw(stdout, "Start calculating ...\n");
    omp_set_num_threads(8);
    for(ydc->ncstep=ydc->ncstep;ydc->ncstep<ydc->mcstep;ydc->ncstep++)
    { show_time_info(ydc,"Ymd");                      /* show time information    */
      Ymd(ydc,yde,ydi,ydn,ydpe,ydpn,ydpm);            /* mesh elements            */

      /********** HERE IS WHERE I WOULD LIKE TO CALL MY C++ PROGRAM ***************/

      Yfd(ydc,yde,ydn,ydi,ydo,ydpe,ydpn,ydpj);        /* nodal forces             */
      Ybor(ydc,yde,ydn,ydb,yds,ydpe,ydpj,ydpn);       /* borholes, inter. fluid   */
      Ycd(ydc,yde,ydi,ydn,ydpe,ydpn);                 /* contact detection        */
      Yid(ydc,yde,ydi,ydn,ydo,ydpe,ydpn, ydpj,ydpm);  /* interaction              */
      Yod(c1name,&yd);                                /* output results           */
      Ysd(ydc,yde,ydn,ydo,ydpe,ydpn );                /* solve equations          */
      Yfrd(ydc,yde,ydi,ydn,ydpe,ydpn,ydpj,ydpm);      /* fracture                 */
      ydc->dctime=ydc->dctime+ydc->dcstec;            /* update time              */
      /* CTRL-C Interruption */
      Tctrlc = enablc(ydc->dctime, ydc->ncstep, ydc->mcstep);
      if(Tctrlc!=1) break;
    }
  }

  /* Termination */
  CHRw(stderr,"   ***** Y HAS ORDERLY FINISHED *****");  CHRwcr(stderr);
  CHRw(stderr,"Press a key to continue");  CHRwcr(stderr);
  getchar();
}

回答后 24 小时更新

我按照提供的答案遵循了建议,结果证明我的问题的解决方案比最初想象的要简单得多(尽管在让它工作之前我确实必须探索几个选项)。最好的部分是它可以在 Unix 和 Visual Studio 中运行。以下是我采取的步骤的摘要:

  1. 将我的主 C 文件转换为 C++。为此,将包含main我的 C 代码函数的文件重命名为 .cpp 扩展名(从 Yc 更改为 Y.cpp),并将main函数的开头更改为:

    main(argc, argv)
      INT argc; char **argv;
    

    int main(int argc,char **argv)
    

    为了使它 C++ '友好'。(注意:我知道将文件重命名为 .cpp 不是必需的,但我认为为了清楚起见最好这样做)。

  2. 包装我所有的 C 头文件

    #ifdef __cplusplus
    extern "C" {
    #endif
    

    一开始,并且

    #ifdef __cplusplus
    }
    #endif
    

    在最后。

  3. 更改我的mainC++ 函数的名称并(暂时)不使用任何参数。我命名它int Ynano()

  4. 创建一个名为 Y_NANO.h 的新头文件(Y_NANO.cpp 是包含原始 C++ 主函数的文件的名称),其中包含以下行:

    int Ynano();
    
  5. 在 Y.cpp 和 Y_NANO.cpp 中包含新标头:

    #include "Y_NANO.h"
    
  6. Ynano()mainY.cpp 中的函数调用该函数。

  7. 要在 Visual Studio 中编译,只需将所有源文件放在同一个文件夹中并创建一个新项目。在 Unix 中,我按照此处给出的步骤进行操作。

这些步骤只会使程序一起运行,而不会在它们之间传输信息。为了在程序之间传输信息,有必要包含一些参数作为 的参数Ynano(),但那是另一回事了。

最后的一些评论:

  • 在不同的头文件中重复宏的问题似乎不是一个真正的问题,只要没有文件包含两个头文件(我不需要对此做任何事情)。
  • 感谢所有提供答案的人。他们真的很有帮助。选择的答案是在完整性的基础上选择的,但其他答案也一样好。我希望该线程可以帮助其他人完成他们的工作,因为许多其他线程也帮助我做同样的事情。
4

5 回答 5

9

1) 我应该用 包装我的 C 头文件extern "C" {}吗?

2) 我应该extern "C"在我的 C 函数中使用吗?

仅当您计划#include从某些 C++ 源文件中获取 C 头文件时,即,如果您想从 C++ 代码中调用其中一个 C 函数。使 C 头文件在 C++ 中可用的典型方法是这样的:

#ifndef MY_C_HEADER_H
#define MY_C_HEADER_H

#ifdef __cplusplus
extern "C" {
#endif

/* All the original content of the C header */

#ifdef __cplusplus
}
#endif

#endif

如果您不想修改标头,也可以在extern "C"将标头包含在 C++ 源文件中时简单地从标头外部应用:

// in my_source.cpp (or some C++ header file):

extern "C" {

#include "my_c_header.h"

}

注意:完全不推荐使用该解决方案,也不是长期/可维护的解决方案,它只是一个快速而肮脏的“让它工作”的解决方案,经常失败,但有时会起作用,具体取决于 C标头看起来像(C 标头不需要包含许多其他标头,通常也不应该包含,但有些作者没有这样做的常识)。

的原因extern "C"是禁用 C++ 名称修改,即告诉编译器函数应该被编译为对应于未修改的符号和/或应该在未修改的符号表中查找(当链接到它们时)。所以,规则很简单,任何你想编译到库中的 C++ 函数都需要被声明为extern "C". 您在 C++ 代码中调用但链接到从 C(或任何其他语言)编译的库的任何函数声明也必须如此extern "C"

3) 或者我应该在我的 C++ 文件中使用 extern "C" 吗?(标题?函数?全部?还是只有我需要从 C 程序调用的那些?)

如果您想从您的 C 代码中调用一些 C++ 函数,那么必须extern "C"在编译该 C++ 代码时声明这些特定函数。在声明这些函数的 C 头文件中(为了从 C 代码中调用它们),不需要extern "C"(它总是隐含在 C 中)。

4)了解我不能有两个“主要”功能。我可以简单地重命名我的 C++ 'main' 函数吗?

两个主要功能的目的是什么?这是不允许的,也没有用。你仍然可以只有一个“程序”,一开始一结束,即一个主要功能。您必须选择一个主要功能,并添加您想要的任何额外步骤(调用另一个库)。换句话说,您必须“合并”主要功能。

5) 在 unix 中编译时,我应该对不同的文件同时使用 C (icc) 和 C++ (icpc) 编译器吗?还是只是 C++ 编译器?

您使用 C 编译器编译 C 代码,使用 C++ 编译器编译 C++ 代码。大多数构建系统(cmake、make 等)无论如何都会自动执行此操作。从技术上讲,您可以尝试使用 C++ 编译器编译 C 代码,但不要指望它可以立即工作,甚至根本不能轻松让它工作,恕我直言,不值得付出努力。

6) 将我的主函数从 C 转换为 C++ 是否是一种选择(简化事情)?

那是一种选择。你的包含C main 函数的源文件看起来比较简单,它包含一个C 头文件并且有一个相当简单的main 函数。如果是这样,这将不难在 C++ 编译器上编译(除非它包含的 C 头文件是许多其他 C 头文件,这是不好的做法,但很可能)。您需要将 C 头文件包含extern "C" { }如上所示。然后,您可以尝试在 C++ 编译器中编译它(仅包含 main 函数的源文件),然后使用 C 编译器编译其余的 C 代码,然后将整个内容链接在一起。如果这可以立即工作,那么很好,您可以开始将该 C 主函数与其他库中的 C++ 主函数合并,您就可以开始了。

否则,通常的选择是弄清楚你需要 C++ 代码做什么。然后,使用 C++ 库在 C++ 中创建一个 C 友好函数(无类等)来执行这些操作。然后,使用说明符创建一个声明该函数的头文件extern "C"(仅在 C++ 下编译时__cplusplus( )。最后,在您拥有主函数的 C 源代码中,包含该标头并从主函数中您需要它的位置调用该函数。将整个事情联系在一起,它应该可以工作。

7)如果我不需要在两个程序之间传递类的信息,我需要对它们做些什么吗?

不,只要您不包含 C 代码中的任何 C++ 标头(编译器无论如何都不会接受),C 代码就不会知道类甚至存在。所以,这里没有危险。

8) 你建议按什么顺序解决这个问题?(例如,首先让我的 C 程序由 C++ 编译器编译;其次,将两个代码一起编译,没有链接;第三,链接代码;第四,在 C++ 中重命名 main 并让我的 C 代码“调用”它;第五,实现转移信息量?)

当然,第一步是确保您可以分别编译两者。第二步是看是否可以用C++编译器编译C程序的主函数(只有主函数)(如上所述)。如果成功,开始将 C++ 主函数中的元素合并到新的“合并”主函数中。如果不成功,请按照我刚才提到的步骤操作。

9)最后,每个程序中都有一些宏,它们是重复的(同名,同实现)。与此有冲突吗?我应该只保留一组宏吗?

宏...这很难说。如果您按照创建一个可以从 C 主函数调用的 C++ 函数的过程进行操作,那么您基本上可以完全隔离这两个库,即它们分别编译并在之后链接在一起。在这种情况下,冲突的 MACRO 不会有问题(但可能存在同名的函数,如果某些函数extern "C"在 C++ 库中)。如果您尝试将主函数合并到一个 C++ 主函数中,您可能会遇到一些问题,即 C 头文件和 C++ 头文件之间的 MACRO 冲突,它们将被包含在一起。

于 2013-06-25T16:27:43.543 回答
2
  1. 是的,但是像这里解释的那样包装:Combining C++ and C - how does #ifdef __cplusplus work?
  2. 如果它们在头文件中,则不需要。如果它们不是,那么您需要在 C++ 文件中使用前向 extern 声明(如果需要),并且使用 extern "C"
  3. 这并不总是可能的,因为类和一些 C++ 典型的东西在 C 中不起作用。但如果 C++ 代码真的只是 C,它也可以工作。在 C++ 中使用 C 比反过来要容易得多。
  4. 重命名第二个主要功能有什么意义?它不会被调用,你只能有一个 main 函数
  5. 您可以选择将 c 文件重命名为 C++ 并开始使用 C++ 编译所有内容。这将解决您与外部“C”内容的链接问题,这也是我首先要做的。否则 C 用 C 编译器编译,C++ 用 C++ 编译器编译。当然,这些编译器的行为不同。
  6. 是的,当然,您的 C 代码可能需要一些返工。这就是我会做的
  7. 不这么认为。再说一次,似乎没有任何依赖关系?怎么会这样 ?
  8. 没关系,开始编译,然后修复链接器问题
  9. 如果包含 2 个包含相同宏的头文件,可能会发生冲突。编译器将抱怨重新定义。
于 2013-06-25T16:01:06.550 回答
1

概括:

我认为你可以main()在一个单独的 CPP 编译单元中编译你的 C,然后“extern C”你所有的 C 函数定义。从 CPP 调用 C 很容易。另一种方法有点麻烦,因为您必须创建“...用于公开 C++ 代码功能的 C API...” - 请参阅如何从 C 调用 C++ 函数?

编辑:以上编辑感谢 Mikael 的反馈(见评论)。看着它,我认为如果 C++ 代码利用 C++ 特定功能(如对象重载等),C++ 到 C 通常仍然更容易,因为它可能需要 C API wappers(见上面的链接)。在这种情况下,正如 Mikael 指出的那样,事实并非如此,因此任何一种方式都一样容易/困难......

注意:将两个main()'s 合并到一个 CPP 函数中。

细节:

以我的 C 程序“调用”C++ 代码的方式运行这两个程序

恐怕这通常有点困难。C++ 做了一些名为name mangling的事情,因此从 C 调用 C++ 函数很难以可移植的方式进行,除非您制作了 C 包装器(请参阅上面的链接)。原因是 CPP 编译器(内部没有你看到这个)重写了函数名称并包括参数类型等内容,通常作为名称的后缀,以便它可以执行函数重载等操作。C 编译器不会这样做,因为在 C 中函数重载是不可能的。

我想说从 C++ 模块运行你的 main 并从那里调用你的 C 函数会更好......这样你就可以解决名称修改问题。

我应该用 extern "C" {} 包装我的 C 头文件吗

是的,在 C 头文件中用 this 包装所有函数定义很重要。通常你会看到类似

#ifndef HEADER_FILE_NAME
#define HEADER_FILE_NAME
#ifdef __cplusplus
   extern "C" {
#endif
/// FILE CONTENTS
#ifdef _cplusplus
   }
#endif
#endif // HEADER_FILE_NAME

它的作用是告诉 CPP 编译器这些函数名称应该被破坏。这样在与 C 函数链接时将使用正确的符号名称。

编译 CPP 模块__cplusplus时应定义,但编译 C 模块时不应定义。这意味着当 CPP 模块包含您的 C 头文件时,它不会破坏函数名称,因此可以正确调用 C 函数。

或者我应该在我的 C++ 文件中使用 extern "C" 吗?

extern "C" 只是告诉编译器该函数具有 C 语言链接,因此生成的符号不会被破坏。所以我认为,如果它是一个尚未重载的函数,那么执行此操作(在函数定义的 H 文件中)将防止函数名称被破坏,因此您可以从 C 中调用它。但是,如果您例如,extern "C" 一个类,它仍然具有 C++ 链接,对于类成员函数等...取决于您是否使用这些...从您的代码示例中看起来并不像它。

在了解我不能有两个主要功能。我可以简单地重命名我的 C++ 主函数吗?将我的主要功能从 C 转换为 C++ 是否是一种选择(简化事情)?

是的,我认为这是最好的选择。如果只有main()函数需要调用这两种类型的代码,那么你就可以了。main()在 CPP 编译单元中编写的一个函数。

但是,如果有一个 C 模块需要调用 C++ 模块,那么您将需要考虑将其编译为 CPP 文件或确保 CPP 函数是否extern "C"重载。

最后,每个程序中都有一些宏,它们是重复的(同名,同实现)。与此有冲突吗?我应该只保留一组宏吗?

如果宏是在 C/CPP 文件中定义的,那么您就可以了。如果它们在头文件中,那么如果一个文件包含两个包含相同宏的头文件,则可能会发生冲突。在任何一种情况下,我都建议将所有常见的宏放到共享头文件中,这样只有一个宏实例......更易于维护......遵循“不要重复自己”的口头禅:)

我还没有解决你所有的观点,但希望这足以让你开始:)

于 2013-06-25T16:03:08.843 回答
0

好吧,理想情况下,您可以在不改变语义的情况下将 C 源代码编译为 C++,然后享受统一的系统。根据您的代码库的大小及其形状值得考虑作为选项。实际上,它可能比摆弄所有那些 extern "C"-s 和后果要少得多。

下一个选择是合作。C++ 旨在与 C 兼容,另一个方向在理论上不是这样,但在实践中是这样的——我希望同一供应商的编译器支持全方位的串扰。

需要注意的最重要的事情是,如果您添加 C++,那么您的所有系统都将被视为 C++,因此您必须注意一个定义规则,让您的 main 来自 C++,等等。您使用 C 编译器编译 C 源并将它们视为来宾...您必须调整编译器选项以兼容并配置系统之间共享的所有标头。这通常意味着使用这些条件,extern "C",将结构类型定义为它们自己的名称等等。

使用交叉调用处理异常。在某些系统中,它们不得跨越 C/C++ 边界。在其他情况下,他们可以,但您需要调整选项以使其正常工作。

在第一遍中,您只需要让这些东西像以前一样执行。对于以后重构的建议,这里有关于 SO 和其他地方的好问题。

于 2013-06-25T16:26:26.400 回答
-1

我会告诉你正确的答案,你可能不会喜欢它。在不了解代码的所有细节的情况下,听起来您需要进行一些繁重的重构。

在理想的世界中,当您编写应用程序时,应该以这样的方式编写应用程序,即实现作为正式的 API 完成,并且 main() 将命令行参数转换/解析为对 API 的适当调用。如果正确完成,具有 main() 例程的文件只需要为其任务构建可执行文件。

更好的是,该实现将构建为一个库和一组用于使用该库的头文件。

那么您的任务将不是混合两个可执行文件,而是构建一个调用两个不同库的新应用程序。

你不喜欢它的原因是这很耗时。做一个正确的设计来制作一个库而不是拼凑一些类/函数来完成任务是很耗时的,但是除非你现有的代码组织得很好,否则这是为了节省你处理的时间最终会出现代码问题。

如果我是你,我会首先弄清楚每个应用程序是如何工作的。穿过它们。查看代码的去向,并将其操作理论带入您的脑海。这样做可以让您以合适的方式打包或重新打包它。

于 2013-06-25T15:51:55.557 回答