How to use Native Code in Unity

You can use native code in Unity by importing Native Plugins.
Native Plugins are basically libraries of native code written in C/C++. You can import them in C# and make function calls to native code. This way you can gain performance, or use them for whatever reasons you have. I want to note that this isn’t Unity specific thing, I am just using Unity as an example.

How To Create a Native Library

I am working with Visual Studio, so I will show you how to do it in Visual Studio.

You need to create a C++ Dynamic-Link Library (DLL) project. After you write your native code there, you need to build the project and a .dll will be generated. Later you will need to import this .dll in Unity.

Let’s write a simple function so that I can show you how to import it in Unity.

#define DllExport __declspec(dllexport)

extern "C" {
	DllExport float GetFloat() { return 1.0f; }
}

How To Import It In Unity

First you have to place the .dll somewhere in the Assets folder. You can create a sub-folder specifically fot the native libraries if you want, it doesn’t matter. Let’s say the name of the library is MyLib.dll. This is how you import it.

public class TestBehaviour : MonoBehaviour
{
    [DllImport("MyLib")]
    private static extern float GetFloat();

    private void Awake()
    {
        Debug.Log(GetFloat());
    }
}

How To Import a Class

Lets say you don’t want to import static functions, but an entire class with instance methods instead.

The concept of “instance” method is actually just some sugar that the compiler/framework provides. Actually all methods are static. Instance methods just have an additional hidden parameter “this” that is a reference to the object instance.
If you want to call a native method for a certain native object, you would need to pass the native object reference along to the method call. Usually, you would create a wrapper class in .NET/C# that holds that native pointer (IntPtr) and provides the required methods for the C# environment. Those calls are then forwarded to the native interface. You just have to pass the object along.

Here is an example

// Header
class Person
{
public:
	Person(const char* name);
	~Person();

	const char* GetName() const;

private:
	char* _name;
};

// Source
Person::Person(const char* name)
{
	size_t nameLength = strlen(name);
	_name = new char[nameLength + 1];

	for (int i = 0; i < nameLength; i++)
	{
		_name[i] = name[i];
	}

	_name[nameLength] = '\0';
}

Person::~Person()
{
	delete _name;
	_name = nullptr;
}

const char* Person::GetName() const
{
	return _name;
}

// Export Interface
extern "C" {
	DllExport Person* CreatePerson(const char* name)
	{
		return new Person(name);
	}

	DllExport void DestroyPerson(Person* person)
	{
		delete person;
	}

	DllExport const char* GetPersonName(Person* person)
	{
		return person->GetName();
	}
}

// C# Wrapper Class
public class Person
{
    private IntPtr _personPtr = IntPtr.Zero;

    public Person(string name)
    {
        IntPtr namePtr = Marshal.StringToHGlobalAnsi(name);
        _personPtr = CreatePerson(namePtr);
        Marshal.FreeHGlobal(namePtr);
    }

    ~Person()
    {
        if (_personPtr != IntPtr.Zero)
        {
            DestroyPerson(_personPtr);
            _personPtr = IntPtr.Zero;
        }
    }

    public string GetName()
    {
        IntPtr namePtr = GetPersonName(_personPtr);
        string name = Marshal.PtrToStringAnsi(namePtr);

        return name;
    }

    [DllImport("MyLib")]
    private static extern IntPtr CreatePerson(IntPtr name);

    [DllImport("MyLib")]
    private static extern void DestroyPerson(IntPtr person);

    [DllImport("MyLib")]
    private static extern IntPtr GetPersonName(IntPtr person);
}
Advertisement