[英]Do I need to marshal IStream returned by CreateStreamOnHGlobal, for use across threads?
I have a COM stream object ( IStream
), created with CreateStreamOnHGlobal
. 我有一个COM流对象( IStream
),使用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)? 我是否需要编组流对象本身(使用CoMarshalInterface
等)? Or is it thread-safe already? 或者它已经是线程安全的吗?
EDITED , reads/writes/seeks are properly synchronized with locks in my codes. EDITED ,读/写/搜索与我的代码中的锁正确同步。
COM treats IStream
as a special type of interface, which can be safely used across threads. COM将IStream
视为一种特殊类型的接口,可以跨线程安全地使用。 This is necessary so that other interfaces can be marshaled across thread boundaries in an IStream
using CoMarshalInterThreadInterfaceInStream
. 这是必要的,以便可以使用CoMarshalInterThreadInterfaceInStream
跨IStream
线程边界封送其他接口。
Additional information can be found in a 2003 article of Dr. Dobb's: Marshaling COM interfaces . 更多信息可以在2003年Dobb博士的文章中找到: Marshaling COM接口 。
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. 由CreateStreamOnHGlobal
返回并通过CoMarshalInterThreadInterfaceInStream
间接创建的OLE提供的IStream
接口实现可以在同一进程中的线程之间安全地访问。
Documentation is scattered and hard to come by. 文档分散且难以获得。 CoMarshalInterThreadInterfaceInStream
states the following: CoMarshalInterThreadInterfaceInStream
指出以下内容:
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. 当在接收线程中运行的客户端尝试解组指针时,ppStm参数中返回的流保证正常运行。
Similar information is available for CreateStreamOnHGlobal
from SHCreateMemStream
: 来自SHCreateMemStream
CreateStreamOnHGlobal
也有类似的信息:
The stream created by CreateStreamOnHGlobal is thread-safe. CreateStreamOnHGlobal创建的流是线程安全的。
The guarantees do not generally hold true for all IStream
implementations. 对于所有IStream
实现,保证通常不适用。 If you want to play it safe, you can always marshal interfaces across thread boundaries using CoMarshalInterThreadInterfaceInStream
, even if not strictly necessary. 如果您想要安全地使用它,您总是可以使用CoMarshalInterThreadInterfaceInStream
跨越线程边界编组接口,即使不是绝对必要的。 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 . 以这种方式编组接口指针永远不会有害,因为如果不需要编组,COM足够聪明,不会编组(或重新编组)指针 。 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 . 如果要从多个线程解组接口,可以将接口放入全局接口表 。
EDITED , according to MSDN : 根据MSDN 编辑 :
Thread safety. 线程安全。 The stream created by SHCreateMemStream is thread-safe as of Windows 8. On earlier systems, the stream is not thread-safe. 从Windows 8开始,SHCreateMemStream创建的流是线程安全的。在早期的系统上,该流不是线程安全的。 The stream created by CreateStreamOnHGlobal is thread-safe . CreateStreamOnHGlobal创建的流是线程安全的 。
I've got two opposite answers, so I decided to verify it. 我有两个相反的答案,所以我决定验证它。
It looks like @HansPassant is right, and @IInspectable is wrong.
看起来@HansPassant是对的,而且@IInspectable是错的。
At least, a proxy is created on another thread for the original
IStream
object.
至少,在原始
IStream
对象的另一个线程上创建代理。
The test case shows the following: 测试用例显示以下内容:
Even if both threads belong to different apartments, a direct reference to IStream
can still be used across threads. 即使两个线程都属于不同的公寓,也可以跨线程使用对IStream
的直接引用。 It just works. 它只是有效。
If both threads are MTA threads, the IStream
from thread
gets unmarshaled on thread2
to exactly the same IUnknown
pointer, no proxy. 如果两个线程都是MTA线程,则来自thread
的IStream
在thread2
上被解thread2
为完全相同的IUnknown
指针,没有代理。
If thread1
is STA, and the thread2
is MTA, there is a proxy. 如果thread1
是STA,而thread2
是MTA,则有一个代理。 But the direct reference, created on thread
, still works on thread2
. 但是在thread
上创建的直接引用仍然适用于thread2
。
Note how reads and writes are performed on stream1
concurrently, inside tight loops from different threads. 注意如何在不同线程的紧密循环内同时对stream1
执行读写操作。 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. 然而, 它证明了CreateStreamOnHGlobal
返回的IStream
对象是真正的线程安全的。
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
. 我不确定这是否是任何 IStream
实现的官方COM要求,正如Dobb博士的文章所暗示的那样 - 很可能它只是特定于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);
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.