结合大型C和C ++程序

时间:2013-06-25 15:42:41

标签: c++ c unix visual-studio-2008 icc

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

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

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

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

最简单的方法是什么?

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

  1. 我应该用extern "C" {}
  2. 包装我的C头文件吗?
  3. 我应该在我的C函数中使用extern "C"吗?
  4. 或者我应该在我的C ++文件中使用extern "C"? (标题?函数?所有这些?或者只是我需要从C程序调用的那些?)
  5. 理解我不能有两个main函数。我可以简单地重命名我的C ++ main函数吗?
  6. 在unix中编译时,我是否应该将C(icc)和C ++(icpc)编译器用于不同的文件?还是仅仅是C ++编译器?
  7. 将我的main函数从C转换为C ++可能是一个选项(简化事情)吗?
  8. 如果我不需要在两个程序之间传递课程信息,我是否需要对它们做任何事情?
  9. 您建议以什么顺序解决此问题? (例如,首先让我的C程序由C ++编译器编译;其次,将两个代码编译在一起,没有链接;第三,链接代码;第四,在C ++中重命名main并让我的C代码“调用”;第五,实施信息传递?)
  10. 最后,每个程序中都有一些宏,它们是重复的(同名,相同的实现)。与此有冲突吗?我应该只保留一组宏吗?
  11. 很抱歉有长文和多个问题。我对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 ++。为此,使用.cpp扩展名(从Y.c更改为Y.cpp)重命名包含我的C代码的main函数的文件,并从{

      }更改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. 更改我的main C ++函数的名称,并(暂时)不使用任何参数。我将其命名为int Ynano()

    4. 使用以下行创建一个名为Y_NANO.h的新头文件(Y_NANO.cpp是包含最初主C ++函数的文件的名称):

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

      #include "Y_NANO.h"
      
    6. 从Y.cpp中的Ynano()函数调用函数main

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

    8. 这些步骤只会使程序一起运行,而不会在它们之间传递信息。要在程序之间传递信息,有必要将一些参数作为Ynano()的参数包含在内,但这是另一个故事。

      一些最终评论:

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

5 个答案:

答案 0 :(得分:7)

  

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

     

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

仅当您计划从某些C ++源文件中#include 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

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

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

extern "C" {

#include "my_c_header.h"

}

注意:完全不建议使用该解决方案,也不是一个长期/可维护的解决方案,它只是一个快速而肮脏的解决方案,只是让它工作&#34;解决方案通常会失败,但有时会起作用,具体取决于C标头的外观(C标头不需要包含许多其他标题,并且通常不应该这样,但有些作者不具备这样做的常识)。

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

  

3)或者我应该使用extern&#34; C&#34;在我的C ++文件中? (标题?函数?所有这些?还是只需要从C程序调用的那些?)

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

  4)理解我不能有两个主要的&#39;功能。我可以简单地重命名我的C ++&#39; main&#39;功能

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

  

5)在unix中编译时,我是否应该将C(icc)和C ++(icpc)编译器用于不同的文件?还是仅仅是C ++编译器?

使用C编译器编译C代码和C ++编译器来编译C ++代码。大多数构建系统(cmake,make等)都会自动执行此操作。从技术上讲,您可以尝试使用C ++编译器编译C代码,但不要期望它能够立即工作,甚至可以轻松使其工作,不值得恕我直言。

  

6)将主函数从C转换为C ++可能是一个选项(简化事情)吗?

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

否则,通常的选择是弄清楚你需要C ++代码做什么。然后,使用C ++库在C ++中创建一个C友好函数(没有类等)来完成这些工作。然后,创建一个声明该函数的头文件,使用extern "C"说明符(仅在C ++下编译(__cplusplus)),并确保此头不包含任何其他C ++头(不是标准的)标头,而不是C ++库中的任何其他标头)。最后,在您拥有main函数的C源代码中,包含该头文件并在main函数中调用您需要的函数。将整个事物连接在一起,它应该有效。

  

7)如果我不需要在两个程序之间传递课程信息,我是否需要对它们做任何事情?

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

  

