Reputation: 125245
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++?
Upvotes: 23
Views: 15023
Reputation: 1
I find the most simplest way to do it via Unity Native API.
In native project
// 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
Upvotes: 0
Reputation: 670
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);
}
}
Upvotes: 0
Reputation: 125245
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:
Now, you can easily implement Debug.LogWarning
and Debug.LogError
.
Upvotes: 33