23

When making C++ plugins in Unity it is easier to use Debug.Log to quickly view variable values but this function is only available from C# side. This makes it very hard to debug C++ plugin since Unity's debugger does not support that. std::cout is not an option since it doesn't show in the Editor.

I looked inside the Unity C++ API located at <UnityInstallationDirecory>\Editor\Data\PluginAPI but didn't find anything about logging in the API.

Any suggestions on how to display in the Editor log from C++?

2
  • 1
    Is reverse Invoking an option for you? answers.unity3d.com/questions/30620/… Commented May 2, 2017 at 8:06
  • 1
    @Smartis Thanks. That looks promising but it won't work on iOS due to AOT. It's a good start. I will post answer that works on iOS too if I get it working. Commented May 2, 2017 at 8:10

3 Answers 3

33

This can be done with a callback function. Send a pointer to a function to from C# to C++ store it in a temporary variable. Put Debug.Log inside that callback function and allow it to receive strings as a pointer(IntPtr).

When this function is called from C++, convert the IntPtr to string with Marshal.PtrToStringAnsi.

To make it work on iOS you have to use the MonoPInvokeCallback attribute on the callback function.

C# (Attach to an empty GameObject):

using AOT;
using System;
using System.Runtime.InteropServices;
using UnityEngine;

public class DebugCPP : MonoBehaviour
{

    // Use this for initialization
    void OnEnable()
    {
        RegisterDebugCallback(OnDebugCallback);
    }

    //------------------------------------------------------------------------------------------------
    [DllImport("DebugLogPlugin", CallingConvention = CallingConvention.Cdecl)]
    static extern void RegisterDebugCallback(debugCallback cb);
    //Create string param callback delegate
    delegate void debugCallback(IntPtr request, int color, int size);
    enum Color { red, green, blue, black, white, yellow, orange };
    [MonoPInvokeCallback(typeof(debugCallback))]
    static void OnDebugCallback(IntPtr request, int color, int size)
    {
        //Ptr to string
        string debug_string = Marshal.PtrToStringAnsi(request, size);

        //Add Specified Color
        debug_string =
            String.Format("{0}{1}{2}{3}{4}",
            "<color=",
            ((Color)color).ToString(),
            ">",
            debug_string,
            "</color>"
            );

        UnityEngine.Debug.Log(debug_string);
    }
}

C++ (DebugCPP.h):

#pragma once
#include<stdio.h>
#include <string>
#include <stdio.h>
#include <sstream>

#define DLLExport __declspec(dllexport)

extern "C"
{
    //Create a callback delegate
    typedef void(*FuncCallBack)(const char* message, int color, int size);
    static FuncCallBack callbackInstance = nullptr;
    DLLExport void RegisterDebugCallback(FuncCallBack cb);
}

//Color Enum
enum class Color { Red, Green, Blue, Black, White, Yellow, Orange };

class  Debug
{
public:
    static void Log(const char* message, Color color = Color::Black);
    static void Log(const std::string message, Color color = Color::Black);
    static void Log(const int message, Color color = Color::Black);
    static void Log(const char message, Color color = Color::Black);
    static void Log(const float message, Color color = Color::Black);
    static void Log(const double message, Color color = Color::Black);
    static void Log(const bool message, Color color = Color::Black);

private:
    static void send_log(const std::stringstream &ss, const Color &color);
};

C++ (DebugCPP.cpp):

#include "DebugCPP.h"

#include<stdio.h>
#include <string>
#include <stdio.h>
#include <sstream>

//-------------------------------------------------------------------
void  Debug::Log(const char* message, Color color) {
    if (callbackInstance != nullptr)
        callbackInstance(message, (int)color, (int)strlen(message));
}

void  Debug::Log(const std::string message, Color color) {
    const char* tmsg = message.c_str();
    if (callbackInstance != nullptr)
        callbackInstance(tmsg, (int)color, (int)strlen(tmsg));
}

void  Debug::Log(const int message, Color color) {
    std::stringstream ss;
    ss << message;
    send_log(ss, color);
}

void  Debug::Log(const char message, Color color) {
    std::stringstream ss;
    ss << message;
    send_log(ss, color);
}

void  Debug::Log(const float message, Color color) {
    std::stringstream ss;
    ss << message;
    send_log(ss, color);
}

void  Debug::Log(const double message, Color color) {
    std::stringstream ss;
    ss << message;
    send_log(ss, color);
}

