Skip to content

Commit 4ea649c

Browse files
Allow IStream to marshal from native to managed (#369)
1 parent ee968d4 commit 4ea649c

3 files changed

Lines changed: 146 additions & 2 deletions

File tree

src/Microsoft.DiaSymReader.Tests/SymUnmanagedFactoryTests.cs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@
99
using Roslyn.Test.Utilities;
1010
using Xunit;
1111

12+
#if NET9_0_OR_GREATER
13+
using System.Runtime.InteropServices.Marshalling;
14+
#endif
15+
1216
namespace Microsoft.DiaSymReader.UnitTests
1317
{
1418
public class SymUnmanagedFactoryTests
@@ -62,5 +66,38 @@ public void GetEnvironmentVariable()
6266
{
6367
Assert.NotNull(SymUnmanagedFactory.GetEnvironmentVariable("MICROSOFT_DIASYMREADER_NATIVE_ALT_LOAD_PATH"));
6468
}
69+
70+
#if NET9_0_OR_GREATER
71+
/// <summary>
72+
/// This test is intended to verify that the marshalling doesn't fall over when we have to do the marshalling conversion from
73+
/// <see cref="System.Runtime.InteropServices.ComTypes.IStream"/> to <see cref="IUnsafeComStream"/>.
74+
/// </summary>
75+
[ConditionalFact(typeof(WindowsOnly))]
76+
public unsafe void ComWrappersCanMarshalIStream()
77+
{
78+
var pdbStream = new MemoryStream(TestResources.SourceLink.WindowsPdb);
79+
var reader = SymUnmanagedReaderFactory.CreateReader<ISymUnmanagedReader5>(pdbStream,
80+
DummySymReaderMetadataProvider.Instance, SymUnmanagedReaderCreationOptions.UseAlternativeLoadPath);
81+
Assert.NotNull(reader);
82+
83+
IUnsafeComStream stream = new ComMemoryStream();
84+
stream.SetSize((int)pdbStream.Length);
85+
86+
fixed (byte* pBytes = TestResources.SourceLink.WindowsPdb)
87+
{
88+
int written = 0;
89+
stream.Write(pBytes, TestResources.SourceLink.WindowsPdb.Length, &written);
90+
91+
long newPos = 0;
92+
stream.Seek(0, 0, &newPos);
93+
}
94+
95+
IntPtr pStream = (IntPtr)ComInterfaceMarshaller<IUnsafeComStream>.ConvertToUnmanaged(stream);
96+
System.Runtime.InteropServices.ComTypes.IStream comStream = ComStreamWrapper.Marshaller.ConvertToManaged(pStream);
97+
98+
int hr = reader.UpdateSymbolStore(null, comStream);
99+
Assert.Equal(0, hr);
100+
}
101+
#endif
65102
}
66103
}

src/Microsoft.DiaSymReader/Utilities/ComStreamWrapper.cs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33
// See the License.txt file in the project root for more information.
44

5+
using Microsoft.DiaSymReader.Utilities;
56
using System;
67
using System.Diagnostics;
78
using System.IO;
@@ -208,8 +209,17 @@ public static IntPtr ConvertToUnmanaged(System.Runtime.InteropServices.ComTypes.
208209
throw new NotSupportedException("IStream implementation cannot be marshalled");
209210
}
210211

211-
public static System.Runtime.InteropServices.ComTypes.IStream ConvertToManaged(IntPtr s)
212-
=> throw new NotSupportedException("IStream cannot be marshalled to managed");
212+
public static System.Runtime.InteropServices.ComTypes.IStream ConvertToManaged(IntPtr native)
213+
{
214+
IUnsafeComStream marshalledStream = ComInterfaceMarshaller<IUnsafeComStream>.ConvertToManaged((void*)native);
215+
if (marshalledStream is null)
216+
{
217+
throw new NotSupportedException("IStream cannot be marshalled to managed");
218+
}
219+
220+
// Put the ComWrappers-compatible IUnsafeComStream into a wrapper that also implements the ComWrappers-incompatible IStream
221+
return new UnsafeComStreamWrapper(marshalledStream);
222+
}
213223

214224
public static void Free(IntPtr unmanaged)
215225
{
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
// Licensed to the.NET Foundation under one or more agreements.
2+
// The.NET Foundation licenses this file to you under the MIT license.
3+
// See the License.txt file in the project root for more information.
4+
5+
using System;
6+
using System.IO;
7+
using System.Runtime.CompilerServices;
8+
using System.Runtime.InteropServices.ComTypes;
9+
10+
#if NET9_0_OR_GREATER
11+
using System.Runtime.InteropServices.Marshalling;
12+
#endif
13+
14+
namespace Microsoft.DiaSymReader.Utilities;
15+
16+
/// <summary>
17+
/// Provides an <see cref="IStream"/>-like wrapper around an <see cref="IUnsafeComStream"/> to handle incompatibilities
18+
/// introduced with source-generated ComWrappers.
19+
/// </summary>
20+
/// <remarks>
21+
/// <see cref="ComStreamWrapper"/> is a wrapper around a <see cref="Stream"/> that implements both interface definitions.
22+
/// This is a wrapper around a marshalled <see cref="IUnsafeComStream"/> that also implements the <see cref="IStream"/> interface./>
23+
/// </remarks>
24+
#if NET9_0_OR_GREATER
25+
[GeneratedComClass]
26+
#endif
27+
internal partial class UnsafeComStreamWrapper : IUnsafeComStream, IStream
28+
{
29+
private readonly IUnsafeComStream _stream;
30+
31+
public UnsafeComStreamWrapper(IUnsafeComStream stream)
32+
{
33+
_stream = stream;
34+
}
35+
36+
#region IUnsafeComStream
37+
38+
public unsafe void Read(byte* pv, int cb, int* pcbRead) => _stream.Read(pv, cb, pcbRead);
39+
40+
public unsafe void Write(byte* pv, int cb, int* pcbWritten) => _stream.Write(pv, cb, pcbWritten);
41+
42+
public unsafe void Seek(long dlibMove, int dwOrigin, long* plibNewPosition) => _stream.Seek(dlibMove, dwOrigin, plibNewPosition);
43+
44+
public void SetSize(long libNewSize) => _stream.SetSize(libNewSize);
45+
46+
public unsafe void CopyTo(IntPtr pstm, long cb, int* pcbRead, int* pcbWritten) => _stream.CopyTo(pstm, cb, pcbRead, pcbWritten);
47+
48+
public void Commit(int grfCommitFlags) => _stream.Commit(grfCommitFlags);
49+
50+
public void Revert() => _stream.Revert();
51+
52+
public void LockRegion(long libOffset, long cb, int dwLockType) => _stream.LockRegion(libOffset, cb, dwLockType);
53+
54+
public void UnlockRegion(long libOffset, long cb, int dwLockType) => _stream.UnlockRegion(libOffset, cb, dwLockType);
55+
56+
public void Stat(out STATSTG pstatstg, int grfStatFlag) => _stream.Stat(out pstatstg, grfStatFlag);
57+
58+
public void Clone(out IntPtr ppstm) => _stream.Clone(out ppstm);
59+
60+
#endregion
61+
62+
#region IStream
63+
64+
public void Clone(out IStream ppstm) => throw new NotSupportedException("Clone is not supported in this version.");
65+
66+
public void CopyTo(IStream pstm, long cb, IntPtr pcbRead, IntPtr pcbWritten) => throw new NotImplementedException("CopyTo is not implemented in this version.");
67+
68+
public unsafe void Read(byte[] pv, int cb, IntPtr pcbRead)
69+
{
70+
fixed (byte* pByte = pv)
71+
{
72+
_stream.Read(pByte, cb, (int*)pcbRead);
73+
}
74+
}
75+
76+
public unsafe void Seek(long dlibMove, int dwOrigin, IntPtr plibNewPosition) => _stream.Seek(dlibMove, dwOrigin, (long*)plibNewPosition);
77+
78+
public unsafe void Write(byte[] pv, int cb, IntPtr pcbWritten)
79+
{
80+
fixed (byte* pByte = pv)
81+
{
82+
_stream.Write(pByte, cb, (int*)pcbWritten);
83+
}
84+
}
85+
86+
void System.Runtime.InteropServices.ComTypes.IStream.Stat(out System.Runtime.InteropServices.ComTypes.STATSTG pstatstg, int grfStatFlag)
87+
{
88+
_stream.Stat(out var unsafeSTASTG, grfStatFlag);
89+
90+
pstatstg = new System.Runtime.InteropServices.ComTypes.STATSTG()
91+
{
92+
cbSize = unsafeSTASTG.cbSize,
93+
};
94+
}
95+
96+
#endregion
97+
}

0 commit comments

Comments
 (0)