diff options
| author | Konstantin <const@const.me> | 2023-01-16 14:52:43 +0100 |
|---|---|---|
| committer | Konstantin <const@const.me> | 2023-01-16 14:52:43 +0100 |
| commit | 8c4603c73675958efc960fbd4bb599a2909d106a (patch) | |
| tree | 714dc6fc9a1672d5fd7f89676b97e10959662abc /WhisperNet/Internal/NativeLogger.cs | |
| parent | 990a8d0dbaefc996244097397259e92758b15cce (diff) | |
Source codes
Diffstat (limited to 'WhisperNet/Internal/NativeLogger.cs')
| -rw-r--r-- | WhisperNet/Internal/NativeLogger.cs | 138 |
1 files changed, 138 insertions, 0 deletions
diff --git a/WhisperNet/Internal/NativeLogger.cs b/WhisperNet/Internal/NativeLogger.cs new file mode 100644 index 0000000..b4b4eb2 --- /dev/null +++ b/WhisperNet/Internal/NativeLogger.cs @@ -0,0 +1,138 @@ +using System.Runtime.CompilerServices; +using System.Runtime.ExceptionServices; +using System.Runtime.InteropServices; + +namespace Whisper.Internal +{ + /// <summary>Utility class to supply logging function pointer to the C++ library,<br/> + /// and provide custom calling conventions to ComLight runtime to convert error messages printed in C++ into .NET exception messages</summary> + 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; + + /// <summary>Called internally by ComLight runtime</summary> + [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(); + } + + /// <summary>Epilogue implementation for unsuccessful status codes</summary> + [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 ); + } + + /// <summary>Called internally by ComLight runtime</summary> + [MethodImpl( MethodImplOptions.AggressiveInlining )] + public static void throwForHR( int hr ) + { + if( hr >= 0 ) + return; // SUCCEEDED + throwException( hr ); + } + + /// <summary>Called internally by ComLight runtime</summary> + [MethodImpl( MethodImplOptions.AggressiveInlining )] + public static bool throwAndReturnBool( int hr ) + { + if( hr >= 0 ) + return 0 == hr; + throwException( hr ); + return false; + } + } +}
\ No newline at end of file |
