Scripting with C++
This blog attempts to showcase the potential of C++ as a scripting language.
Disclaimer: This is purely Windows based and has some programming :(.. Sorry!
C++ Scripting
Scripting is almost a requirement for game engines these days. The idea behind scripting is to distance the game programmer from engine code. The benefits to providing scripting is the end user employs an “easier” language to code with and avoid complex systems that could potentially confuse or cause problems for the game programmer.
There has been many avenues taken to provide scripting, Unity’s C# and Java based scripting, embedded LUA is a common scripting language, some programmers even create their own language to help interface with their engine. An overlooked scenario is C++ as a scripting language.
I know what you are thinking; C++ is too hard and requires proficiency in concepts such as object oriented programming, memory management, and polymorphism. C++ scripts will also require an absurd amount of code to get it working! The truth is you are right, but there is some tricks and tips that can be utilized to simplify the scripting experience and as it turns out, it does not require a large code base to implement.
Pros and cons of Embedded LUA
Pros | Cons |
Light weight | Slow performance compared to compiled code |
Fast code implementation | Exposing engine code to LUA |
Easy language syntax and behavior | Backwards compatibility (is difficult) |
Pros and cons of C++ Scripting
Pros | Cons |
Performance(After Dll is loaded) | Complicated language and syntax |
Exposed completely to the engine | Exposed completely to the engine |
Given the pros and cons list it is rather evident that LUA wins by a landslide, however there might be cases where C++ might be a better scripting stream -- cases where the end user has knowledge of C++ and needs the performance and power of the language. Another benefit for C++ scripting is that you eliminate having to expose engine code to the scripting language, which can potentially save oodles of time. Anyone familiar with embedding LUA knows the hassle of trying to interface C++ code with LUA.
Code
class Script { public: Script() {}; virtual void Update() = 0; virtual void Start() = 0; virtual void OnCollisionEnter() {}; std::string ScriptPath; };
Here is a simple yet powerful base class that all scripts will inherit. Similar to Unity’s format the Update and Start functions must be implemented for every script. I use the variable ScriptPath as a key to a hash map, which holds constructors for each script. Essentially ScriptPath is how I establish what “type” the script is.
Script Implementation
#include "Script.h" #include class TestScriptClass : public DI::Script { public: TestScriptClass(){}; ~TestScriptClass(){}; void Update() { printf("Update"); }; void Start() { printf("Start"); }; }; extern "C" { __declspec(dllexport) DI::Script *CreateScript() { return new TestScriptClass(); } }
Here is a script that on Update prints a string “Update” to the console window and the function Start prints “Start”. Here is where the time saved is evident, instead of constantly exposing your engine code to whatever scripting language you are using, the user can just include whichever header is necessary from the engine and work from there. The script above is a standard print script; the key part to focus on is the CreateScript function at the bottom. The CreateScript function is in extern C to avoid name mangling on compilation to a DLL. Whenever CreateScript is invoked, it returns a pointer to a new TestScript on the heap; this is how the engine will create new scripts of this particular type.
Compilation
The next step in the process is compiling the TestScript into a DLL, if you are on windows I would recommend integrating the developer console into your program to automate script compilation, if your engine is cross platform I would look at C-make. For now, we will compile the scripts manually into DLLs. This step will require some effort to fully integrate a compiler into your engine and handle the script directories automatically.
Loading DLLs
This is where the magic happens, now that the script is compiled into a DLL we can dynamically link to it during runtime using Window’s functions GetProcAddress and LoadLibraryA.
#include "ScriptManager.h" #include #include #include "Script.h" void ScriptManager::LoadDll(std::string s) { //typedef DI::Script* (__stdcall *scriptPtr)(); std::string tempScriptPath = "./Dependencies/" + s; HINSTANCE hGetProcIDDLL = LoadLibraryA((tempScriptPath.c_str())); if (!hGetProcIDDLL) { std::cout << "could not load the dynamic library" << std::endl; } //# resolve function address here scriptPtr CreateScript = (scriptPtr)GetProcAddress(hGetProcIDDLL, "CreateScript"); if (!CreateScript) { std::cout << "could not locate the function" << std::endl; } DI::Script *f_s = CreateScript(); ScriptMap[tempScriptPath] = CreateScript; }
LoadLibraryA is the function that loads the DLL into memory so we can use it for our program. Once the DLL is loaded in we need to access the CreateScript function, this can be done using GetProcAddress. In this function, we pass the Instance of our LoadLibraryA call and specify the name of the function we are trying to find. GetProcAddress returns a pointer to the function so we assign a function pointer that we can use later to create more scripts of that “type”.
Conclusion
Some recap, the benefits of C++ scripting is the performance of the language; the favorable time saved not exposing C++ to whichever scripting language is being used and the power of C++. I hope that this blog has provided some insight into the potential of C++ scripts. Feel free to leave a comment, question, critique, optimization, or a friendly hello :).
Read more about:
BlogsAbout the Author
You May Also Like