I am trying to setup the native logger to call a C# function. Since logging in the native side is setup using a singleton (log4cpp), I need to make sure to setup the listener early on (before any other native call). So I decided to follow advice from:
Thus I have written the following static class (to act as my singleton):
internal static class MyNativeService
{
private const string NativeLibraryName = "my.so.0";
private static Delegate _logDelegate; // to prevent garbage collection of C# delegate
static void LogMessage(int logLevel, string loggerName, string message)
{
Console.WriteLine($"{logLevel} - {loggerName} - {message}");
}
static MyNativeService()
{
_logDelegate = new LogDelegate(LogMessage);
IntPtr ptr = Marshal.GetFunctionPointerForDelegate(_logDelegate);
// my_listener_configure should be called before any other native calls (only once)
my_listener_configure(ptr);
}
[UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
internal delegate void LogDelegate(int logLevel,
[MarshalAs(UnmanagedType.LPUTF8Str)] string loggerName,
[MarshalAs(UnmanagedType.LPUTF8Str)] string logMessage);
[DllImport(NativeLibraryName, CallingConvention = CallingConvention.Cdecl)]
private static extern void my_listener_configure(IntPtr aCallback);
}
This works as expected, but this is not exactly what I want. I would prefer to forward the message to the actual ILogger
framework (Microsoft.Extensions.Logging
). So how should I refactor the above code so that my LogMessage
becomes:
using Microsoft.Extensions.Logging;
internal class MyNativeLoggerListener
{
private readonly ILogger<MyNativeLoggerListener> _logger; // created somehow at startup
private void LogMessage(int logLevel, string loggerName, string message)
{
_logger.Log((LogLevel)logLevel, 0, $"{loggerName} - {message}", null, null);
}
}
Since I cannot mix dependency injection and static constructor, how and when can I create my singleton in my C# application to setup forwarding of native message into the properly configured logger (defined in the application) ?
Another naive solution, would be to create a logger in the Startup
class, so that:
namespace Acme
{
public class Startup
{
private readonly ILogger<Startup> _logger;
private readonly Delegate _logDelegate; // to prevent garbage collection of C# delegate
public void LogMessage(int logLevel, string loggerName, string message)
{
_logger.Log((LogLevel)logLevel, 0, message, null, null);
}
public Startup(IConfiguration configuration, ILogger<Startup> logger)
{
Configuration = configuration;
_logger = logger;
_logDelegate = MyNativeService.my_listener_configure(LogMessage);
}
Of course, this wont work since the ILogger mechanism has not been setup yet. The above throw a:
System.InvalidOperationException: Unable to resolve service for type 'Microsoft.Extensions.Logging.ILogger`1[Acme.Startup]' while attempting to activate 'Acme.Startup'.
For reference, here is the pattern I followed: