Skip to content

Convert IDacDbiInterface to COM interface#125074

Draft
max-charlamb wants to merge 1 commit intodotnet:mainfrom
max-charlamb:droid-debug-dacdbi-com
Draft

Convert IDacDbiInterface to COM interface#125074
max-charlamb wants to merge 1 commit intodotnet:mainfrom
max-charlamb:droid-debug-dacdbi-com

Conversation

@max-charlamb
Copy link
Member

@max-charlamb max-charlamb commented Mar 2, 2026

Summary

Converts IDacDbiInterface from a plain C++ abstract class into a proper COM interface, enabling future implementation in managed C# via the cDAC. This follows the same pattern used by ISOSDacInterfaceSOSDacImpl.

Motivation

This is Phase 1 of enabling the cDAC (contract-based Data Access Component) to implement IDacDbiInterface in managed C#. The prior PR #124836 converted all methods to return HRESULT; this PR completes the COM conversion by making IDacDbiInterface a standard COM interface that can be consumed via [GeneratedComInterface] in C#.

Types of Changes

Each change below was required to convert from a C++ abstract class to a valid COM interface:

1. IUnknown Inheritance

IDacDbiInterface now inherits from IUnknown via MIDL_INTERFACE. This gives the interface a stable GUID identity, enabling QueryInterface discovery and standard COM ref-counting. Required because COM interfaces must derive from IUnknown — it's the root of all COM.

2. Lifecycle: Destroy() → Release()

Removed the custom Destroy() method. COM objects manage their lifetime through AddRef()/Release() ref-counting — when the last reference is released, the destructor runs automatically. Having a separate Destroy() would conflict with this model.

3. QueryInterface/AddRef/Release Implementation

DacDbiInterfaceImpl inherits IUnknown from two paths (via ClrDataAccess and via IDacDbiInterface), creating diamond inheritance. The explicit QI/AddRef/Release implementations resolve this by delegating to ClrDataAccess while also handling the new IDacDbiInterface IID.

4. STDMETHODCALLTYPE Calling Convention

All interface methods now use STDMETHODCALLTYPE (__stdcall on Windows) to match the COM binary standard. This follows the same pattern used by ClrDataAccess in dacimpl.h. Required because COM defines a fixed calling convention for vtable dispatch.

5. bool → BOOL

All bool parameters converted to BOOL (a 32-bit int). C++ bool is 1 byte with platform-dependent behavior, which can't be reliably marshaled across COM boundaries. BOOL is the standard COM boolean type with well-defined size and marshaling.

6. References → Pointers

C++ reference parameters (T&) converted to pointers (T*) with E_POINTER null checks. References are a C++ language feature with no representation in IDL or other languages. COM uses pointers for all indirection, and the null checks follow COM convention for validating output parameters.

7. IAllocator & IMetaDataLookup → IUnknown

These inner callback interfaces now inherit from IUnknown with their own GUIDs. They're implemented by heap-allocated CordbProcess and passed across the DAC/DBI boundary, so they need proper COM identity. CordbProcess::QueryInterface updated to expose them.

8. IStringHolder — NOT Converted

