1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
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;
}
}
}
|