Skip to content

Commit f37fd76

Browse files
authored
Add Debug Interface Access (DIA) SDK COM wrappers and tests (#7)
Adds struct-based COM wrappers for the DIA SDK interfaces in the new Microsoft.VisualStudio.Debugging.DebugInterfaceAccess namespace, following the same pattern as the existing VS Setup interfaces (IComIID structs with a nested [ComImport] Interface, dual NETFRAMEWORK/static IID, and delegate* vtable calls). All 54 dia2 interfaces are covered, with vtable indices verified against dia2.h. - Documentation is sourced from the DIA headers: IDL helpstrings drive member summaries, SAL annotations drive parameter docs, and csymrow.h field comments enrich the IDiaSymbol boolean flags. - IEnumUnknown, PROPSPEC, and IEnumSTATPROPSTG are generated via CsWin32 (NativeMethods.txt) and aggregated onto Madowaku; net472 IComIID polyfills are added for the generated COM structs. - Adds a CLASSID class with the DiaSource, DiaSourceAlt, and DiaStackWalker CLSIDs. - Tests load the architecture-appropriate msdia140.dll from the Microsoft.Diagnostics.Tracing.TraceEvent.SupportFiles package via Madowaku's ComClassFactory and exercise the load/session/symbol pipeline.
1 parent 3d1bc9f commit f37fd76

67 files changed

Lines changed: 32361 additions & 2 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

Directory.Packages.props

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
<PackageVersion Include="Microsoft.Build" Version="17.14.8" />
1212
<PackageVersion Include="Microsoft.CodeAnalysis.ResxSourceGenerator" Version="5.0.0-1.25277.114" />
1313
<PackageVersion Include="Microsoft.CSharp" Version="4.7.0" />
14+
<PackageVersion Include="Microsoft.Diagnostics.Tracing.TraceEvent.SupportFiles" Version="1.0.23" />
1415
<PackageVersion Include="Microsoft.SourceLink.GitHub" Version="10.0.300" />
1516
<PackageVersion Include="Microsoft.Windows.CsWin32" Version="0.3.275" />
1617
<PackageVersion Include="MinVer" Version="7.0.0" />
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
// Copyright (c) 2025 Jeremy W Kuhne
2+
// SPDX-License-Identifier: MIT
3+
// See LICENSE file in the project root for full license information
4+
5+
using Windows.Win32.Foundation;
6+
using Windows.Win32.System.Com;
7+
8+
namespace Microsoft.VisualStudio.Debugging.DebugInterfaceAccess;
9+
10+
[TestClass]
11+
public unsafe class DiaSourceTests
12+
{
13+
private static string TestPdbPath => Path.ChangeExtension(typeof(DiaSourceTests).Assembly.Location, ".pdb");
14+
15+
[TestMethod]
16+
public void CanCreateDiaSourceFactory()
17+
{
18+
using ComClassFactory factory = Msdia.CreateFactory(CLASSID.DiaSource);
19+
Assert.AreEqual(CLASSID.DiaSource, factory.ClassId);
20+
}
21+
22+
[TestMethod]
23+
public void CanCreateIDiaDataSource()
24+
{
25+
using ComClassFactory factory = Msdia.CreateFactory(CLASSID.DiaSource);
26+
using var dataSource = factory.CreateInstance<IDiaDataSource>();
27+
Assert.IsFalse(dataSource.IsNull);
28+
}
29+
30+
[TestMethod]
31+
public void LoadDataFromPdb_NonExistentPath_Fails()
32+
{
33+
using ComClassFactory factory = Msdia.CreateFactory(CLASSID.DiaSource);
34+
using var dataSource = factory.CreateInstance<IDiaDataSource>();
35+
36+
string missing = Path.Combine(AppContext.BaseDirectory, "this_file_does_not_exist.pdb");
37+
38+
HRESULT result;
39+
fixed (char* path = missing)
40+
{
41+
result = dataSource.Pointer->loadDataFromPdb(path);
42+
}
43+
44+
Assert.IsTrue(result.Failed, "Loading a missing PDB returns a failure HRESULT.");
45+
}
46+
47+
[TestMethod]
48+
public void LoadDataFromPdb_OpenSession_GlobalScopeIsExe()
49+
{
50+
using ComClassFactory factory = Msdia.CreateFactory(CLASSID.DiaSource);
51+
using var dataSource = factory.CreateInstance<IDiaDataSource>();
52+
53+
LoadTestData(dataSource);
54+
55+
using ComScope<IDiaSession> session = default;
56+
dataSource.Pointer->openSession(session).ThrowOnFailure();
57+
Assert.IsFalse(session.IsNull);
58+
59+
using ComScope<IDiaSymbol> globalScope = default;
60+
session.Pointer->get_globalScope(globalScope).ThrowOnFailure();
61+
Assert.IsFalse(globalScope.IsNull);
62+
63+
uint symTag;
64+
globalScope.Pointer->get_symTag(&symTag).ThrowOnFailure();
65+
Assert.AreEqual((uint)SymTagEnum.SymTagExe, symTag);
66+
}
67+
68+
[TestMethod]
69+
public void GlobalScope_HasSymbolName()
70+
{
71+
using ComClassFactory factory = Msdia.CreateFactory(CLASSID.DiaSource);
72+
using var dataSource = factory.CreateInstance<IDiaDataSource>();
73+
74+
LoadTestData(dataSource);
75+
76+
using ComScope<IDiaSession> session = default;
77+
dataSource.Pointer->openSession(session).ThrowOnFailure();
78+
79+
using ComScope<IDiaSymbol> globalScope = default;
80+
session.Pointer->get_globalScope(globalScope).ThrowOnFailure();
81+
82+
using BSTR name = default;
83+
globalScope.Pointer->get_name(&name).ThrowOnFailure();
84+
Assert.IsFalse(name.IsNull, "The global (exe) symbol has a name.");
85+
}
86+
87+
[TestMethod]
88+
public void FindChildren_EnumeratesCompilands()
89+
{
90+
using ComClassFactory factory = Msdia.CreateFactory(CLASSID.DiaSource);
91+
using var dataSource = factory.CreateInstance<IDiaDataSource>();
92+
93+
LoadTestData(dataSource);
94+
95+
using ComScope<IDiaSession> session = default;
96+
dataSource.Pointer->openSession(session).ThrowOnFailure();
97+
98+
using ComScope<IDiaSymbol> globalScope = default;
99+
session.Pointer->get_globalScope(globalScope).ThrowOnFailure();
100+
101+
using ComScope<IDiaEnumSymbols> compilands = default;
102+
globalScope.Pointer->findChildren(
103+
SymTagEnum.SymTagCompiland,
104+
name: default,
105+
compareFlags: 0,
106+
compilands).ThrowOnFailure();
107+
Assert.IsFalse(compilands.IsNull);
108+
109+
int count;
110+
compilands.Pointer->get_Count(&count).ThrowOnFailure();
111+
Assert.IsTrue(count > 0, "The module has at least one compiland.");
112+
113+
using ComScope<IDiaSymbol> compiland = default;
114+
uint fetched;
115+
compilands.Pointer->Next(1, compiland, &fetched).ThrowOnFailure();
116+
Assert.AreEqual(1u, fetched);
117+
118+
uint symTag;
119+
compiland.Pointer->get_symTag(&symTag).ThrowOnFailure();
120+
Assert.AreEqual((uint)SymTagEnum.SymTagCompiland, symTag);
121+
}
122+
123+
[TestMethod]
124+
public void GetEnumTables_ReturnsTables()
125+
{
126+
using ComClassFactory factory = Msdia.CreateFactory(CLASSID.DiaSource);
127+
using var dataSource = factory.CreateInstance<IDiaDataSource>();
128+
129+
LoadTestData(dataSource);
130+
131+
using ComScope<IDiaSession> session = default;
132+
dataSource.Pointer->openSession(session).ThrowOnFailure();
133+
134+
using ComScope<IDiaEnumTables> tables = default;
135+
session.Pointer->getEnumTables(tables).ThrowOnFailure();
136+
Assert.IsFalse(tables.IsNull);
137+
138+
int count;
139+
tables.Pointer->get_Count(&count).ThrowOnFailure();
140+
Assert.IsTrue(count > 0, "A session exposes at least one table.");
141+
}
142+
143+
private static void LoadTestData(ComScope<IDiaDataSource> dataSource)
144+
{
145+
string pdbPath = TestPdbPath;
146+
Assert.IsTrue(File.Exists(pdbPath), $"Test PDB not found at '{pdbPath}'.");
147+
148+
fixed (char* path = pdbPath)
149+
{
150+
dataSource.Pointer->loadDataFromPdb(path).ThrowOnFailure();
151+
}
152+
}
153+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// Copyright (c) 2025 Jeremy W Kuhne
2+
// SPDX-License-Identifier: MIT
3+
// See LICENSE file in the project root for full license information
4+
5+
using System.Runtime.InteropServices;
6+
using Windows.Win32.Foundation;
7+
using Windows.Win32.System.Com;
8+
using Windows.Win32.System.LibraryLoader;
9+
10+
namespace Microsoft.VisualStudio.Debugging.DebugInterfaceAccess;
11+
12+
/// <summary>
13+
/// Loads the architecture-appropriate <c>msdia140.dll</c> (supplied by the
14+
/// Microsoft.Diagnostics.Tracing.TraceEvent.SupportFiles package) and creates DIA class factories from it.
15+
/// </summary>
16+
internal static class Msdia
17+
{
18+
/// <summary>
19+
/// The full path to the <c>msdia140.dll</c> that matches the current process architecture.
20+
/// </summary>
21+
internal static string LibraryPath { get; } = Path.Combine(
22+
AppContext.BaseDirectory,
23+
"Dia",
24+
RuntimeInformation.ProcessArchitecture switch
25+
{
26+
Architecture.X64 => "amd64",
27+
Architecture.X86 => "x86",
28+
Architecture.Arm64 => "arm64",
29+
_ => throw new PlatformNotSupportedException(
30+
$"msdia140.dll is not available for {RuntimeInformation.ProcessArchitecture}.")
31+
},
32+
"msdia140.dll");
33+
34+
// msdia140.dll has a static dependency on the Visual C++ runtime (msvcp140.dll, vcruntime140*.dll), which
35+
// the package places next to it. Loading with LOAD_WITH_ALTERED_SEARCH_PATH makes Windows resolve those
36+
// dependencies from the DLL's own directory rather than the application directory.
37+
private static readonly Lazy<HMODULE> s_module = new(static () =>
38+
HMODULE.LoadModule(LibraryPath, LOAD_LIBRARY_FLAGS.LOAD_WITH_ALTERED_SEARCH_PATH));
39+
40+
/// <summary>
41+
/// Creates a <see cref="ComClassFactory"/> for the given DIA class, loading <c>msdia140.dll</c> on first use.
42+
/// </summary>
43+
/// <param name="classId">The CLSID of the DIA coclass, for example <see cref="CLASSID.DiaSource"/>.</param>
44+
internal static ComClassFactory CreateFactory(Guid classId) => new(s_module.Value, classId);
45+
}

vsinterop.tests/vsinterop.tests.csproj

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,29 @@
2323
</PackageReference>
2424
<PackageReference Include="MSTest" />
2525
<PackageReference Include="AwesomeAssertions" />
26+
27+
<!--
28+
Provides the native msdia140.dll (Debug Interface Access) binaries for each architecture under
29+
lib/native/<arch>. We don't want the managed Dia2Lib.dll interop assembly the package also carries,
30+
so all assets are excluded; GeneratePathProperty still gives us the restored package location for
31+
the copy target below.
32+
-->
33+
<PackageReference Include="Microsoft.Diagnostics.Tracing.TraceEvent.SupportFiles" GeneratePathProperty="true" ExcludeAssets="all" />
2634
</ItemGroup>
2735

36+
<!--
37+
Copy the DIA native binaries (msdia140.dll and its CRT dependencies) for every architecture into a
38+
Dia/<arch> folder next to the test assembly. Tests load the architecture-appropriate copy at runtime.
39+
-->
40+
<Target Name="CopyDiaNativeBinaries" AfterTargets="Build">
41+
<ItemGroup>
42+
<_DiaNativeFile Include="$(PkgMicrosoft_Diagnostics_Tracing_TraceEvent_SupportFiles)\lib\native\**\*.dll" />
43+
</ItemGroup>
44+
<Copy SourceFiles="@(_DiaNativeFile)"
45+
DestinationFiles="@(_DiaNativeFile->'$(OutDir)Dia\%(RecursiveDir)%(Filename)%(Extension)')"
46+
SkipUnchangedFiles="true" />
47+
</Target>
48+
2849
<ItemGroup>
2950
<ProjectReference Include="..\vsinterop\vsinterop.csproj" AdditionalProperties="TargetFramework=$(TargetFramework)" />
3051
</ItemGroup>
@@ -34,11 +55,11 @@
3455
</ItemGroup>
3556

3657
<ItemGroup>
37-
<EditorConfigFiles Remove="N:\repos\vsinterop\vsinterop.tests\.editorconfig" />
58+
<EditorConfigFiles Remove=".editorconfig" />
3859
</ItemGroup>
3960

4061
<ItemGroup>
41-
<None Include="N:\repos\vsinterop\vsinterop.tests\.editorconfig" />
62+
<None Include=".editorconfig" />
4263
</ItemGroup>
4364

4465
</Project>
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
// Copyright (c) 2025 Jeremy W Kuhne
2+
// SPDX-License-Identifier: MIT
3+
// See LICENSE file in the project root for full license information
4+
5+
namespace Microsoft.VisualStudio.Debugging.DebugInterfaceAccess;
6+
7+
/// <summary>
8+
/// Class identifiers (CLSIDs) for the Debug Interface Access (DIA) SDK coclasses exposed by msdia*.dll.
9+
/// </summary>
10+
/// <remarks>
11+
/// <para>
12+
/// Use these with <c>CoCreateInstance</c> or a class factory (for example, by loading <c>msdia140.dll</c>
13+
/// directly and calling <c>DllGetClassObject</c>) to create the corresponding DIA objects.
14+
/// </para>
15+
/// </remarks>
16+
public static class CLASSID
17+
{
18+
/// <summary>
19+
/// CLSID of the <c>DiaSource</c> class, which provides an <see cref="IDiaDataSource"/> that allocates from
20+
/// the process heap and returns <c>BSTR</c> values that can be freed with the standard <c>SysFreeString</c>.
21+
/// </summary>
22+
/// <value>The CLSID <c>{E6756135-1E65-4D17-8576-610761398C3C}</c>.</value>
23+
public static Guid DiaSource { get; } = new(0xe6756135, 0x1e65, 0x4d17, 0x85, 0x76, 0x61, 0x07, 0x61, 0x39, 0x8c, 0x3c);
24+
25+
/// <summary>
26+
/// CLSID of the <c>DiaSourceAlt</c> class, a variant of <c>DiaSource</c> that does not use the system heap.
27+
/// </summary>
28+
/// <remarks>
29+
/// <para>
30+
/// A process may create either <c>DiaSourceAlt</c> objects or <c>DiaSource</c> objects, but not both. When
31+
/// using <c>DiaSourceAlt</c>, all returned <c>BSTR</c> values are really <c>LPCOLESTR</c> and must be released
32+
/// with <c>LocalFree</c> rather than the usual <c>BSTR</c> routines.
33+
/// </para>
34+
/// </remarks>
35+
/// <value>The CLSID <c>{91904831-49CA-4766-B95C-25397E2DD6DC}</c>.</value>
36+
public static Guid DiaSourceAlt { get; } = new(0x91904831, 0x49ca, 0x4766, 0xb9, 0x5c, 0x25, 0x39, 0x7e, 0x2d, 0xd6, 0xdc);
37+
38+
/// <summary>
39+
/// CLSID of the <c>DiaStackWalker</c> class, which provides an <see cref="IDiaStackWalker"/> for performing
40+
/// general stack walks.
41+
/// </summary>
42+
/// <value>The CLSID <c>{CE4A85DB-5768-475B-A4E1-C0BCA2112A6B}</c>.</value>
43+
public static Guid DiaStackWalker { get; } = new(0xce4a85db, 0x5768, 0x475b, 0xa4, 0xe1, 0x0c, 0xbc, 0xa2, 0x11, 0x2a, 0x6b);
44+
}

0 commit comments

Comments
 (0)