void Debug::Log(const bool message, Color color) {
    std::stringstream ss;
    if (message)
        ss << "true";
    else
        ss << "false";

    send_log(ss, color);
}

void Debug::send_log(const std::stringstream &ss, const Color &color) {
    const std::string tmp = ss.str();
    const char* tmsg = tmp.c_str();
    if (callbackInstance != nullptr)
        callbackInstance(tmsg, (int)color, (int)strlen(tmsg));
}
//-------------------------------------------------------------------

//Create a callback delegate
void RegisterDebugCallback(FuncCallBack cb) {
    callbackInstance = cb;
}

Usage from C++:

Debug::Log("Hellow Red", Color::Red);
Debug::Log("Hellow Green", Color::Green);
Debug::Log("Hellow Blue", Color::Blue);
Debug::Log("Hellow Black", Color::Black);
Debug::Log("Hellow White", Color::White);
Debug::Log("Hellow Yellow", Color::Yellow);
Debug::Log("Hellow Orange", Color::Orange);

Debug::Log(true, Color::Black);
Debug::Log(false, Color::Red);

Output from the Editor:

enter image description here

Now, you can easily implement Debug.LogWarning and Debug.LogError.

Sign up to request clarification or add additional context in comments.

1 Comment

Color is really useful to easily separate different logs. You are welcome!
0

Here's a simple solution, using automatic marshalling (from string to char*).

C++ (DebugLogPlugin.cpp)

// The callback signature (shared by Unity C#)`enter code here`
using DebugLogCallback = void(const char*);

// Storage for the pointer to the Unity C# callback
static DebugLogCallback s_debugLogCallback = nullptr;

// Register the callback (called from Unity C#)
void RegisterDebugLog(DebugLogCallback callback)
{
    s_debugLogCallback = callback;
}

// Use from C++ to log the message to Unity C#
void DebugLog(const char* message)
{
    s_debugLogCallback(message);
}

// Usage example
DebugLog("Hello World");

C# (DebugLogBehaviour.cs)

// The callback signature (shared by C++). string is marshalled as const char* automatically
public delegate void DebugLogCallback(string message);

// Import our C++ register function
[DllImport("DebugLogPlugin")]
public static extern void RegisterDebugLog(DebugLogCallback callback);

// MonoBehaviour to load our plugin and register the callback
public class DebugLogBehaviour : MonoBehaviour
{
    void OnEnable()
    {
        // Call our C++ function to register the callback.
        RegisterDebugLog(DebugLog);
    }
    
    [MonoPInvokeCallback]
    static void DebugLog(string message)
    {
        Debug.Log(message);
    }
}

Comments

0

I find the most simplest way to do it via Unity Native API.

In native project

  • Please include the Unity Native API in %UNITY_LOCATION%\Editor\Data\PluginAPI
  • Write a code like below
// Must include this one
#include "IUnityLog.h"

extern "C" void UNITY_INTERFACE_EXPORT UNITY_INTERFACE_API SendDataFromUnityToLog(float t)
{
    string content = "Native log: " + to_string(t);
    const char* c_content = content.c_str();

    // Do this three function below to warp the log from unity
    UNITY_LOG(mLogger, c_content);
    UNITY_LOG_WARNING(mLogger, c_content);
    UNITY_LOG_ERROR(mLogger, c_content);
}

// This event trigger once unity load your plugin
extern "C" void UNITY_INTERFACE_EXPORT UNITY_INTERFACE_API UnityPluginLoad(IUnityInterfaces * pInterfaces)
{
    mLogger = pInterfaces->Get<IUnityLog>();
    UNITY_LOG(mLogger, "Native Plugin load");
}

// This event trigger when unity unload your plugin
extern "C" void UNITY_INTERFACE_EXPORT UNITY_INTERFACE_API UnityPluginUnload()
{
    UNITY_LOG(mLogger, "Native Plugin unload");
    mLogger = nullptr;
}

In your unity project

    [DllImport("NativePlugin")]
    private static extern void SendDataFromUnityToLog(float _);

    private void Start()
    {
        UnityEngine.Random.InitState(System.DateTime.Now.Second);
        float randNum = UnityEngine.Random.value;
        Debug.Log("Unity Log: " + randNum.ToString());

        // You must trigger this to trigger UnityPluginLoad load
        // and test it 
        SendDataFromUnityToLog(randNum);
    }

Result: result

It costs me a lot of time to try without any unity documents. So I wrote a enample in: https://github.com/Darkgrouptw/UnityNativeLogger

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.