问题 结合大型C和C ++程序


我已经阅读了几种结合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(从Y.c更改为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. 改变我的名字 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. 调用该功能 Ynano() 来自 main Y.cpp中的函数。

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

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

一些最终评论:

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

4113
2018-06-25 15:42


起源

风格说明:反引号不是强调。 :P它们用于突出显示由计算机输入或输入到计算机中的代码和其他逐字文本。 - cHao
@cHao,谢谢你清理它。我在这里比较新,所以我不确定格式:-P - Leonardo Trivino


答案:


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代码(或任何其他语言)调用的库的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头文件并且具有相当简单的主函数。如果是这样,在C ++编译器上编译就不难了(除非它包含的C头是很多其他C头,这是不好的做法,但很可能)。您将需要包含C头文件包含 extern "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 ++编译器编译C程序的主函数(仅主函数)(如上所述)。如果成功,请开始将C ++主函数中的元素合并到新的“合并”主函数中。如果不成功,请按照我刚才提到的步骤操作。

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

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


7
2018-06-25 16:27



该 extrern "C" { #include <my.h> } 是一个很大的禁忌。标题通常包括其他标题,包括库,标准,将它们放入该块是坏的。 - Balog Pal
@BalogPal:+1,你是对的,这是最后的手段。当你想要它工作时,值得一试。但这不是一个长期/可维护的解决方案。 - Mikael Persson
@MikaelPersson感谢您的回答。只是评论: 4。 也许我对这个问题不太清楚。目前,C和C ++程序都是独立的,因此每个程序都有自己的主要功能。我想要问的是我是否应该将当前的C ++主函数重命名为其他东西,正是为了能够从我的C代码中调用它。 - Leonardo Trivino
@LeonardoTrivino是的,您当然可以将C ++代码中的main函数重命名为其他内容,将其标记为 extern "C" 并从C代码中调用它。除了练习之外,我只是没有看到太多用途。使用直接内存参数(如数组指针等)而不是(argc,argv)参数创建不同的函数会更实际。否则,它与简单地执行C ++程序没有什么不同 system("/path/to/the/cpp/project/my_cpp_program"); 来自C代码。 - Mikael Persson


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

2
2018-06-25 16:01



通常你给编译器或makefile魔法只强制C或C ++编译,重命名文件并不是绝对必要的。 - Balog Pal
@philip谢谢你的回答。一些评论: 4。 也许我对这个问题不太清楚。目前,C和C ++程序都是独立的,因此每个程序都有自己的主要功能。我想要问的是我是否应该将当前的C ++主函数重命名为其他东西,正是为了能够从我的C代码中调用它。 7。 我在这一点上并不准确。 C程序中当前存在一些数组,需要由C ++程序传递和修改。我的意思是没有C ++中的类或对象需要传递给C程序。 - Leonardo Trivino
@Balog Pal:我知道,但编译具有c扩展名的C ++文件并不是一个好主意。这令人困惑。 - Philip Stuyck
@LeonardoTrivino:您可以从另一个主要调用重命名的main。 - Philip Stuyck
@PhilipStuyck:好的,谢谢 - Leonardo Trivino


概要:

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

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

注意:结合你的 main()进入一个CPP功能。

详情:

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

我害怕这通常有点困难。 C ++做了一些事情 名字错误所以从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() 函数需要调用这两种类型的代码然后你没事。唯一的那个 main() 写在CPP编译单元中的函数。

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

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

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

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


1
2018-06-25 16:03



我不同意从C调用C ++很难。两者都同样容易/困难。在这两种情况下,您都需要一个C接口,其中包含一些可以从其他语言中包含的标头。选择哪些调用主要取决于代码的关系(谁需要谁控制流程与谁提供服务)。在这种情况下,似乎C ++库是帮助C库的库。 - Mikael Persson
@Jimbo感谢您的回答。关于宏点,它们在两个头文件中,但我不认为会有任何C / CPP文件包括这两个头文件(除非我的主函数需要调用它们两个,我不确定但如果这是一个要求)。 - Leonardo Trivino
@Mikael:对你的评论做了一些挖掘并同意了一些例子...我认为如果C ++代码利用C ++特定功能(如重载等),C ++到C仍然通常更容易。在这种情况下,正如你所指出的那样,情况并非如此,所以无论哪种方式都很简单/困难。感谢您的反馈 :) - Jimbo
@Leonardo:一些基于Mikaels评论的更新。在回复你的评论时,如果宏是相同的,那么从维护的角度来看,我仍然认为一个文件更好。例如,如果您更改文件X中的宏A以解决错误,则必须在文件Y中更改它...更容易将它们全部放在一个地方:) - Jimbo


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

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

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

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

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


0
2018-06-25 16:26





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

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

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

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

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

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


-1
2018-06-25 15:51



有很多代码一起使用C和C ++而不使用库的代码示例。从C ++中消耗C实际上非常简单,反之则更成问题。然而他关于主要问题的问题令我感到困惑。您只能有一个main,只有那个将被执行。因此,如果两件事都需要运行,一个人会打电话给另一个人。如果是这种情况,C ++主要根据情况在开头的末尾调用C重命名的main函数。 - Philip Stuyck
我不是说这不可能,我说这不是最好的做法。 - plinth
@plinth嗯,首先感谢你的回答。第二,你是对的,我不喜欢它。如果我理解正确,你建议完全重新设计两个代码......好吧,这些是大代码,即使我理解大多数算法和信息流,我也不相信重新设计是正确的方法去。这些是工程代码,实际上我需要测试很多东西,甚至声称合并它们是个好主意(为此我实际上需要首先合并它们!)。可以在稍后阶段考虑重新设计。 - Leonardo Trivino
这也不错。这实际上是常见的做法。但是如果由我决定,我会使用C ++编译器编译所有遗留的C模块,以避免所有具有链接规范的任务,并为C ++接口的C接口创建额外的头文件,以便它可以在C模块中使用。然而,有很多实例嵌入式sw,硬件相关模块都在C中,而在它们之上的应用程序是用C ++编写的。因此,依赖关系是这样的,C ++模块使用C包含。没什么大不了的。 - Philip Stuyck
“这也不错。这实际上是常见做法。”当然 - 因为它是权宜之计,但是当代码包含全局变量,不正确的模块依赖性,不正确(或没有)抽象等时,它会产生各种问题 - 所有常见的实践都在单一的应用程序中,但却变成了一个活生生的地狱第一次在需要多个单独的上下文或多线程的不同应用程序中重新打包,或者...... - plinth