Tutorial — Create C/C++ plugins for Unity3D
Today I decided to share with you something that I think is not a “common knowledge” and there aren’t many resources on that topic on the internet. In this tutorial I’ll show you how to create plain C/C++ libraries for Unity3.
Please note that this method will only work on Mac, on Windows you’ll need to use Visual Studio. Windows version is coming soon!
You may ask, why should I bother writing C or C++ code when Unity supports C#?
Answer is simple. It’s just much faster. Really. I decided to prepare some benchmarks just to show you how C++ outperforms C# based on Mono framework.
Benchmarks
Reference machine: Macbook Pro 15″ Mid 2014, Intel I7 2.2Ghz
- Fill 10000 x 10000 Integer array with series:
[Unity C#] 1.0258 seconds vs 0.2346 seconds [C Library] - Fill 500 x 500 Integer array with series:
[Unity C#] 3.794 milliseconds vs 0.35 milliseconds [C Library] - Fill 10000 x 10000 Integer array with Random numbers:
[Unity C#] 4.26 seconds vs 0.995 seconds [C Library] - Fill 10000 x 10000 Integer array with Perlin Noise:
[Unity C#] 6.015 seconds vs 2.394 seconds [C Library] - Fill 500 x 500 Integer array with Perlin Noise:
[Unity C#] 20,47 milliseconds vs 5.697 milliseconds [C Library]
As you can see, C library can easily outperform Mono in some tasks. I think this technique is especially useful in procedural generation which I’ve fallen in love with some time ago. I know that these operations may be even 100x faster when calculated on GPU but for know I’ll focus just on pushing logic on C++ + CPU layer. Let’s start creating our libraries!
Part 1. Creating C++ Library
Open Xcode, click File -> New -> Project. From list of project types select OS X -> Framework & Library -> Bundle.
Now, we have to create source file with header file in C++ language. To do that, simply click folder containing your project and select “New File…”.
Following window should appear. Select C++ file, name it LowLevelPlugin and select “Also create header file”.
After doing that, you should see following project structure/hierarchy.
If after expanding folders you don’t have following structure it means that probably you’ve done something wrong and I recommend to start over again.
Cool, once you have everything setup, go ahead and open LowLevelPlugin.h file. You should see code like this:
#ifndef __LowLevelPlugin__LowLevelPlugin__
#define __LowLevelPlugin__LowLevelPlugin__
#include <stdio.h>
#endif /* defined(__LowLevelPlugin__LowLevelPlugin__) */
If you don’t understand this code don’t worry. We are going to remove it 😛 Instead, we are going to paste this:
#pragma once #if UNITY_METRO
#define EXPORT_API __declspec(dllexport) __stdcall
#elif UNITY_WIN
#define EXPORT_API __declspec(dllexport)
#else
#define EXPORT_API
#endif
This may seem even more complicated than previous one but don’t worry. I’ll explain.
Purpose of this whole header is to behave differently basing on which platform are you currently compiling your code. It means, that you can easily compile it in Visual Studio on Windows.
#pragma once
– This is C/C++ specific directive designed to cause the current source file to be included only once in a single compilation.
Next is:
#if UNITY_METRO
– this tells compiler to something inside this if block only if we are running on Metro based Windows e.g. 8.1. Inside this if block we have
#define EXPORT_API __declspec(dllexport) __stdcall
– keyword “define” works something like replace function, it’s syntax may be defined something like this #define <String_to_replace> <with_this_text>. In our case, everytime compiler sees EXPORT_API character sequence in code, it replaces it with the second part. Likewise compiler will behave in case when you compile your code on older windows, it will replace EXPORT_API with __declspec(dllexport) and will just remove EXPORT_API in any other case.
That’s it when it comes to header. Now let’s move on to something that it more interesting, actual source code. Open C source file and paste following code:
#include <stdlib.h>
#include <math.h>
#include "LowLevelPlugin.h" extern "C" int ** EXPORT_API fillArray(int size) {
int i = 0, j = 0;
int ** array = (int**) calloc(size, sizeof(int*)); for(i = 0; i < size; i++) {
array[i] = (int*) calloc(size, sizeof(int));
for(j = 0; j < size; j++) {
array[i][j] = i * size + j;
}
}
return array;
}
Some more advanced users will notice that I wrote this code more like in C than C++, that’s because I’m more familiar with plain C rather than C++. If you don’t understand what’s going on here’s short clarification:
First three lines are IMHO self explanatory. We are simply adding few standard libraries and our just created header file.
Next up is
extern "C" int ** EXPORT_API fillArray(int size) {
This is our function declaration. Our function is called “fillArray”, takes one int argument and returns two dimensional integer array. (Yes, ** means two dimensions) Word “extern” extends the visibility to the whole program, the functions can be used anywhere in any of the files of the whole program and outside our library/plugin. We have also our EXPORT_API keyword, I described how it works a bit earlier.
Next interesting thing is:
int ** array = (int**) calloc(size, sizeof(int*));
Here I’m defining two-dimensional int array and then I’m allocating memory for it. Since I’m writing code in C, I can’t just create array and assign values. I have to reserve some space in memory for it and then do any operations. In this particular case, I’m allocating next N memory “cells” of size of int* and cast it to two dimensional int array type.
Same thing I’m doing here:
array[i] = (int*) calloc(size, sizeof(int));
The only difference is that now I’m allocating memory for every “i’th” row of an array.
And in the end I’m returning an filled array. I hope that for loops and assigning values is obvious for you so I’ll not explain that in this tutorial.
Ok, C++ programming is done. How we have to generate library and copy it to the Unity. Simply press this big “Play” like looking button and in couple of seconds you should see bundle file generated right here:
Part 2. Using library in Unity3d
Right click on that file and select “Show in Finder”. Now copy that file and create new Unity3D Project. Inside /Assets/ folder create folder called Plugins and paste here bundle file. You should also create C# script in base directory so your project structure should like this:
Now it’s time to hook our library in Unity. To do that we have to do following things.
- At the top of your C# script add
“using System.Runtime.InteropServices;”
- Add external function declaration. In our case its
[DllImport ("LowLevelPlugin")]
private static extern int [,] fillArray(int size);
(if you named your library differently, use your custom name)
3. Create two-dimensional array variable.
int [,] array;
4. Call function from library and assign result to array variable
array = fillArray(size);
If you followed instructions carefully, your Unity project now should be able to use C++ library. Congratulations!
Part 3. Run benchmarks on your own! (optional)
For quick comparsion you can use this code:
using UnityEngine;
using System.Collections;
using System.Runtime.InteropServices; public class PerformanceTests : MonoBehaviour {
public int size = 512; [DllImport ("LowLevelPlugin")]
private static extern int[,] fillArray(int size); void Start () {
ArrayFillTest();
} private void ArrayFillTest() {
var start = Time.realtimeSinceStartup;
int[,] tab = fillArray(size); Debug.Log( (Time.realtimeSinceStartup-start).ToString("f6") + " secs"); start = Time.realtimeSinceStartup;
int[,] array = new int[size,size]; for(int i = 0; i < size; i++) {
for(int j = 0; j < size; j++) {
array[i,j] = i * size + j;
}
}
Debug.Log( (Time.realtimeSinceStartup-start).ToString("f6") + " secs"); }
}
In my case C is much faster. Results are even more clear in some more complex operations like generating noise. I really recommend this technique for large-scale math operations, It may save you some milliseconds each frame if your game relies heavily on calculations.
I hope you liked it, feel free to ask questions, any feedback is appreciated.