1
+ using System ;
2
+ using System . Collections . Generic ;
3
+ using System . Runtime . CompilerServices ;
4
+ using Rubberduck . VBEditor . SafeComWrappers . Abstract ;
5
+
6
+ #if DEBUG
7
+ using System . Diagnostics ;
8
+ using System . IO ;
9
+ using System . Runtime . InteropServices ;
10
+ #endif
11
+
12
+ namespace Rubberduck . VBEditor . ComManagement
13
+ {
14
+ public abstract class ComSafeBase : IComSafe
15
+ {
16
+ public abstract void Add ( ISafeComWrapper comWrapper ) ;
17
+
18
+ public abstract bool TryRemove ( ISafeComWrapper comWrapper ) ;
19
+
20
+ //We do not use GetHashCode because subclasses of SafeComWrapper<T> overwrite this method
21
+ //and we need to distinguish between individual instances.
22
+ protected int GetComWrapperObjectHashCode ( ISafeComWrapper comWrapper )
23
+ {
24
+ return RuntimeHelpers . GetHashCode ( comWrapper ) ;
25
+ }
26
+
27
+ private bool _disposed ;
28
+ public void Dispose ( )
29
+ {
30
+ Dispose ( true ) ;
31
+
32
+ #if DEBUG
33
+ if ( _disposed )
34
+ {
35
+ return ;
36
+ }
37
+
38
+ _disposed = true ;
39
+
40
+ lock ( _streamLock )
41
+ {
42
+ try
43
+ {
44
+ if ( _traceStream == null )
45
+ {
46
+ return ;
47
+ }
48
+
49
+ _traceStream . Close ( ) ;
50
+ if ( string . IsNullOrWhiteSpace ( _directory ) )
51
+ {
52
+ File . Delete ( _traceFilePath ) ;
53
+ }
54
+ else
55
+ {
56
+ File . Move ( _traceFilePath ,
57
+ Path . Combine ( _directory ,
58
+ Path . GetFileNameWithoutExtension ( _traceFilePath ) + " final.csv" ) ) ;
59
+ }
60
+ }
61
+ finally
62
+ {
63
+ _traceStream ? . Dispose ( ) ;
64
+ _traceStream = null ;
65
+ }
66
+ }
67
+ #endif
68
+ }
69
+
70
+ protected abstract void Dispose ( bool disposing ) ;
71
+
72
+ #if DEBUG
73
+ private struct TraceData
74
+ {
75
+ internal int HashCode { get ; set ; }
76
+ internal string IUnknownAddress { get ; set ; }
77
+ internal IEnumerable < string > StackTrace { get ; set ; }
78
+ }
79
+ private StreamWriter _traceStream ;
80
+ private string _traceFilePath ;
81
+ private string _directory ;
82
+ private readonly object _streamLock = new object ( ) ;
83
+
84
+ /// <summary>
85
+ /// The first few stack frames come from the ComSafe and thus are not
86
+ /// particularly interesting. Typically, we want to look at the frames
87
+ /// outside the ComSafe.
88
+ /// </summary>
89
+ private const int StackTraceNumberOfElementsToSkipOnRemoval = 6 ;
90
+ private const int StackTrackNumberOfElementsToSkipOnAddUpdate = 8 ;
91
+ private const int StackTraceDepth = 5 ;
92
+
93
+ /// <inheritdoc cref="IComSafe.Serialize"/>
94
+ public void Serialize ( string targetDirectory )
95
+ {
96
+ lock ( _streamLock )
97
+ {
98
+ _directory = targetDirectory ;
99
+ var serializeTime = DateTime . UtcNow ;
100
+ using ( var stream = File . AppendText ( Path . Combine ( _directory ,
101
+ $ "COM Safe Content Snapshot { serializeTime : yyyyMMddhhmmss} .csv") ) )
102
+ {
103
+ stream . WriteLine (
104
+ $ "Ordinal\t Key\t COM Wrapper Type\t Wrapping Null?\t IUnknown Pointer Address") ;
105
+ var i = 0 ;
106
+ foreach ( var kvp in GetWrappers ( ) )
107
+ {
108
+ var line = kvp . Value != null
109
+ ? $ "{ i ++ } \t { kvp . Key } \t \" { kvp . Value . GetType ( ) . FullName } \" \t \" { kvp . Value . IsWrappingNullReference } \" \t \" { ( kvp . Value . IsWrappingNullReference ? "null" : GetPtrAddress ( kvp . Value . Target ) ) } \" "
110
+ : $ "{ i ++ } \t { kvp . Key } \t \" null\" \t \" null\" \t \" null\" ";
111
+ stream . WriteLine ( line ) ;
112
+ }
113
+ }
114
+
115
+ if ( _traceStream == null )
116
+ {
117
+ return ;
118
+ }
119
+
120
+ _traceStream . Flush ( ) ;
121
+ File . Copy ( _traceFilePath , Path . Combine ( _directory , $ "COM Safe Stack Trace { serializeTime : yyyyMMddhhmmss} .csv") ) ;
122
+ }
123
+ }
124
+
125
+ protected void TraceAdd ( ISafeComWrapper comWrapper )
126
+ {
127
+ Trace ( "Add" , comWrapper , StackTrackNumberOfElementsToSkipOnAddUpdate ) ;
128
+ }
129
+
130
+ protected void TraceUpdate ( ISafeComWrapper comWrapper )
131
+ {
132
+ Trace ( "Update" , comWrapper , StackTrackNumberOfElementsToSkipOnAddUpdate ) ;
133
+ }
134
+
135
+ protected void TraceRemove ( ISafeComWrapper comWrapper , bool wasRemoved )
136
+ {
137
+ var activity = wasRemoved ? "Removed" : "Not removed" ;
138
+ Trace ( activity , comWrapper , StackTraceNumberOfElementsToSkipOnRemoval ) ;
139
+ }
140
+
141
+ private readonly object _idLock = new object ( ) ;
142
+ private int _id ;
143
+ private void Trace ( string activity , ISafeComWrapper comWrapper , int framesToSkip )
144
+ {
145
+ lock ( _streamLock )
146
+ {
147
+ if ( _disposed )
148
+ {
149
+ return ;
150
+ }
151
+
152
+ if ( _traceStream == null )
153
+ {
154
+ var directory = Path . GetTempPath ( ) ;
155
+ _traceFilePath = Path . Combine ( directory ,
156
+ $ "COM Safe Stack Trace { DateTime . UtcNow : yyyyMMddhhmmss} .{ GetHashCode ( ) } .csv") ;
157
+ _traceStream = File . AppendText ( _traceFilePath ) ;
158
+ _traceStream . WriteLine (
159
+ $ "Ordinal\t Timestamp\t Activity\t Key\t IUnknown Pointer Address\t { FrameHeaders ( ) } ") ;
160
+ }
161
+
162
+ int id ;
163
+ lock ( _idLock )
164
+ {
165
+ id = _id ++ ;
166
+ }
167
+
168
+ var traceData = new TraceData
169
+ {
170
+ HashCode = GetComWrapperObjectHashCode ( comWrapper ) ,
171
+ IUnknownAddress = comWrapper . IsWrappingNullReference ? "null" : GetPtrAddress ( comWrapper . Target ) ,
172
+ StackTrace = GetStackTrace ( StackTraceDepth , framesToSkip )
173
+ } ;
174
+
175
+ var line =
176
+ $ "{ id } \t { DateTime . UtcNow } \t \" { activity } \" \t { traceData . HashCode } \t { traceData . IUnknownAddress } \t \" { string . Join ( "\" \t \" " , traceData . StackTrace ) } \" ";
177
+ _traceStream . WriteLine ( line ) ;
178
+ }
179
+ }
180
+
181
+ private static string FrameHeaders ( )
182
+ {
183
+ var headers = new System . Text . StringBuilder ( ) ;
184
+ for ( var i = 1 ; i <= StackTraceDepth ; i ++ )
185
+ {
186
+ headers . Append ( $ "Frame { i } \t ") ;
187
+ }
188
+
189
+ return headers . ToString ( ) ;
190
+ }
191
+
192
+ protected abstract IDictionary < int , ISafeComWrapper > GetWrappers ( ) ;
193
+
194
+ private static IEnumerable < string > GetStackTrace ( int frames , int framesToSkip )
195
+ {
196
+ var list = new List < string > ( ) ;
197
+ var trace = new StackTrace ( ) ;
198
+ if ( trace . FrameCount < ( frames + framesToSkip ) )
199
+ {
200
+ frames = trace . FrameCount ;
201
+ }
202
+ else
203
+ {
204
+ frames += framesToSkip ;
205
+ }
206
+
207
+ framesToSkip -= 1 ;
208
+ frames -= 1 ;
209
+
210
+ for ( var i = framesToSkip ; i < frames ; i ++ )
211
+ {
212
+ var frame = trace . GetFrame ( i ) ;
213
+ var type = frame . GetMethod ( ) . DeclaringType ;
214
+
215
+ var typeName = type ? . FullName ?? string . Empty ;
216
+ var methodName = frame . GetMethod ( ) . Name ;
217
+
218
+ var qualifiedName = $ "{ typeName } { ( typeName . Length > 0 ? "::" : string . Empty ) } { methodName } ";
219
+ list . Add ( qualifiedName ) ;
220
+ }
221
+
222
+ return list ;
223
+ }
224
+
225
+ protected static string GetPtrAddress ( object target )
226
+ {
227
+ if ( target == null )
228
+ {
229
+ return IntPtr . Zero . ToString ( ) ;
230
+ }
231
+
232
+ if ( ! Marshal . IsComObject ( target ) )
233
+ {
234
+ return "Not a COM object" ;
235
+ }
236
+
237
+ var pointer = IntPtr . Zero ;
238
+ try
239
+ {
240
+ pointer = Marshal . GetIUnknownForObject ( target ) ;
241
+ }
242
+ finally
243
+ {
244
+ if ( pointer != IntPtr . Zero )
245
+ {
246
+ Marshal . Release ( pointer ) ;
247
+ }
248
+ }
249
+
250
+ return pointer . ToString ( ) ;
251
+ }
252
+ #endif
253
+ }
254
+ }
0 commit comments