IStringHolder intentionally remains a plain C++ abstract class. It's implemented by stack-allocated StringCopyHolder objects, which violates COM's ref-counting contract (you can't delete this on a stack object). It's represented as an opaque void* in the IDL.

9. Forward Declaration: class → struct

MIDL_INTERFACE(...) expands to struct __declspec(uuid(...)), so forward declarations updated from class IDacDbiInterface to struct IDacDbiInterface to avoid type mismatch.

10. Overload Rename: GetReJitInfoByAddress

COM interfaces cannot have overloaded methods (IDL doesn't support them, and vtable slot ordering would be ambiguous). The second GetReJitInfo overload renamed to GetReJitInfoByAddress to give each method a unique name.

11. Caller-Side Updates

All call sites updated to pass &variable where references became pointers, and to use BOOL/TRUE/FALSE where bool changed.

12. New Files

  • dacdbi.idl — Formal IDL definition of the COM interface (159 methods). Marked [local] since these interfaces are in-process only (no cross-process marshaling).
  • dacdbi_i.cpp — Prebuilt GUID definitions for non-Windows platforms that can't run MIDL.
  • IDacDbiInterface.cs — C# managed interface with [GeneratedComInterface] and all supporting struct definitions.

Testing

  • Local Release build verified (daccess.lib, mscordbi.dll, mscordaccore.dll, mscordaccore_universal.dll) — 0 errors, 0 warnings
  • Internal ICorDebug diagnostic test suite: 761 total, 0 failed, 0 errors (identical to baseline)
  • Verified our build was loaded via deliberate failure injection (poison test)

@dotnet-policy-service
Copy link
Contributor

Tagging subscribers to this area: @steveisok, @tommcdon, @dotnet/dotnet-diag
See info in area-owners.md if you want to be subscribed.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR converts IDacDbiInterface (DAC↔DBI contract) from a plain C++ abstract class to a COM-style interface (IUnknown + GUID), with the goal of enabling a future managed (C#) cDAC implementation and standard COM lifetime management.

Changes:

  • Make IDacDbiInterface a COM interface (MIDL_INTERFACE, IUnknown base) and remove the custom Destroy() lifecycle method in favor of Release().
  • Implement QueryInterface/AddRef/Release on DacDbiInterfaceImpl and update DBI call sites for the new pointer-based out params.
  • Add dacdbi.idl describing the intended COM contract (including callback interfaces).

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
src/coreclr/debug/inc/dacdbiinterface.h Converts IDacDbiInterface to MIDL_INTERFACE + IUnknown, removes Destroy(), adjusts several signatures for COM compatibility.
src/coreclr/debug/inc/dacdbi.idl Adds an IDL definition for the full COM contract and callback interfaces.
src/coreclr/debug/di/rstype.cpp Updates GetExactTypeHandle call to pass an out pointer.
src/coreclr/debug/di/rspriv.h Updates forward-declaration to struct to match MIDL_INTERFACE expansion.
src/coreclr/debug/di/process.cpp Updates metadata query call sites to use pointer out-params; switches DAC teardown from Destroy() to Release().
src/coreclr/debug/di/module.cpp Updates metadata query call sites to use pointer out-params.
src/coreclr/debug/di/divalue.cpp Updates exception stack frame query to pass DacDbiArrayList by pointer.
src/coreclr/debug/daccess/dacdbiimpl.h Declares IUnknown methods and updates signatures to pointer out-params.
src/coreclr/debug/daccess/dacdbiimpl.cpp Implements IUnknown methods; updates implementations for pointer out-params.
Comments suppressed due to low confidence (2)

src/coreclr/debug/daccess/dacdbiimpl.cpp:1201

  • GetMetaDataFileInfoFromPEFile now takes pTimeStamp/pImageSize as pointers, but the implementation unconditionally dereferences them on the success path and also leaves them uninitialized when vmPEAssembly resolves to NULL. For COM-style APIs (and consistent with other methods in this file that return E_POINTER), please validate pTimeStamp/pImageSize (and other required out params like pResult) up-front, return E_POINTER when null, and ensure out values are always initialized on all return paths.
HRESULT DacDbiInterfaceImpl::GetMetaDataFileInfoFromPEFile(VMPTR_PEAssembly vmPEAssembly, DWORD * pTimeStamp, DWORD * pImageSize, IStringHolder* pStrFilename, OUT bool * pResult)
{
    DD_ENTER_MAY_THROW;

    HRESULT hr = S_OK;
    EX_TRY
    {

        DWORD dwDataSize;
        DWORD dwRvaHint;
        PEAssembly * pPEAssembly = vmPEAssembly.GetDacPtr();
        _ASSERTE(pPEAssembly != NULL);
        if (pPEAssembly == NULL)
        {
            *pResult = false;
        }

src/coreclr/debug/daccess/dacdbiimpl.cpp:3756

  • GetStackFramesFromException now takes the DacDbiArrayList as a pointer, but the method doesn’t validate pDacStackFrames before dereferencing it (Alloc / operator[]). Please add an E_POINTER check at the top of the method (and consider initializing it to an empty list on success paths where there are 0 frames).
HRESULT DacDbiInterfaceImpl::GetStackFramesFromException(VMPTR_Object vmObject, DacDbiArrayList<DacExceptionCallStackData>* pDacStackFrames)
{
    DD_ENTER_MAY_THROW;

    HRESULT hr = S_OK;
    EX_TRY
    {

        PTR_Object objPtr = vmObject.GetDacPtr();

    #ifdef _DEBUG

@max-charlamb max-charlamb force-pushed the droid-debug-dacdbi-com branch from 3d9e111 to f3b3cb8 Compare March 2, 2026 21:14
Copilot AI review requested due to automatic review settings March 2, 2026 21:40
@max-charlamb max-charlamb force-pushed the droid-debug-dacdbi-com branch from f3b3cb8 to 1c619d9 Compare March 2, 2026 21:40
@max-charlamb max-charlamb added NO-REVIEW Experimental/testing PR, do NOT review it and removed NO-REVIEW Experimental/testing PR, do NOT review it labels Mar 2, 2026
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 11 out of 11 changed files in this pull request and generated 3 comments.

Comments suppressed due to low confidence (1)

src/coreclr/debug/daccess/dacdbiimpl.cpp:1190

  • GetMetaDataFileInfoFromPEFile now takes multiple pointer parameters (pTimeStamp, pImageSize, pStrFilename, pResult) but does not validate them before dereferencing later in the method. This was previously safe with reference parameters; now a null argument will crash. Add E_POINTER checks (and consider setting outputs to safe defaults on failure paths).
HRESULT DacDbiInterfaceImpl::GetMetaDataFileInfoFromPEFile(VMPTR_PEAssembly vmPEAssembly, DWORD * pTimeStamp, DWORD * pImageSize, IStringHolder* pStrFilename, OUT BOOL * pResult)
{
    DD_ENTER_MAY_THROW;

    HRESULT hr = S_OK;

@max-charlamb max-charlamb force-pushed the droid-debug-dacdbi-com branch from 1c619d9 to 3f45dad Compare March 2, 2026 21:50
Copilot AI review requested due to automatic review settings March 3, 2026 15:36
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 13 out of 13 changed files in this pull request and generated 2 comments.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 13 out of 13 changed files in this pull request and generated 3 comments.

Comments suppressed due to low confidence (1)

src/coreclr/debug/daccess/dacdbiimpl.cpp:1219

  • GetMetaDataFileInfoFromPEFile returns S_OK even when vmPEAssembly.GetDacPtr() is null, but in that path it only sets *pResult = FALSE and leaves *pTimeStamp, *pImageSize, and pStrFilename unchanged. Because these are out parameters, callers could observe stale values if they reuse variables/holders. Recommend explicitly zeroing timestamp/size and clearing the filename holder on the failure path.
HRESULT DacDbiInterfaceImpl::GetMetaDataFileInfoFromPEFile(VMPTR_PEAssembly vmPEAssembly, DWORD * pTimeStamp, DWORD * pImageSize, IStringHolder* pStrFilename, OUT BOOL * pResult)
{
    if (pTimeStamp == NULL || pImageSize == NULL || pStrFilename == NULL || pResult == NULL)
        return E_POINTER;

    DD_ENTER_MAY_THROW;

    HRESULT hr = S_OK;
    EX_TRY
    {

        DWORD dwDataSize;
        DWORD dwRvaHint;
        PEAssembly * pPEAssembly = vmPEAssembly.GetDacPtr();
        _ASSERTE(pPEAssembly != NULL);
        if (pPEAssembly == NULL)
        {
            *pResult = FALSE;
        }
        else
        {
        WCHAR wszFilePath[MAX_LONGPATH] = {0};
        DWORD cchFilePath = MAX_LONGPATH;
        bool ret = ClrDataAccess::GetMetaDataFileInfoFromPEFile(pPEAssembly,
                                                                *pTimeStamp,
                                                                *pImageSize,
                                                                dwDataSize,
                                                                dwRvaHint,
                                                                wszFilePath,
                                                                cchFilePath);

        pStrFilename->AssignCopy(wszFilePath);
        *pResult = ret;
        }

Comment on lines +446 to +455
STDMETHODIMP
DacDbiInterfaceImpl::QueryInterface(THIS_ IN REFIID interfaceId, OUT PVOID* iface)
{
HRESULT hr = S_OK;
EX_TRY
if (IsEqualIID(interfaceId, __uuidof(IDacDbiInterface)))
{
m_pAllocator = NULL;

this->Release();
// Memory is deleted, don't access this object any more
AddRef();
*iface = static_cast<IDacDbiInterface*>(this);
return S_OK;
}
EX_CATCH_HRESULT(hr);
return hr;
return ClrDataAccess::QueryInterface(interfaceId, iface);
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DacDbiInterfaceImpl::QueryInterface dereferences iface without a null check. If a caller passes a null out-pointer (or uses it incorrectly), this will AV before you can return an HRESULT. Consider matching COM expectations here by returning E_POINTER when iface is null and (optionally) setting *iface = nullptr before any IID checks.

Copilot uses AI. Check for mistakes.
Copilot AI review requested due to automatic review settings March 10, 2026 16:49
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 16 out of 16 changed files in this pull request and generated 4 comments.

@max-charlamb max-charlamb force-pushed the droid-debug-dacdbi-com branch 2 times, most recently from 55e1ca0 to cfaad28 Compare March 18, 2026 14:41
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 33 out of 33 changed files in this pull request and generated 6 comments.

Comments suppressed due to low confidence (1)

src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiThreadDumpTests.cs:1

  • DacDbiImpl.EnumerateThreads explicitly filters out threads in Dead or Unstarted state (“Match native: skip dead and unstarted threads”). Comparing the callback count to ThreadStoreData.ThreadCount is likely incorrect if ThreadCount includes those threads. To avoid brittle/flaky validation, compute the expected count by walking the thread list and applying the same state filter (or use a contract API that enumerates with matching semantics, if available).

Comment on lines +447 to +456
DacDbiInterfaceImpl::QueryInterface(THIS_ IN REFIID interfaceId, OUT PVOID* iface)
{
HRESULT hr = S_OK;
EX_TRY
if (IsEqualIID(interfaceId, __uuidof(IDacDbiInterface)))
{
m_pAllocator = NULL;

this->Release();
// Memory is deleted, don't access this object any more
AddRef();
*iface = static_cast<IDacDbiInterface*>(this);
return S_OK;
}
EX_CATCH_HRESULT(hr);
return hr;
return ClrDataAccess::QueryInterface(interfaceId, iface);
}
bool _isDead;
BOOL _isDead;
IfFailThrow(GetProcess()->GetDAC()->IsThreadMarkedDead(m_vmThreadToken, &_isDead));
return _isDead;
Copilot AI review requested due to automatic review settings March 18, 2026 20:16
@max-charlamb max-charlamb force-pushed the droid-debug-dacdbi-com branch from 2ee7c87 to 693593c Compare March 18, 2026 20:28
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

@max-charlamb max-charlamb force-pushed the droid-debug-dacdbi-com branch from ec7f213 to 9bc05d7 Compare March 18, 2026 20:53
Copilot AI review requested due to automatic review settings March 19, 2026 14:34
@max-charlamb max-charlamb force-pushed the droid-debug-dacdbi-com branch from 9bc05d7 to 874452f Compare March 19, 2026 14:34
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 16 out of 16 changed files in this pull request and generated 2 comments.

@max-charlamb max-charlamb force-pushed the droid-debug-dacdbi-com branch from 874452f to 6e98da8 Compare March 19, 2026 17:16
Copilot AI review requested due to automatic review settings March 19, 2026 17:35
@max-charlamb max-charlamb force-pushed the droid-debug-dacdbi-com branch from 6e98da8 to c150f00 Compare March 19, 2026 17:35
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 14 out of 14 changed files in this pull request and generated 3 comments.

Comment on lines +446 to +456
STDMETHODIMP
DacDbiInterfaceImpl::QueryInterface(THIS_ IN REFIID interfaceId, OUT PVOID* iface)
{
HRESULT hr = S_OK;
EX_TRY
if (IsEqualIID(interfaceId, __uuidof(IDacDbiInterface)))
{
m_pAllocator = NULL;

this->Release();
// Memory is deleted, don't access this object any more
AddRef();
*iface = static_cast<IDacDbiInterface*>(this);
return S_OK;
}
EX_CATCH_HRESULT(hr);
return hr;
return ClrDataAccess::QueryInterface(interfaceId, iface);
}
@max-charlamb max-charlamb force-pushed the droid-debug-dacdbi-com branch from c150f00 to 4a6ec2b Compare March 19, 2026 19:49
Copilot AI review requested due to automatic review settings March 19, 2026 19:49
@max-charlamb max-charlamb force-pushed the droid-debug-dacdbi-com branch from 4a6ec2b to b0b4646 Compare March 19, 2026 19:49
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 14 out of 14 changed files in this pull request and generated 5 comments.

Comment on lines +446 to +456
STDMETHODIMP
DacDbiInterfaceImpl::QueryInterface(THIS_ IN REFIID interfaceId, OUT PVOID* iface)
{
HRESULT hr = S_OK;
EX_TRY
if (IsEqualIID(interfaceId, __uuidof(IDacDbiInterface)))
{
m_pAllocator = NULL;

this->Release();
// Memory is deleted, don't access this object any more
AddRef();
*iface = static_cast<IDacDbiInterface*>(this);
return S_OK;
}
EX_CATCH_HRESULT(hr);
return hr;
return ClrDataAccess::QueryInterface(interfaceId, iface);
}
Copy link

Copilot AI Mar 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

QueryInterface dereferences iface without null-checking. If a caller passes nullptr (legal to detect and return E_POINTER for COM APIs), this will AV. Fix by returning E_POINTER when iface == nullptr, and (per COM convention) initializing *iface = nullptr before any IID checks/delegation.

Copilot uses AI. Check for mistakes.
//-----------------------------------------------------------------------------
class IAllocator
{
class IAllocator : public IUnknown {
Copy link

Copilot AI Mar 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The extra spacing before { is inconsistent with surrounding formatting in this header and makes future diffs noisier. Consider normalizing these declarations to the file’s prevailing brace/spacing style.

Copilot uses AI. Check for mistakes.
//-----------------------------------------------------------------------------
class IMetaDataLookup
{
class IMetaDataLookup : public IUnknown {
Copy link

Copilot AI Mar 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The extra spacing before { is inconsistent with surrounding formatting in this header and makes future diffs noisier. Consider normalizing these declarations to the file’s prevailing brace/spacing style.

Copilot uses AI. Check for mistakes.
@max-charlamb max-charlamb force-pushed the droid-debug-dacdbi-com branch from b0b4646 to 7e5576d Compare March 19, 2026 20:12
Convert the native IDacDbiInterface from a C++ abstract class to a proper
COM interface defined in IDL. This includes:

- Add dacdbi.idl with full COM interface definition (159 methods)
- Update dacdbiinterface.h to use MIDL_INTERFACE with proper GUID
- Convert all C++ reference parameters to pointers for COM compatibility
- Update all callers in the debugger DI layer (process.cpp, rsthread.cpp,
  rstype.cpp, module.cpp, divalue.cpp)
- Add managed IDacDbiInterface.cs with [GeneratedComInterface]
- Add prebuilt GUID definitions for cross-platform builds
- Use Interop.BOOL for all BOOL parameters matching native IDL

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings March 19, 2026 20:25
@max-charlamb max-charlamb force-pushed the droid-debug-dacdbi-com branch from 7e5576d to 3a0737e Compare March 19, 2026 20:25
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 14 out of 14 changed files in this pull request and generated 3 comments.

Comment on lines 1199 to 1205
PEAssembly * pPEAssembly = vmPEAssembly.GetDacPtr();
_ASSERTE(pPEAssembly != NULL);
if (pPEAssembly == NULL)
{
*pResult = false;
*pResult = FALSE;
}
else
Copy link

Copilot AI Mar 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In GetMetaDataFileInfoFromPEFile, the pPEAssembly == NULL path returns S_OK with *pResult = FALSE but leaves *pTimeStamp, *pImageSize, and the string holder potentially untouched. Please initialize these outputs to deterministic defaults (e.g., 0 / empty string) so callers don’t observe stale/uninitialized values when the lookup fails early.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants