我有一个 COM 流对象 ( IStream
),用CreateStreamOnHGlobal
.
我想在同一进程中的不同线程中使用它。我是否需要编组流对象本身(使用CoMarshalInterface
等)?或者它已经是线程安全的了?
EDITED,读/写/搜索与我的代码中的锁正确同步。
我有一个 COM 流对象 ( IStream
),用CreateStreamOnHGlobal
.
我想在同一进程中的不同线程中使用它。我是否需要编组流对象本身(使用CoMarshalInterface
等)?或者它已经是线程安全的了?
EDITED,读/写/搜索与我的代码中的锁正确同步。
COM 将IStream
其视为一种特殊类型的接口,可以安全地跨线程使用。这是必要的,以便可以在IStream
using中跨线程边界封送其他接口CoMarshalInterThreadInterfaceInStream
。
更多信息可以在 Dobb 博士 2003 年的文章:Marshaling COM interfaces中找到。
更新:
最初发布的答案并不完全正确。OLE 提供的IStream
接口实现,由返回CreateStreamOnHGlobal
和间接创建,CoMarshalInterThreadInterfaceInStream
可以在同一进程中跨线程安全地访问。
文档分散且难以获得。CoMarshalInterThreadInterfaceInStream
声明如下:
当在接收线程中运行的客户端尝试解组指针时,ppStm 参数中返回的流保证行为正确。
类似的信息可CreateStreamOnHGlobal
从SHCreateMemStream
:
CreateStreamOnHGlobal 创建的流是线程安全的。
这些保证通常不适用于所有IStream
实现。如果你想安全起见,你总是可以使用 跨线程边界编组接口CoMarshalInterThreadInterfaceInStream
,即使不是绝对必要的。以这种方式编组接口指针永远不会有害,因为如果不需要编组,COM 足够聪明,不会编组(或重新编组)指针。请记住,这是一次编组 - 解组一次。如果要从多个线程解组接口,可以将接口放入Global Interface Table。
根据MSDN编辑:
线程安全。从 Windows 8 开始,由 SHCreateMemStream 创建的流是线程安全的。在早期系统上,流不是线程安全的。 CreateStreamOnHGlobal 创建的流是线程安全的。
我有两个相反的答案,所以我决定验证一下。看起来@HansPassant 是对的,而@IInspectable 是错的。至少,在另一个线程上为原始IStream
对象创建了一个代理。
测试用例显示以下内容:
即使两个线程都属于不同的单元,IStream
仍然可以跨线程使用对的直接引用。它只是工作。
如果两个线程都是 MTA 线程,则IStream
fromthread
将解组thread2
到完全相同的IUnknown
指针,没有代理。
如果thread1
是 STA,并且thread2
是 MTA,则存在代理。但是创建于 的直接引用thread
仍然适用于thread2
。
stream1
请注意如何在来自不同线程的紧密循环内同时执行读取和写入。当然,这没什么意义,通常有锁来同步读/写。然而,它证明了IStream
返回的对象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);
}
}