65.9K
CodeProject 正在变化。 阅读更多。
Home

Gecko!应用程序的嵌入式 C++ 脚本

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.93/5 (22投票s)

2003年9月1日

6分钟阅读

viewsIcon

248144

downloadIcon

2598

在您的项目中嵌入 C++ 编译器,将 C++ 用作已编译的“脚本”语言!

什么?你疯了吗?

脚本语言是一个时髦的东西。以 Bethesda Softworks 的游戏 Morrowind 为例。该游戏使用自定义语言运行大量脚本:在运行游戏内部之前,将大量脚本编译为伪字节码形式。Ensemble Studios 的游戏 Age of Mythology 也是如此:它使用自定义脚本语言进行计算机 AI 编程。因此,这是几个程序中的关键部分,程序的运行速度部分取决于它们。

我喜欢速度。我喜欢快速的程序,精心设计的应用程序。我一直在寻找速度,而且我希望,就像你们中的许多人一样。然后我想,当我受到启发时:为什么不嵌入一个 C++ 编译器来将 C++ 用作已编译的“脚本”语言?

我考虑了著名的 GNU GCC 项目。如果您不知道它,它是一个开源的 C/C++/Java/其他编译器,主要面向 Linux 平台。这一套开发工具的最大优点是它是免费的。我想我能够将 GCC 源代码编译成一个漂亮的 Windows DLL;事实证明这几乎是不可能的。我发现了 MinGW 项目。这很好:编译的编译器 + Windows 和标准头文件 + 库在一个相对小的包(15MB)中。

当然,15MB 的包对某些项目来说确实很大。但本文主要面向像游戏这样的大型项目。而且,如果您只保留 MinGW 包中所需的文件(头文件 + 库 + c++ 编译器),您可以轻松地将其压缩到 4MB 以下。

而且,如果您不想让最终用户修改或创建新脚本,实际上并不需要嵌入 MinGW。

实际上 Gecko 带来了世界上最快的“脚本”语言!请参阅 The Great Computer Language Shootout,看看与一些知名语言相比,gcc(甚至是 g++)的速度有多快。此外,使用本地机器码(已编译的 C++)可以实现非常低的内存使用,与其他需要解释器或字节码解释器的脚本语言相比。此外,借助 MinGW 提供的库和头文件,您拥有无限的可能性:使用 windows.h 函数、math.h 函数、IO 函数……

另一件事:您可能想知道:为什么是 Gecko?Gecko 代表“Gnu Embedded C++ KOmpiler”(我那天真的很有灵感:)。Gecko 生成 ECK 文件。ECK 代表 “Embedded C++, Kompiled”。如您可能已经猜到的,ECK 文件实际上是重命名的 DLL 文件。

但 Gecko 是什么?

Gecko 实际上包含几个部分

  • 用于脚本功能的嵌入式 C++ 编译器的想法,
  • GeckoSetup 工具为您的方便生成了大量代码(GeckoSetup 依赖于 PXPerl 命名空间)。
  • CGecko 类。示例用法
CGecko gecko;


if (!gecko.IsEckUpToDate("test"))
  gecko.Compile("test");

if (gecko.Load("test"))
{
  if (gecko.RunThreaded())
  {
    printf("[Press any key to abort]");
    getchar();
    gecko.AbortThread();
  }
}

视觉上

CGecko 类 - 公共方法

CGecko 类依赖于 CRedirect 类来重定向标准 IO,同时运行 MinGW(在 codeproject 上找到的类——非常感谢它的作者)。

在使用函数之前,请仔细阅读每个注释。

class CGecko : protected CRedirect
{

public:
  // ctor does nothing special.
  // dtor will call AbortThread() and Unload() if you ommit them.
  CGecko(void);
  ~CGecko(void);


  // Normally initialization is done automatically 
  // thanks to GeckoSetup code
  // so you shouldn't use these functions. Anyway they 
  // are self-explanatory.
  void SetMinGWDir(LPCSTR szDir);
  void SetGeckoDir(LPCSTR szDir);


