2 // System.Drawing.ComIStreamMarshaler.cs
5 // Kornél Pál <http://www.kornelpal.hu/>
7 // Copyright (C) 2005-2006 Kornél Pál
11 // Permission is hereby granted, free of charge, to any person obtaining
12 // a copy of this software and associated documentation files (the
13 // "Software"), to deal in the Software without restriction, including
14 // without limitation the rights to use, copy, modify, merge, publish,
15 // distribute, sublicense, and/or sell copies of the Software, and to
16 // permit persons to whom the Software is furnished to do so, subject to
17 // the following conditions:
19 // The above copyright notice and this permission notice shall be
20 // included in all copies or substantial portions of the Software.
22 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
23 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
24 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
25 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
26 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
27 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
28 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
31 // Undefine to debug the protected blocks
34 // Define to debug wrappers recursively
35 // #define RECURSIVE_WRAPPING
39 using System
.Reflection
;
40 using System
.Runtime
.InteropServices
;
42 using System
.Runtime
.InteropServices
.ComTypes
;
43 using STATSTG
= System
.Runtime
.InteropServices
.ComTypes
.STATSTG
;
45 using IStream
= System
.Runtime
.InteropServices
.UCOMIStream
;
48 namespace System
.Drawing
50 // Mono does not implement COM interface marshaling
51 // This custom marshaler should be replaced with UnmanagedType.Interface
52 // Provides identical behaviour under Mono and .NET Framework
53 internal sealed class ComIStreamMarshaler
: ICustomMarshaler
55 private const int S_OK
= 0x00000000;
56 private const int E_NOINTERFACE
= unchecked((int)0x80004002);
58 private delegate int QueryInterfaceDelegate(IntPtr
@this, [In()] ref Guid riid
, IntPtr ppvObject
);
59 private delegate int AddRefDelegate(IntPtr
@this);
60 private delegate int ReleaseDelegate(IntPtr
@this);
61 private delegate int ReadDelegate(IntPtr
@this, [Out(), MarshalAs(UnmanagedType
.LPArray
, SizeParamIndex
= 2)] byte[] pv
, int cb
, IntPtr pcbRead
);
62 private delegate int WriteDelegate(IntPtr
@this, [MarshalAs(UnmanagedType
.LPArray
, SizeParamIndex
= 2)] byte[] pv
, int cb
, IntPtr pcbWritten
);
63 private delegate int SeekDelegate(IntPtr
@this, long dlibMove
, int dwOrigin
, IntPtr plibNewPosition
);
64 private delegate int SetSizeDelegate(IntPtr
@this, long libNewSize
);
65 private delegate int CopyToDelegate(IntPtr
@this, [MarshalAs(UnmanagedType
.CustomMarshaler
, MarshalTypeRef
=typeof(ComIStreamMarshaler
))] IStream pstm
, long cb
, IntPtr pcbRead
, IntPtr pcbWritten
);
66 private delegate int CommitDelegate(IntPtr
@this, int grfCommitFlags
);
67 private delegate int RevertDelegate(IntPtr
@this);
68 private delegate int LockRegionDelegate(IntPtr
@this, long libOffset
, long cb
, int dwLockType
);
69 private delegate int UnlockRegionDelegate(IntPtr
@this, long libOffset
, long cb
, int dwLockType
);
70 private delegate int StatDelegate(IntPtr
@this, out STATSTG pstatstg
, int grfStatFlag
);
71 private delegate int CloneDelegate(IntPtr
@this, out IntPtr ppstm
);
73 [StructLayout(LayoutKind
.Sequential
)]
74 private sealed class IStreamInterface
76 internal IntPtr lpVtbl
;
77 internal IntPtr gcHandle
;
80 [StructLayout(LayoutKind
.Sequential
)]
81 private sealed class IStreamVtbl
83 internal QueryInterfaceDelegate QueryInterface
;
84 internal AddRefDelegate AddRef
;
85 internal ReleaseDelegate Release
;
86 internal ReadDelegate Read
;
87 internal WriteDelegate Write
;
88 internal SeekDelegate Seek
;
89 internal SetSizeDelegate SetSize
;
90 internal CopyToDelegate CopyTo
;
91 internal CommitDelegate Commit
;
92 internal RevertDelegate Revert
;
93 internal LockRegionDelegate LockRegion
;
94 internal UnlockRegionDelegate UnlockRegion
;
95 internal StatDelegate Stat
;
96 internal CloneDelegate Clone
;
99 // Managed COM Callable Wrapper implementation
100 // Reference counting is thread safe
101 private sealed class ManagedToNativeWrapper
103 // Mono does not implement Marshal.Release
104 [StructLayout(LayoutKind
.Sequential
)]
105 private sealed class ReleaseSlot
107 internal ReleaseDelegate Release
;
110 private static readonly Guid IID_IUnknown
= new Guid("00000000-0000-0000-C000-000000000046");
111 private static readonly Guid IID_IStream
= new Guid("0000000C-0000-0000-C000-000000000046");
112 private static readonly MethodInfo exceptionGetHResult
= typeof(Exception
).GetProperty("HResult", BindingFlags
.GetProperty
| BindingFlags
.NonPublic
| BindingFlags
.Instance
| BindingFlags
.DeclaredOnly
| BindingFlags
.ExactBinding
, null, typeof(int), new Type
[] {}, null).GetGetMethod(true);
113 // Keeps delegates alive while they are marshaled
114 private static readonly IStreamVtbl managedVtable
;
115 private static IntPtr comVtable
;
116 private static int vtableRefCount
;
118 private IStream managedInterface
;
119 private IntPtr comInterface
;
120 // Keeps the object alive when it has no managed references
121 private GCHandle gcHandle
;
122 private int refCount
= 1;
124 static ManagedToNativeWrapper()
126 EventHandler onShutdown
;
127 AppDomain currentDomain
;
128 IStreamVtbl newVtable
;
130 onShutdown
= new EventHandler(OnShutdown
);
131 currentDomain
= AppDomain
.CurrentDomain
;
132 currentDomain
.DomainUnload
+= onShutdown
;
133 currentDomain
.ProcessExit
+= onShutdown
;
135 newVtable
= new IStreamVtbl();
136 newVtable
.QueryInterface
= new QueryInterfaceDelegate(QueryInterface
);
137 newVtable
.AddRef
= new AddRefDelegate(AddRef
);
138 newVtable
.Release
= new ReleaseDelegate(Release
);
139 newVtable
.Read
= new ReadDelegate(Read
);
140 newVtable
.Write
= new WriteDelegate(Write
);
141 newVtable
.Seek
= new SeekDelegate(Seek
);
142 newVtable
.SetSize
= new SetSizeDelegate(SetSize
);
143 newVtable
.CopyTo
= new CopyToDelegate(CopyTo
);
144 newVtable
.Commit
= new CommitDelegate(Commit
);
145 newVtable
.Revert
= new RevertDelegate(Revert
);
146 newVtable
.LockRegion
= new LockRegionDelegate(LockRegion
);
147 newVtable
.UnlockRegion
= new UnlockRegionDelegate(UnlockRegion
);
148 newVtable
.Stat
= new StatDelegate(Stat
);
149 newVtable
.Clone
= new CloneDelegate(Clone
);
150 managedVtable
= newVtable
;
155 private ManagedToNativeWrapper(IStream managedInterface
)
157 IStreamInterface newInterface
;
161 // Vtable may have been disposed when shutting down
162 if (vtableRefCount
== 0 && comVtable
== IntPtr
.Zero
)
169 this.managedInterface
= managedInterface
;
170 gcHandle
= GCHandle
.Alloc(this);
172 newInterface
= new IStreamInterface();
173 newInterface
.lpVtbl
= comVtable
;
174 newInterface
.gcHandle
= (IntPtr
)gcHandle
;
175 comInterface
= Marshal
.AllocHGlobal(Marshal
.SizeOf(typeof(IStreamInterface
)));
176 Marshal
.StructureToPtr(newInterface
, comInterface
, false);
185 private void Dispose()
187 if (gcHandle
.IsAllocated
)
190 if (comInterface
!= IntPtr
.Zero
)
192 Marshal
.FreeHGlobal(comInterface
);
193 comInterface
= IntPtr
.Zero
;
196 managedInterface
= null;
200 // Dispose vtable when shutting down
201 if (--vtableRefCount
== 0 && Environment
.HasShutdownStarted
)
206 private static void OnShutdown(object sender
, EventArgs e
)
210 // There may be object instances when shutting down
211 if (vtableRefCount
== 0 && comVtable
!= IntPtr
.Zero
)
216 private static void CreateVtable()
218 comVtable
= Marshal
.AllocHGlobal(Marshal
.SizeOf(typeof(IStreamVtbl
)));
219 Marshal
.StructureToPtr(managedVtable
, comVtable
, false);
222 private static void DisposeVtable()
224 Marshal
.DestroyStructure(comVtable
, typeof(IStreamVtbl
));
225 Marshal
.FreeHGlobal(comVtable
);
226 comVtable
= IntPtr
.Zero
;
229 internal static IStream
GetUnderlyingInterface(IntPtr comInterface
, bool outParam
)
231 if (Marshal
.ReadIntPtr(comInterface
) == comVtable
)
233 IStream managedInterface
= GetObject(comInterface
).managedInterface
;
236 Release(comInterface
);
238 return managedInterface
;
244 internal static IntPtr
GetInterface(IStream managedInterface
)
248 if (managedInterface
== null)
250 #if !RECURSIVE_WRAPPING
251 else if ((comInterface
= NativeToManagedWrapper
.GetUnderlyingInterface(managedInterface
)) == IntPtr
.Zero
)
253 comInterface
= new ManagedToNativeWrapper(managedInterface
).comInterface
;
258 internal static void ReleaseInterface(IntPtr comInterface
)
260 if (comInterface
!= IntPtr
.Zero
)
262 IntPtr vtable
= Marshal
.ReadIntPtr(comInterface
);
264 if (vtable
== comVtable
)
265 Release(comInterface
);
268 ReleaseSlot releaseSlot
= (ReleaseSlot
)Marshal
.PtrToStructure((IntPtr
)((long)vtable
+ (long)(IntPtr
.Size
* 2)), typeof(ReleaseSlot
));
269 releaseSlot
.Release(comInterface
);
274 // Mono does not implement Marshal.GetHRForException
275 private static int GetHRForException(Exception e
)
277 return (int)exceptionGetHResult
.Invoke(e
, null);
280 private static ManagedToNativeWrapper
GetObject(IntPtr
@this)
282 return (ManagedToNativeWrapper
)((GCHandle
)Marshal
.ReadIntPtr(@this, IntPtr
.Size
)).Target
;
285 private static int QueryInterface(IntPtr
@this, ref Guid riid
, IntPtr ppvObject
)
291 if (IID_IUnknown
.Equals(riid
) || IID_IStream
.Equals(riid
))
293 Marshal
.WriteIntPtr(ppvObject
, @this);
299 Marshal
.WriteIntPtr(ppvObject
, IntPtr
.Zero
);
300 return E_NOINTERFACE
;
306 return GetHRForException(e
);
311 private static int AddRef(IntPtr
@this)
317 ManagedToNativeWrapper thisObject
= GetObject(@this);
321 return ++thisObject
.refCount
;
332 private static int Release(IntPtr
@this)
338 ManagedToNativeWrapper thisObject
= GetObject(@this);
342 if ((thisObject
.refCount
!= 0) && (--thisObject
.refCount
== 0))
343 thisObject
.Dispose();
345 return thisObject
.refCount
;
356 private static int Read(IntPtr
@this, byte[] pv
, int cb
, IntPtr pcbRead
)
362 GetObject(@this).managedInterface
.Read(pv
, cb
, pcbRead
);
368 return GetHRForException(e
);
373 private static int Write(IntPtr
@this, byte[] pv
, int cb
, IntPtr pcbWritten
)
379 GetObject(@this).managedInterface
.Write(pv
, cb
, pcbWritten
);
385 return GetHRForException(e
);
390 private static int Seek(IntPtr
@this, long dlibMove
, int dwOrigin
, IntPtr plibNewPosition
)
396 GetObject(@this).managedInterface
.Seek(dlibMove
, dwOrigin
, plibNewPosition
);
402 return GetHRForException(e
);
407 private static int SetSize(IntPtr
@this, long libNewSize
)
413 GetObject(@this).managedInterface
.SetSize(libNewSize
);
419 return GetHRForException(e
);
424 private static int CopyTo(IntPtr
@this, IStream pstm
, long cb
, IntPtr pcbRead
, IntPtr pcbWritten
)
430 GetObject(@this).managedInterface
.CopyTo(pstm
, cb
, pcbRead
, pcbWritten
);
436 return GetHRForException(e
);
441 private static int Commit(IntPtr
@this, int grfCommitFlags
)
447 GetObject(@this).managedInterface
.Commit(grfCommitFlags
);
453 return GetHRForException(e
);
458 private static int Revert(IntPtr
@this)
464 GetObject(@this).managedInterface
.Revert();
470 return GetHRForException(e
);
475 private static int LockRegion(IntPtr
@this, long libOffset
, long cb
, int dwLockType
)
481 GetObject(@this).managedInterface
.LockRegion(libOffset
, cb
, dwLockType
);
487 return GetHRForException(e
);
492 private static int UnlockRegion(IntPtr
@this, long libOffset
, long cb
, int dwLockType
)
498 GetObject(@this).managedInterface
.UnlockRegion(libOffset
, cb
, dwLockType
);
504 return GetHRForException(e
);
509 private static int Stat(IntPtr
@this, out STATSTG pstatstg
, int grfStatFlag
)
515 GetObject(@this).managedInterface
.Stat(out pstatstg
, grfStatFlag
);
521 pstatstg
= new STATSTG();
522 return GetHRForException(e
);
527 private static int Clone(IntPtr
@this, out IntPtr ppstm
)
534 IStream newInterface
;
536 GetObject(@this).managedInterface
.Clone(out newInterface
);
537 ppstm
= ManagedToNativeWrapper
.GetInterface(newInterface
);
543 return GetHRForException(e
);
549 // Managed Runtime Callable Wrapper implementation
550 private sealed class NativeToManagedWrapper
: IStream
552 private IntPtr comInterface
;
553 private IStreamVtbl managedVtable
;
555 private NativeToManagedWrapper(IntPtr comInterface
, bool outParam
)
557 this.comInterface
= comInterface
;
558 managedVtable
= (IStreamVtbl
)Marshal
.PtrToStructure(Marshal
.ReadIntPtr(comInterface
), typeof(IStreamVtbl
));
560 managedVtable
.AddRef(comInterface
);
563 ~
NativeToManagedWrapper()
568 private void Dispose(bool disposing
)
570 managedVtable
.Release(comInterface
);
573 comInterface
= IntPtr
.Zero
;
574 managedVtable
= null;
575 GC
.SuppressFinalize(this);
579 internal static IntPtr
GetUnderlyingInterface(IStream managedInterface
)
581 if (managedInterface
is NativeToManagedWrapper
)
583 NativeToManagedWrapper wrapper
= (NativeToManagedWrapper
)managedInterface
;
585 wrapper
.managedVtable
.AddRef(wrapper
.comInterface
);
586 return wrapper
.comInterface
;
592 internal static IStream
GetInterface(IntPtr comInterface
, bool outParam
)
594 IStream managedInterface
;
596 if (comInterface
== IntPtr
.Zero
)
598 #if !RECURSIVE_WRAPPING
599 else if ((managedInterface
= ManagedToNativeWrapper
.GetUnderlyingInterface(comInterface
, outParam
)) == null)
601 managedInterface
= (IStream
)new NativeToManagedWrapper(comInterface
, outParam
);
603 return managedInterface
;
606 internal static void ReleaseInterface(IStream managedInterface
)
608 if (managedInterface
is NativeToManagedWrapper
)
609 ((NativeToManagedWrapper
)managedInterface
).Dispose(true);
612 // Mono does not implement Marshal.ThrowExceptionForHR
613 private static void ThrowExceptionForHR(int result
)
616 throw new COMException(null, result
);
619 public void Read(byte[] pv
, int cb
, IntPtr pcbRead
)
621 ThrowExceptionForHR(managedVtable
.Read(comInterface
, pv
, cb
, pcbRead
));
624 public void Write(byte[] pv
, int cb
, IntPtr pcbWritten
)
626 ThrowExceptionForHR(managedVtable
.Write(comInterface
, pv
, cb
, pcbWritten
));
629 public void Seek(long dlibMove
, int dwOrigin
, IntPtr plibNewPosition
)
631 ThrowExceptionForHR(managedVtable
.Seek(comInterface
, dlibMove
, dwOrigin
, plibNewPosition
));
634 public void SetSize(long libNewSize
)
636 ThrowExceptionForHR(managedVtable
.SetSize(comInterface
, libNewSize
));
639 public void CopyTo(IStream pstm
, long cb
, IntPtr pcbRead
, IntPtr pcbWritten
)
641 ThrowExceptionForHR(managedVtable
.CopyTo(comInterface
, pstm
, cb
, pcbRead
, pcbWritten
));
644 public void Commit(int grfCommitFlags
)
646 ThrowExceptionForHR(managedVtable
.Commit(comInterface
, grfCommitFlags
));
651 ThrowExceptionForHR(managedVtable
.Revert(comInterface
));
654 public void LockRegion(long libOffset
, long cb
, int dwLockType
)
656 ThrowExceptionForHR(managedVtable
.LockRegion(comInterface
, libOffset
, cb
, dwLockType
));
659 public void UnlockRegion(long libOffset
, long cb
, int dwLockType
)
661 ThrowExceptionForHR(managedVtable
.UnlockRegion(comInterface
, libOffset
, cb
, dwLockType
));
664 public void Stat(out STATSTG pstatstg
, int grfStatFlag
)
666 ThrowExceptionForHR(managedVtable
.Stat(comInterface
, out pstatstg
, grfStatFlag
));
669 public void Clone(out IStream ppstm
)
673 ThrowExceptionForHR(managedVtable
.Clone(comInterface
, out newInterface
));
674 ppstm
= NativeToManagedWrapper
.GetInterface(newInterface
, true);
678 private static readonly ComIStreamMarshaler defaultInstance
= new ComIStreamMarshaler();
680 private ComIStreamMarshaler()
684 private static ICustomMarshaler
GetInstance(string cookie
)
686 return defaultInstance
;
689 public IntPtr
MarshalManagedToNative(object managedObj
)
691 #if RECURSIVE_WRAPPING
692 managedObj
= NativeToManagedWrapper
.GetInterface(ManagedToNativeWrapper
.GetInterface((IStream
)managedObj
), true);
694 return ManagedToNativeWrapper
.GetInterface((IStream
)managedObj
);
697 public void CleanUpNativeData(IntPtr pNativeData
)
699 ManagedToNativeWrapper
.ReleaseInterface(pNativeData
);
702 public object MarshalNativeToManaged(IntPtr pNativeData
)
704 #if RECURSIVE_WRAPPING
705 pNativeData
= ManagedToNativeWrapper
.GetInterface(NativeToManagedWrapper
.GetInterface(pNativeData
, true));
707 return NativeToManagedWrapper
.GetInterface(pNativeData
, false);
710 public void CleanUpManagedData(object managedObj
)
712 NativeToManagedWrapper
.ReleaseInterface((IStream
)managedObj
);
715 public int GetNativeDataSize()