从C ++程序构建C程序

时间:2014-10-22 21:57:38

标签: c++ c c++11

那么,我想做什么:基本上我想从另一个c ++程序(主程序)构建一个C / C ++程序(准备执行)。

让我们举个例子: main.cpp中

int main()
{
   //do something
   //there I will modify source of program which I want to build 
   sourceCode = ""; //maybe from a file
   //then a function like this:
   buildProgramFromSource(sourceCode, "program.exe");
}

我想从main.cpp生成的程序源

int main()
{
    //do another thing
    int value1 = value From main.cpp
    int value2 = value frim main.cpp too
}

所以,我有源存储在main.cpp上,我想修改该源然后构建该程序。

我该如何实现?

或者,也许还有其他方法可以从主C ++程序生成.exe程序吗?

稍后编辑:假设环境没有安装任何编译器。编译器应该集成在主程序中。

换句话说,我不想使用 system() shellexecute 等功能,这些功能正在使用环境中的编译器。

该应用程序将在Windows下使用。

一个例子非常有用。

2 个答案:

答案 0 :(得分:2)

特定于操作系统。如果第二个(构建的)程序是独立程序或库(或第一个加载的代码),则情况会有所不同。

在Linux(和其他Posix系统)上,您可以生成C或C ++文件(通过使用常用的文本输出函数;我建议在内存中构建生成的C ++文件的一些AST)。然后你可以通过分支进程来运行一些C ++编译器(在简单的情况下,使用popen(3)system(3),或使用较低级fork(2)& execve(2),等等....)。有关详情,请参阅Advanced Linux Programming

然后,您可以使用dlopen(3)动态加载获取的共享对象,并使用dlsym

检索一些符号(函数指针)

Linux详细信息

  • 使用-rdynamic编译并链接主程序,即g++ -Wall -g -rdynamic main.cc -o prog;需要-rdynamic标志才能使程序函数从加载的代码中可见。声明加载函数的签名并声明全局函数指针很有用,例如:

     typedef int somefun_t (const std::string&);
     somefun_t *globfun;
    
  • 在运行时生成一个C ++文件genfoo.cc,其函数可由声明为extern "C"的主程序访问。

  • 将编译分成具有位置无关代码的共享对象(shared library):因此从程序内部运行:g++ -Wall -fPIC -g -O -shared genfoo.cc -o genfoo.so

  • 从您的主程序中调用dlopendlsym(请注意,dlopen如果您传递了一个没有任何/的文件名,会发生奇怪的事情:

      void* hdl = dlopen("./genfoo.so", RTLD_NOW);
      if (!hdl) 
        { std::cerr << "dlopen:" 
                    << dlerror() << std::endl; exit(EXIT_FAILURE); };
      globfun = dlsym(hdl, "func");
      if (!globfun) 
        { std::cerr << "dlsym func:" 
          << dlerror() << std::endl; exit(EXIT_FAILURE); };
    
  • 根据需要使用函数指针globfun,例如

    std::string s;
    int r = (*globfun) (s);
    
  • 如果你是认真的,你应该在退出前dlclose(hdl)

如果您只想编译并运行不同的程序,事情要简单得多:只需像往常一样编译生成的程序(gcc -Wall -g genfoo.cc -o genprog)并像往常一样执行它({{1} },system + fork,....)

如果目标系统没有任何C编译器(但也考虑tinycc能够从包含字符串的C代码在内存中编译execve),则不能这样做。您需要使用JIT compilationlibjitGNU lightning之类的asmjit库。 ....或者考虑嵌入一些脚本解释器,如LuaGuile

Windows详细信息

我不了解Windows,但传闻它具有相同的dynamic loading功能,例如LoadLibrary。如果要避免在运行时运行C编译器,您还需要一些JIT库。细节不同(libtcc

某些框架或库(PocoQtBoost ....)正在为这些动态加载功能提供系统中立的包装。


注意:在Linux上,您几乎可以执行dllexport(数十万)的批次,请参阅manydl.c。我的MELT系统(扩展GCC的特定于域的语言)正在生成C ++代码并且dlopen即时生成它。

另请注意,某些编程语言(如Common Lisp(以及Meta-Ocaml))更适合运行时evaluation&amp; meta-programmingmulti-staged)。值得注意的是,Common Lisp的SBCL实现能够编译任意表达式,然后在运行时运行它们!另请参阅J.Pitrat's blog

答案 1 :(得分:1)

最简单的解决方案可能是在程序的文件中包含一个独立的编译器。当程序需要重新编译时:

  1. 在系统上创建原始main.cpp源的另一个副本
    • 类似:Windows上的system("copy main.cpp new_main.cpp");
  2. 打开一个到new_main.cpp的流(假设您的代码名为新副本new_main.cpp
  3. 将附加代码附加到文件的结尾/开头
  4. 刷新并关闭输出流
  5. 调用编译器并将其传递给新文件的路径
    • 例如,如果您使用的是g ++,则可以编写类似:g++ new_main.cpp -o new_main.exe
    • 的内容
    • 如果您使用Microsoft Visual C ++编译器,则可以通过运行system("vcvarsall");(在我C:\Program Files (x86)\Microsoft Visual Studio 11.0\VC中找到),然后system("cl 'path/to/project/new_main.cpp');(在C:\Program Files (x86)\Microsoft Visual Studio 11.0\VC\bin中找到)来访问它我)。
  6. 在此之后你有new_main.exe,这是main.cpp的编译形式+一些额外的代码。

    警告:如果您分发应用程序,此选项将要求您安装编译器或包含其必需的文件。您不需要使用new_main.exe安装/包含它们,但是您需要使用编译new_main.exe的程序安装/包含它们。


    如果要从内存中编译,过程类似:

    1. 打开一个到main.cpp
    2. 的信息流
    3. main.cpp读取到char缓冲区(假设main.cpp仅包含ASCII字符)
    4. 关闭指向main.cpp
    5. 的信息流
    6. 将您的代码附加到缓冲区的开头/结尾(确保缓冲区中有空间!
    7. 调用编译器库
      • 此调用取决于您的编译器库,但类似于:compileCode(String_Version_Of_Code_Read_From_File);

    8. 作为额外的信息,如果您处于需要动态修改和构建程序的情况,通常会有更好的解决方案。情况并非总是如此,但通常会有更好的方法。

      第三种选择可能是编写自己的编译器,但我强烈反对。