  // Tests if a ECK file is up-to-date, by checking the
  // INI written when compiled.
  // If the ECK file or the INI file can't be found,
  // eckIsUpToDate() returns false.
  bool eckIsUpToDate(LPCSTR szName);
  // Tests if ECK file exists.
  bool eckExists(LPCSTR szName);
  // Launches compilation/linkage of the "szName.cpp" file.
  // Compiler/linker outputs will go to virtual functions below.
  // WARNING: if you try to re-compile to an ECK 
  // previously loaded, even unloaded,
  // the linker will complain: 'Unable to write(...)'. That's 
  // because I don't manage to have
  // the ECK file unmapped from process. See eckUnload() below.
  bool eckCompile(LPCSTR szName);

  // Overridable functions called when compiling.
  virtual void OnCommandStart(LPCSTR lpszCmdLine) {};
  virtual void OnCommandSTDOUT(LPCSTR lpszOutput) {};
  virtual void OnCommandSTDERR(LPCSTR lpszOutput) {};
  virtual void OnCommandEnd(bool bSuccessful) {};

  // Loads an ECK file. If a ECK file is already loaded/running, 
  // it will be automatically stopped/unloaded.
  // To execute several ECK's, construct several CGecko.
  bool eckLoad(LPCSTR szName);
  // Unloads an ECK file. Actually it will never be unloaded 
  // completely until program termination:
  // variables will remain with the same value at next Load() 
  // of the same ECK.
  // That's because I don't manage to have the ECK file unmapped
  // from process, even with several calls
  // to FreeLibrary(). If someone has an idea...
  void eckUnload(void);

  // Runs a previously loaded ECK file (same as calling
  // eckSendMessage(GM_RUN)).
  // Returns what you returns in your script, or -1 in case 
  // of error (no ECK loaded for example).
  int eckRun(WPARAM wParam=0, LPARAM lParam=0);
  // Aborts (stops) a previously loaded and running ECK file 
  // (same as calling eckSendMessage(GM_ABORT)).
  // Returns what you returns in your script, or -1 in case of error.
  int eckAbort(WPARAM wParam=0, LPARAM lParam=0);
  // Sends a message to currently loaded ECK file.
  // Returns what you returns in your script, or -1 in case of error.
  int eckSendMessage(UINT nMsg, WPARAM wParam=0, LPARAM lParam=0);

  // Creates a thread and runs the loaded ECK file from it. 
  bool eckRunThreaded(WPARAM wParam=0, LPARAM lParam=0, 
    int nPriority=THREAD_PRIORITY_NORMAL);
  // Returns true if a thread is running.
  bool eckIsThreadRunning(void);
  // Returns the thread handle of current running script. 
  // Can be NULL if no thread.
  HANDLE GetThreadHandle(void);
  // Aborts running script (sending GM_ABORT), causing thread 
  // to return if the script indeed stops.
  // If the running script doesn't stop within the specified time 
  // (in milli-seconds), either bAllowKill
  // is true and the thread will be terminated with TerminateThread(), 
  // or eckAbortThread() will return.
  // Returns true if the script is aborted and thread is stopped.
  bool eckAbortThread(DWORD dwTimeout=5000, bool bAllowKill=false);
};

安全、责任等。

唯一的问题是:向非经验丰富的用户提供 C++ 编译器和底层函数可能很危险。如果您集成 Gecko 并嵌入 MinGW,您因此必须承担最终用户的责任,并警告他们如果他们做了任何事情,他们的计算机将面临的风险。这就是为什么我放了一个大的免责声明:我将不对 Gecko 组件的误用或脚本造成的任何损害负责。

但是,您可以选择不嵌入 MinGW:然后您将能够运行 ECK 文件,但不能编译。这样就消除了问题。

