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






4.93/5 (22投票s)
2003年9月1日
6分钟阅读

248144

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 文件,但不能编译。这样就消除了问题。
安装
- 在您的项目目录中创建一个名为 *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) { ... } ...
- 运行 GeckoSetup.exe。填写字段,点击 Go!就可以了。
- 将 MyGecko.cpp 和 MyGecko.h 添加到您的项目中(这些文件已复制到您的项目目录)。无论何时何地需要使用
CGecko
类,请包含文件 MyGecko.h。
就是这样:)
关于 MinGW
必需文件
- 对于 Gecko 编译,您不需要官方 MinGW 包的所有文件。 下载 MinGW 3.0.0 (Minimal GNU for Windows) (~15MB)。
- 安装。
- 只保留这些目录
- bin
- doc
- include
- lib
- 在 bin 目录中,只保留这些文件
- 在 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 日:首次发布。