1
1
using System ;
2
- using System . Collections ;
3
2
using System . Collections . Generic ;
4
- using System . Collections . ObjectModel ;
5
3
using System . Diagnostics ;
6
4
using System . IO ;
7
5
using System . Linq ;
@@ -17,7 +15,34 @@ namespace Python.Runtime
17
15
{
18
16
public static class RuntimeData
19
17
{
20
- private static Type ? _formatterType ;
18
+
19
+ public readonly static Func < IFormatter > DefaultFormatterFactory = ( ) =>
20
+ {
21
+ try
22
+ {
23
+ return new BinaryFormatter ( ) ;
24
+ }
25
+ catch
26
+ {
27
+ return new NoopFormatter ( ) ;
28
+ }
29
+ } ;
30
+
31
+ private static Func < IFormatter > _formatterFactory { get ; set ; } = DefaultFormatterFactory ;
32
+
33
+ public static Func < IFormatter > FormatterFactory
34
+ {
35
+ get => _formatterFactory ;
36
+ set
37
+ {
38
+ if ( value == null )
39
+ throw new ArgumentNullException ( nameof ( value ) ) ;
40
+
41
+ _formatterFactory = value ;
42
+ }
43
+ }
44
+
45
+ private static Type ? _formatterType = null ;
21
46
public static Type ? FormatterType
22
47
{
23
48
get => _formatterType ;
@@ -31,6 +56,14 @@ public static Type? FormatterType
31
56
}
32
57
}
33
58
59
+ /// <summary>
60
+ /// Callback called as a last step in the serialization process
61
+ /// </summary>
62
+ public static Action ? PostStashHook { get ; set ; } = null ;
63
+ /// <summary>
64
+ /// Callback called as the first step in the deserialization process
65
+ /// </summary>
66
+ public static Action ? PreRestoreHook { get ; set ; } = null ;
34
67
public static ICLRObjectStorer ? WrappersStorer { get ; set ; }
35
68
36
69
/// <summary>
@@ -74,6 +107,7 @@ internal static void Stash()
74
107
using NewReference capsule = PyCapsule_New ( mem , IntPtr . Zero , IntPtr . Zero ) ;
75
108
int res = PySys_SetObject ( "clr_data" , capsule . BorrowOrThrow ( ) ) ;
76
109
PythonException . ThrowIfIsNotZero ( res ) ;
110
+ PostStashHook ? . Invoke ( ) ;
77
111
}
78
112
79
113
internal static void RestoreRuntimeData ( )
@@ -90,6 +124,7 @@ internal static void RestoreRuntimeData()
90
124
91
125
private static void RestoreRuntimeDataImpl ( )
92
126
{
127
+ PreRestoreHook ? . Invoke ( ) ;
93
128
BorrowedReference capsule = PySys_GetObject ( "clr_data" ) ;
94
129
if ( capsule . IsNull )
95
130
{
@@ -250,11 +285,102 @@ private static void RestoreRuntimeDataObjects(SharedObjectsState storage)
250
285
}
251
286
}
252
287
288
+ static readonly string serialization_key_namepsace = "pythonnet_serialization_" ;
289
+ /// <summary>
290
+ /// Removes the serialization capsule from the `sys` module object.
291
+ /// </summary>
292
+ /// <remarks>
293
+ /// The serialization data must have been set with <code>StashSerializationData</code>
294
+ /// </remarks>
295
+ /// <param name="key">The name given to the capsule on the `sys` module object</param>
296
+ public static void FreeSerializationData ( string key )
297
+ {
298
+ key = serialization_key_namepsace + key ;
299
+ BorrowedReference oldCapsule = PySys_GetObject ( key ) ;
300
+ if ( ! oldCapsule . IsNull )
301
+ {
302
+ IntPtr oldData = PyCapsule_GetPointer ( oldCapsule , IntPtr . Zero ) ;
303
+ Marshal . FreeHGlobal ( oldData ) ;
304
+ PyCapsule_SetPointer ( oldCapsule , IntPtr . Zero ) ;
305
+ PySys_SetObject ( key , null ) ;
306
+ }
307
+ }
308
+
309
+ /// <summary>
310
+ /// Stores the data in the <paramref name="stream"/> argument in a Python capsule and stores
311
+ /// the capsule on the `sys` module object with the name <paramref name="key"/>.
312
+ /// </summary>
313
+ /// <remarks>
314
+ /// No checks on pre-existing names on the `sys` module object are made.
315
+ /// </remarks>
316
+ /// <param name="key">The name given to the capsule on the `sys` module object</param>
317
+ /// <param name="stream">A MemoryStream that contains the data to be placed in the capsule</param>
318
+ public static void StashSerializationData ( string key , MemoryStream stream )
319
+ {
320
+ if ( stream . TryGetBuffer ( out var data ) )
321
+ {
322
+ IntPtr mem = Marshal . AllocHGlobal ( IntPtr . Size + data . Count ) ;
323
+
324
+ // store the length of the buffer first
325
+ Marshal . WriteIntPtr ( mem , ( IntPtr ) data . Count ) ;
326
+ Marshal . Copy ( data . Array , data . Offset , mem + IntPtr . Size , data . Count ) ;
327
+
328
+ try
329
+ {
330
+ using NewReference capsule = PyCapsule_New ( mem , IntPtr . Zero , IntPtr . Zero ) ;
331
+ int res = PySys_SetObject ( key , capsule . BorrowOrThrow ( ) ) ;
332
+ PythonException . ThrowIfIsNotZero ( res ) ;
333
+ }
334
+ catch
335
+ {
336
+ Marshal . FreeHGlobal ( mem ) ;
337
+ }
338
+ }
339
+ else
340
+ {
341
+ throw new NotImplementedException ( $ "{ nameof ( stream ) } must be exposable") ;
342
+ }
343
+
344
+ }
345
+
346
+ static byte [ ] emptyBuffer = new byte [ 0 ] ;
347
+ /// <summary>
348
+ /// Retreives the previously stored data on a Python capsule.
349
+ /// Throws if the object corresponding to the <paramref name="key"/> parameter
350
+ /// on the `sys` module object is not a capsule.
351
+ /// </summary>
352
+ /// <param name="key">The name given to the capsule on the `sys` module object</param>
353
+ /// <returns>A MemoryStream containing the previously saved serialization data.
354
+ /// The stream is empty if no name matches the key. </returns>
355
+ public static MemoryStream GetSerializationData ( string key )
356
+ {
357
+ BorrowedReference capsule = PySys_GetObject ( key ) ;
358
+ if ( capsule . IsNull )
359
+ {
360
+ // nothing to do.
361
+ return new MemoryStream ( emptyBuffer , writable : false ) ;
362
+ }
363
+ var ptr = PyCapsule_GetPointer ( capsule , IntPtr . Zero ) ;
364
+ if ( ptr == IntPtr . Zero )
365
+ {
366
+ // The PyCapsule API returns NULL on error; NULL cannot be stored
367
+ // as a capsule's value
368
+ PythonException . ThrowIfIsNull ( null ) ;
369
+ }
370
+ var len = ( int ) Marshal . ReadIntPtr ( ptr ) ;
371
+ byte [ ] buffer = new byte [ len ] ;
372
+ Marshal . Copy ( ptr + IntPtr . Size , buffer , 0 , len ) ;
373
+ return new MemoryStream ( buffer , writable : false ) ;
374
+ }
375
+
253
376
internal static IFormatter CreateFormatter ( )
254
377
{
255
- return FormatterType != null ?
256
- ( IFormatter ) Activator . CreateInstance ( FormatterType )
257
- : new BinaryFormatter ( ) ;
378
+
379
+ if ( FormatterType != null )
380
+ {
381
+ return ( IFormatter ) Activator . CreateInstance ( FormatterType ) ;
382
+ }
383
+ return FormatterFactory ( ) ;
258
384
}
259
385
}
260
386
}
0 commit comments