问题
I have a COM stream object (IStream), created with CreateStreamOnHGlobal.
I want to use it across different threads within the same process. Do I need to marshal the stream object itself (with CoMarshalInterface etc)? Or is it thread-safe already?
EDITED, reads/writes/seeks are properly synchronized with locks in my codes.
回答1:
COM treats IStream as a special type of interface, which can be safely used across threads. This is necessary so that other interfaces can be marshaled across thread boundaries in an IStream using CoMarshalInterThreadInterfaceInStream.
Additional information can be found in a 2003 article of Dr. Dobb's: Marshaling COM interfaces.
Update:
The answer, as initially posted, is not entirely correct. The OLE-provided implementation of the IStream interface, as returned by CreateStreamOnHGlobal and indirectly created through CoMarshalInterThreadInterfaceInStream can be safely accessed across threads in the same process.
Documentation is scattered and hard to come by. CoMarshalInterThreadInterfaceInStream states the following:
The stream returned in the ppStm parameter is guaranteed to behave correctly when a client running in the receiving thread attempts to unmarshal the pointer.
Similar information is available for CreateStreamOnHGlobal from SHCreateMemStream:
The stream created by CreateStreamOnHGlobal is thread-safe.
The guarantees do not generally hold true for all IStream implementations. If you want to play it safe, you can always marshal interfaces across thread boundaries using CoMarshalInterThreadInterfaceInStream, even if not strictly necessary. It is never harmful to marshal an interface pointer in this way because COM is smart enough not to marshal (or remarshal) the pointer if marshaling isn't necessary. Keep in mind though that this is marshal once - unmarshal once. If you want to unmarshal the interface from multiple threads you can place the interface into the Global Interface Table.
回答2:
EDITED, according to MSDN:
Thread safety. The stream created by SHCreateMemStream is thread-safe as of Windows 8. On earlier systems, the stream is not thread-safe. The stream created by CreateStreamOnHGlobal is thread-safe.
I've got two opposite answers, so I decided to verify it. It looks like @HansPassant is right, and @IInspectable is wrong. At least, a proxy is created on another thread for the original IStream object.
The test case shows the following:
Even if both threads belong to different apartments, a direct reference to
IStreamcan still be used across threads. It just works.If both threads are MTA threads, the
IStreamfromthreadgets unmarshaled onthread2to exactly the sameIUnknownpointer, no proxy.If
thread1is STA, and thethread2is MTA, there is a proxy. But the direct reference, created onthread, still works onthread2.
Note how reads and writes are performed on stream1 concurrently, inside tight loops from different threads. Of course, it makes very little sense, normally there are locks to synchronize reads/writes. Yet, it proves that IStream object as returned by CreateStreamOnHGlobal is truly thread-safe.
I am not sure if that is an official COM requirement to any IStream implementation, as that Dr. Dobb's article suggests - most likely it is just specific to CreateStreamOnHGlobal.
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Threading;
namespace ConsoleApplication1
{
class Program
{
static void TestStream()
{
// start thread1
var thread1 = new Thread(() =>
{
// create stream1 on thread1
System.Runtime.InteropServices.ComTypes.IStream stream1;
CreateStreamOnHGlobal(IntPtr.Zero, true, out stream1);
IntPtr unkStream1 = Marshal.GetIUnknownForObject(stream1);
// marshal stream1, to be unmarshalled on thread2
Guid iid = typeof(System.Runtime.InteropServices.ComTypes.IStream).GUID;
System.Runtime.InteropServices.ComTypes.IStream marshallerStream;
CoMarshalInterThreadInterfaceInStream(ref iid, stream1, out marshallerStream);
// write to stream1
var buf1 = new byte[] { 1, 2, 3, 4 };
stream1.Write(buf1, buf1.Length, IntPtr.Zero);
// start thread2
var thread2 = new Thread(() =>
{
// read from stream1 (the direct reference) on thread2
var buf2 = new byte[buf1.Length];
for (var i = 0; i < 10000; i++)
{
stream1.Seek(0, 0, IntPtr.Zero);
stream1.Read(buf2, buf2.Length, IntPtr.Zero);
// trule thread safe, this always works!
for (var j = 0; j < buf2.Length; j++)
Debug.Assert(buf1[j] == buf2[j]);
}
// Unmarshal and compare IUnknown pointers
object stream2;
CoGetInterfaceAndReleaseStream(marshallerStream, ref iid, out stream2);
IntPtr unkStream2 = Marshal.GetIUnknownForObject(stream2);
// Bangs if thread1 is STA, works OK if thread1 is MTA
Debug.Assert(unkStream1 == unkStream2);
Marshal.Release(unkStream2);
});
for (var i = 0; i < 10000; i++)
{
stream1.Seek(0, 0, IntPtr.Zero);
stream1.Write(buf1, buf1.Length, IntPtr.Zero);
}
thread2.SetApartmentState(ApartmentState.MTA);
thread2.Start();
thread2.Join();
Marshal.Release(unkStream1);
});
thread1.SetApartmentState(ApartmentState.STA);
thread1.Start();
thread1.Join();
}
static void Main(string[] args)
{
TestStream();
}
[DllImport("ole32.dll", PreserveSig = false)]
public static extern void CreateStreamOnHGlobal(
IntPtr hGlobal,
bool fDeleteOnRelease,
[Out] out System.Runtime.InteropServices.ComTypes.IStream pStream);
[DllImport("ole32.dll", PreserveSig = false)]
public static extern void CoMarshalInterThreadInterfaceInStream(
[In] ref Guid riid,
[MarshalAs(UnmanagedType.IUnknown)] object unk,
out System.Runtime.InteropServices.ComTypes.IStream stream);
[DllImport("ole32.dll", PreserveSig = false)]
public static extern void CoGetInterfaceAndReleaseStream(
[In] System.Runtime.InteropServices.ComTypes.IStream stream,
[In] ref Guid riid,
[Out, MarshalAs(UnmanagedType.IUnknown)] out object unk);
}
}
来源:https://stackoverflow.com/questions/19895635/do-i-need-to-marshal-istream-returned-by-createstreamonhglobal-for-use-across-t