8)你建议以什么顺序解决这个问题? (例如,首先将我的C程序编译为C ++编译器;其次,将两个代码编译在一起,没有链接;第三,链接代码;第四,在C ++中重命名main并使用#C;在我的C代码中调用&#34;五,实施信息传递?)

当然,第一步是确保您可以单独编译。第二步是查看是否可以使用C ++编译器编译C程序的主函数(仅主函数)(如上所述)。如果成功,请开始将C ++主函数中的元素合并到新的&#34;合并的#34;主功能。如果不成功,请按照我刚才提到的步骤进行操作。

  

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

MACROs ......这很难说。如果您按照创建可以从C main函数调用的C ++函数的过程,那么您基本上可以完全隔离这两个库,即它们是单独编译并在之后链接在一起。在这种情况下,冲突的MACRO没有问题(但是如果C ++库中有一些extern "C",可能会有相同名称的函数)。如果您尝试将主要功能合并到一个C ++主函数中,则可能会出现一些问题,即C头和C ++头之间存在冲突的宏,这些头将包含在一起。

答案 1 :(得分:2)

  1. 是,但包装如下所示:Combining C++ and C - how does #ifdef __cplusplus work?
  2. 如果它们在头文件中,则不需要
  3. 。如果它们不是那么你需要在你的C ++文件中有一个前向extern声明,如果它需要它,并且是extern“C”
  4. 这并不总是可行的,因为类和一些C ++典型的东西在C中不起作用。但是如果C ++代码实际上只是C,它也会起作用。在C ++中使用C比反过来容易得多。
  5. 重命名第二个主要功能是什么意思?它不会被调用,你只能有一个主要功能
  6. 您可以选择将c文件重命名为C ++并开始使用C ++编译所有内容。这将解决你与外部“C”的东西的联系问题,这是我首先要做的。否则,C使用C编译器编译,C ++使用C ++编译器编译。这些编译器的行为当然不同。
  7. 是的,当然,您的C代码可能需要一些返工。这就是我要做的事情
  8. 不这么认为。那么,似乎没有任何依赖?怎么会这样?
  9. 无关紧要,开始编译,然后修复链接器问题<​​/ li> 如果包含2个包含相同宏的头文件,则
  10. 可能会发生冲突。编译器会抱怨重新定义。

答案 2 :(得分:1)

要点:

我认为您可以在单独的CPP编译单元中编译C main(),然后在“extern C”中编译所有C函数定义。从CPP到C的呼叫很容易。反过来说有点麻烦,因为你必须创建“......用于公开C ++代码功能的C API ......” - 见How to call C++ function from C?

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

注意:将main()组合到一个CPP函数中。

详情:

  
    

以我的C程序“调用”C ++代码

的方式运行这两个程序   

我害怕这通常有点困难。 C ++做了一些名为name mangling的事情,所以从C语言调用C ++函数很难以便携方式完成,除非你已经创建了一个C包装器(参见上面的链接)。原因是CPP编译器(在内部没有看到这个)重写函数名称并包含诸如参数类型之类的东西,通常作为名称的后缀,以便它可以执行诸如函数重载之类的操作。 C编译器不这样做,因为在C中无法进行函数重载。

我认为最好从C ++模块运行你的main并从那里调用你的C函数......这样你就可以解决名称错误问题。

  
    

我应该用extern“C”{}

包装我的C头文件   

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

这样的东西
#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()函数需要调用这两种类型的代码,那么你就没事了。在CPP编译单元中编写的一个main()函数。

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

  
    

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

  

如果宏是在C / CPP文件中定义的那么你没问题。如果它们在头文件中,那么如果一个文件包含两个包含相同宏的头文件,则可能存在冲突。在任何一种情况下,我都建议将所有常见的宏输出到共享头文件中,这样只有一个宏的实例......更易于维护...请使用“不要重复自己”的口头禅:)

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

答案 3 :(得分:0)

好吧,理想情况下,您可以将C源代码编译为C ++而无需更改语义,然后只需享受统一的系统。根据您的代码库的大小及其形状值得考虑作为选项。实际上,这可能不如摆弄所有那些外部“C” - 和后果。

下一个选择是合作。 C ++旨在与C兼容,另一个方向在理论上并非如此,但它在实践中 - 我希望同一供应商的编译器支持所有方向的串扰。

最重要的是要注意的是,如果你添加C ++,那么你的所有系统都被认为是C ++,所以你必须注意一个定义规则,从C ++获得你的主要等等。你编译C源代码和C编译器将它们视为访客...您必须将编译器选项调整为兼容并对系统之间共享的所有标头进行调整。这通常意味着使用那些条件,extern“C”,typedefing结构到他们自己的名字等等。

使用交叉调用查看异常。在某些系统中,它们不得跨越C / C ++边界。在其他人他们可以,但你需要调整选项,使其运作良好。

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

答案 4 :(得分:-1)

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

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

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

然后你的任务不会是两个可执行文件的混合,而是构建一个调用两个不同库的新应用程序。

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

如果我是你,我会做的是首先弄清楚每个应用程序的工作原理。一步一步。查看代码的位置,并将其操作理论融入您的脑海。这样做可以让你以合适的方式打包或重新打包。