Search Unity

Discussion How to make a simple native C/C++ plugin using IL2CPP

Discussion in 'Editor & General Support' started by JoeStrout, Oct 30, 2022.

  1. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    9,859
    I (apparently) need to make a native plugin just to change the title of the player window at runtime. I've found the documentation on making such plugins rather thin, so I thought I would document my adventure here in case others find it useful.

    First objective: call some C code from a Unity script. I'm working on macOS, so I will tackle that first. Steps:

    1. Make sure you have the IL2CPP backend installed in Unity.
    2. In Player Settings > Configuration, make sure Scripting Backend is set to IL2CPP. (This is necessary since we're going to be including raw C/C++ files in the project, and expecting these to get compiled in along with all the converted C# code.)
    3. Set up a folder hierarchy in the project: Assets/Plugins/OSX.
    4. Inside that OSX folder, create a C/C++ file. Since this is for OSX, I'm actually creating an Obj-C++ file. Mine is called WindowTools.mm, and contains:
    Code (Obj-C++):
    1. // Mac code (in Objective-C++) for WindowTools plugin.
    2. extern "C" {
    3.     void TestEntryPoint() {
    4.         NSLog(@"Hello world!  This is a test.");
    5.         NSLog(@"Specifically, it's a TestEntryPoint().");
    6.     }
    7. }
    • In Unity, create a script to import and invoke the plugin entry point. Mine is called TestScript.cs.
    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using System.Runtime.InteropServices;        // REQUIRED for DllImport
    5.  
    6. public class TestScript : MonoBehaviour
    7. {
    8.     [DllImport("__Internal")]
    9.     static extern void TestEntryPoint();
    10.  
    11.     public void DoTest()
    12.     {
    13.         Debug.Log("About to call TestEntryPoint");
    14.         TestEntryPoint();
    15.         Debug.Log("Called TestEntryPoint");
    16.     }
    17. }
    • Hook up a simple button to invoke that DoTest method.
    • Build and run. You should see a "Building native binary with IL2CPP..." step in the progress window.
    At this point, the build failed for me.
    upload_2022-10-30_8-16-12.png

    The error doesn't contain much in the way of useful information; just "il2cppcore.dll did not run properly!" Huh. Looks like this bug, but I'm running 2019.4.33f1 here (the latest LTS of 2019.4), so it ought to have the fix. I guess we pause this adventure while I go try to track down what's going wrong here.
     
    Deleted User and Kurt-Dekker like this.
  2. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    9,859
    OK, so it turns out that "il2cppcore.dll did not run properly!" is what you get if there is an error in your C code. You don't get to see the actual C compiler errors — just the fact that it didn't run properly.

    In my case, I guess it's because I didn't have any declaration of that NSLog function. Some fumbling about finally got me to this version of WindowTest.mm, which worked:

    Code (Obj-C++):
    1. // Mac code (in Objective-C++) for WindowTools plugin.
    2.  
    3. #include <CoreFoundation/CoreFoundation.h>
    4.  
    5. extern "C" {
    6.  
    7.     void NSLog(CFStringRef format, ...);
    8.  
    9.     void TestEntryPoint() {
    10.         NSLog(CFSTR("Hello world!  This is a test."));
    11.     }
    12.  
    13.     int TestEntryPoint2() {
    14.         return 42;
    15.     }
    16. }
    You'll note in the process I also made a second entry point that returns an int; the Unity script now looks like:

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using System.Runtime.InteropServices;        // REQUIRED for DllImport
    5.  
    6. public class TestScript : MonoBehaviour
    7. {
    8.     [DllImport("__Internal")]
    9.     static extern void TestEntryPoint();
    10.    
    11.     [DllImport("__Internal")]
    12.     static extern int TestEntryPoint2();
    13.    
    14.    
    15.     public void DoTest()
    16.     {
    17.         Debug.Log("About to call TestEntryPoint");
    18.         TestEntryPoint();
    19.         Debug.Log("Called TestEntryPoint");
    20.         int result = TestEntryPoint2();
    21.         Debug.Log($"Called TestEntryPoint2, and got back: {result}");
    22.     }
    23. }
    And this works. Note that the NSLog output goes to the player log as well; I thought it would go to the system console log, but nope. It's right there with the Debug.Logs. Output in the player log looks like:

    So! We're now calling C code from my Unity code, and if you're very very careful, you don't need to fire up XCode or Visual Studio or any other IDE — you can do it all within Unity.

    The next step, in my particular case, will be figuring out how to set the window title property from my .mm file (and then repeating that process for Windows and Linux). Wish me luck.
     
  3. dynamicbutter

    dynamicbutter

    Joined:
    Jun 11, 2021
    Posts:
    63
    Pretty easy to do at build time for all platforms: Edit -> Project Settings -> Player -> Product Name. Does it have to be changeable at runtime?
     
  4. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    9,859
    Yes.