using System.Runtime.CompilerServices; using System.Runtime.ExceptionServices; using System.Runtime.InteropServices; namespace Whisper.Internal { /// Utility class to supply logging function pointer to the C++ library,
/// and provide custom calling conventions to ComLight runtime to convert error messages printed in C++ into .NET exception messages
public static class NativeLogger { internal static void startup() { } static NativeLogger() { sink = logSink; sLoggerSetup setup = default; setup.sink = sink; setup.level = eLogLevel.Warning; Library.setupLogger( ref setup ); } internal static void setup( eLogLevel lvl, eLoggerFlags flags, pfnLogMessage? pfn ) { logMessage = pfn; sLoggerSetup setup = default; setup.sink = sink; setup.level = lvl; setup.flags = flags; Library.setupLogger( ref setup ); } // This field is here to protect the function pointer from being collected by the GC static readonly pfnLoggerSink sink; static void logSink( IntPtr context, eLogLevel lvl, string message ) { if( lvl == eLogLevel.Error ) state.setText( message ); logMessage?.Invoke( lvl, message ); } sealed class ThreadState { string? errorText = null; ExceptionDispatchInfo? dispatchInfo = null; public void setText( string text ) => errorText = text; public void capture( Exception ex ) => dispatchInfo = ExceptionDispatchInfo.Capture( ex ); public void clear() { errorText = null; dispatchInfo = null; } public void Deconstruct( out string? text, out ExceptionDispatchInfo? edi ) { text = errorText; edi = dispatchInfo; errorText = null; dispatchInfo = null; } } [ThreadStatic] static ThreadState state = new ThreadState(); internal static void captureException( Exception ex ) => state.capture( ex ); static pfnLogMessage? logMessage = null; /// Called internally by ComLight runtime [MethodImpl( MethodImplOptions.AggressiveInlining )] public static void prologue() { // https://stackoverflow.com/a/2043505/126995 if( null != state ) state.clear(); else createState(); } [MethodImpl( MethodImplOptions.NoInlining )] static void createState() { state = new ThreadState(); } /// Epilogue implementation for unsuccessful status codes [MethodImpl( MethodImplOptions.NoInlining )] static void throwException( int hr ) { // Move state from the thread local object into local variables, and clear that object (string? text, ExceptionDispatchInfo? edi) = state; if( null != edi && edi.SourceException.HResult == hr ) { // The error comes from a callback, and we have original context of that exception. // Re-throw the original exception. // This uses the original error message, and even correctly deals with the stack trace. edi.Throw(); } if( null != text ) { // C++ code has printed an error on the current thread, between prologue and epilogue. // Use that text for the exception message. Exception? ex = Marshal.GetExceptionForHR( hr ); throw new ApplicationException( text, ex ); } // We don’t have any additional info about the exception. // Throw an exception from just the HRESULT code. Marshal.ThrowExceptionForHR( hr ); } /// Called internally by ComLight runtime [MethodImpl( MethodImplOptions.AggressiveInlining )] public static void throwForHR( int hr ) { if( hr >= 0 ) return; // SUCCEEDED throwException( hr ); } /// Called internally by ComLight runtime [MethodImpl( MethodImplOptions.AggressiveInlining )] public static bool throwAndReturnBool( int hr ) { if( hr >= 0 ) return 0 == hr; throwException( hr ); return false; } } }