11using System ;
2- using System . Collections ;
32using System . Collections . Generic ;
4- using System . Collections . ObjectModel ;
53using System . Diagnostics ;
64using System . IO ;
75using System . Linq ;
@@ -17,7 +15,34 @@ namespace Python.Runtime
1715{
1816 public static class RuntimeData
1917 {
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 ;
2146 public static Type ? FormatterType
2247 {
2348 get => _formatterType ;
@@ -31,6 +56,14 @@ public static Type? FormatterType
3156 }
3257 }
3358
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 ;
3467 public static ICLRObjectStorer ? WrappersStorer { get ; set ; }
3568
3669 /// <summary>
@@ -74,6 +107,7 @@ internal static void Stash()
74107 using NewReference capsule = PyCapsule_New ( mem , IntPtr . Zero , IntPtr . Zero ) ;
75108 int res = PySys_SetObject ( "clr_data" , capsule . BorrowOrThrow ( ) ) ;
76109 PythonException . ThrowIfIsNotZero ( res ) ;
110+ PostStashHook ? . Invoke ( ) ;
77111 }
78112
79113 internal static void RestoreRuntimeData ( )
@@ -90,6 +124,7 @@ internal static void RestoreRuntimeData()
90124
91125 private static void RestoreRuntimeDataImpl ( )
92126 {
127+ PreRestoreHook ? . Invoke ( ) ;
93128 BorrowedReference capsule = PySys_GetObject ( "clr_data" ) ;
94129 if ( capsule . IsNull )
95130 {
@@ -250,11 +285,102 @@ private static void RestoreRuntimeDataObjects(SharedObjectsState storage)
250285 }
251286 }
252287
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+
253376 internal static IFormatter CreateFormatter ( )
254377 {
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 ( ) ;
258384 }
259385 }
260386}
0 commit comments