安装

  1. 在您的项目目录中创建一个名为 *gecko.natives.h* 的文件。按照以下模型在此文件中声明您希望从脚本中访问的函数
    GECKO_NATIVE void GECKO_NATIVE_CALL SetPlayerHealth(int nNewHealth);
    GECKO_NATIVE void GECKO_NATIVE_CALL AlertDamage(int nDamage);
    GECKO_NATIVE void GECKO_NATIVE_CALL AppendOutput(LPCSTR szText);
    GECKO_NATIVE int GECKO_NATIVE_CALL foo(int i);
    GECKO_NATIVE const char* GECKO_NATIVE_CALL bar(LPCSTR a, float d);
    ...
    
    目前仅支持 C 风格函数。这里没有类。将函数体放在您项目中的任何 .cpp 文件中,并注意在之前包含 *MyGecko.h*。
    #include "MyGecko.h"
    
    GECKO_NATIVE void GECKO_NATIVE_CALL SetPlayerHealth(int nNewHealth)
    {
    ...
    }
    
    GECKO_NATIVE void GECKO_NATIVE_CALL AlertDamage(int nDamage)
    {
    ...
    }
    
    ...
  2. 运行 GeckoSetup.exe。填写字段,点击 Go!就可以了。



  3. MyGecko.cppMyGecko.h 添加到您的项目中(这些文件已复制到您的项目目录)。无论何时何地需要使用 CGecko 类,请包含文件 MyGecko.h

就是这样:)

关于 MinGW

必需文件

  • 在 doc 目录中,只保留 MinGW 目录。
  • 在 lib 目录中,删除 gcc-lib 子目录。

通过这些努力,您将获得仅 3.25 MB(zip 压缩)。

许可条款

URL - http://www.mingw.org/licensing.shtml

基本 MinGW 运行时 MinGW 基本运行时包不受版权保护,并置于公共领域。这基本上意味着您可以随心所欲地处理代码。

w32api 您可以自由使用、修改和复制此包。对使用此库编译的程序或对象文件没有限制。您不得限制此库的使用。您可以将此库作为另一个包的一部分或作为修改后的包分发,前提是您不对构成此(可选修改后的)库的部分的使用施加限制。如果作为修改后的包分发,则必须包含此文件。

分发此库是希望它有用,但没有任何保证;甚至没有隐含的适销性或特定用途的适用性保证。

MinGW 分析代码 MinGW 分析代码根据 GNU 通用公共许可证分发。

GCC、GDB、GNU Make 等开发工具均受 GNU 通用公共许可证的约束。

这是 GNU 通用公共许可证的相关部分

“1.您可以按原样复制和分发程序的源代码的字面副本,在任何媒介上,前提是您在每个副本上显眼地、恰当地发布适当的版权声明和免责声明;保留所有指向本许可证和任何免责声明的通知;并将程序的任何其他接收者连同程序一起提供一份本许可证的副本。”

“3.您可以在上述第 1 条和第 2 条的条款下,以对象代码或可执行形式复制和分发程序(或基于它的作品,
在第 2 条下)
您还必须执行以下任一操作

a) 附带完整的相应机器可读
源代码,该源代码必须根据上述第 1 条和第 2 条的条款在通常用于软件交换的媒介上分发;或者,
b) 附带一份书面要约,有效期至少为三年,

向任何第三方提供,收费不超过您的
实体执行源代码分发的成本,一份完整的
机器可读的相应源代码副本,
根据上述第 1 条和第 2 条的条款在媒介上分发
通常用于软件交换;或者,
c) 附带您收到的有关提供

源代码分发的信息。(此替代方案仅
允许非商业分发,并且仅当您
根据上述第 b 款,以具有此类
要约的对象代码或可执行形式接收了程序。)

简而言之

如果您想将*部分* MinGW 包嵌入*非商业*产品中: “*附带您收到的有关提供源代码分发的信息。*”

如果您计划将*部分* MinGW 包嵌入*商业*产品中,您必须提供以下软件包的源代码

  • gcc-core-3.3.1-20030804-1-src.tar.gz
  • gcc-g++-3.3.1-20030804-1-src.tar.gz
  • binutils-2.14.90-20030807-1-src.tar.gz

这些软件包可以在以下位置找到:http://www.mingw.org/download.shtml

历史

  • 2003 年 8 月 29 日:首次发布。
© . All rights reserved.