Hacktoberfest 2026:メンテナが10月に向けて印を付けた、オープンで初心者向けの issue。 Hacktoberfest の issue を見る

`ref`/`out ComPtr<T>` overloads don't pin: a GC during the native call loses the result and corrupts the heap

オープン
#2,632 コメント 0 件 リアクション 2 件 担当者 0 名 GitHub で見る

メンテナーはふだん 1 日以内に返信

まだ誰も着手していません。

評価

難易度
4/5
見積もり時間
3〜5日
初心者へのやさしさ
30/100
issue の種類
バグ
明瞭さ
明確に書かれている
活発さ
活発
技術スタック
csharp

調査の方向性

The fix is in the code generator: src/Core/Silk.NET.BuildTools/Overloading/Complex/ComPtrOverloader.cs, where the generated ref and out ComPtr<T> overloads pass param.GetAddressOf() at lines 61 and 95. ComPtr<T>.GetAddressOf() in Silk.NET.Core/Native/ComPtr1.csreturns an unpinned address. Done means the generated overloads pin the parameter withfixedand regenerated bindings no longer callGetAddressOf(), with a GC-stress repro (a Holderclass with aComPtr` field, compacting GCs on another thread) returning a non-null device.

索引モデルが issue の本文から書いたものです。

説明

Summary

The generated overloads that take a ref ComPtr<T> or out ComPtr<T> pass param.GetAddressOf() to the native function (ComPtrOverloader.cs, lines 61 and 95).

ComPtr<T>.GetAddressOf() is (T**) Unsafe.AsPointer(ref Unsafe.AsRef(in this)), so the pointer is not pinned.

When the ComPtr<T> is a field of a class (or an element of an array), another thread can run a GC during the native call and move the object. The native function then writes its result to the old address:

  • The call returns S_OK, but the field is still null.
  • One pointer per output is written into GC heap memory that may now belong to another object. In our runs this crashed the process inside the GC ("Internal CLR error 0x80131506").

Input arrays (e.g. OMSetRenderTargets(n, ref views[0], dsv) with a ComPtr<T>[] field) have the same problem: the native function can read stale memory.

Steps to reproduce

  • Platform: Desktop (Windows 11, WARP)
  • Framework Version: .NET 10
  • API: Direct3D11 (same code path in Direct3D12, DXGI, D3DCompiler)
  • Silk.NET 2.23.0
class Holder
{
    public ComPtr<ID3D11Device> Device;
    public ComPtr<ID3D11DeviceContext> Context;
}

// Thread 2: allocate and call GC.Collect(2, GCCollectionMode.Forced, blocking: true, compacting: true) in a loop.

// Thread 1:
var holder = new Holder();
int hr = d3d11.CreateDevice(default(ComPtr<IDXGIAdapter>), D3DDriverType.Warp, default, 0, null, 0,
    D3D11.SdkVersion, ref holder.Device, null, ref holder.Context);
// hr == S_OK, holder.Device.Handle == null
  1. Run thread 2 (compacting GCs) in the background.
  2. Call CreateDevice as above in a loop, with a new Holder each time.

Every call where the holder moved during CreateDevice returned S_OK with a null device and a null context, and some runs crashed the process. The same code with locals, or with fixed (ID3D11Device** p = &holder.Device.Handle) and the raw overload, never failed.

In a real project (Stride) this showed up on CI as a NullReferenceException in one random D3D11 test, in some runs.

Silk's own D3D11 tutorial has the same pattern (Tutorial 1.2 - Hello quad/Program.cs): its ComPtr locals are captured by window.Load += OnLoad, so the compiler stores them in a closure object on the heap, and they are passed as ref device, ref swapchain, ref vertexBuffer, ...

Comments

Scope in 2.23.0:

Package GetAddressOf() calls in generated overloads
Silk.NET.Direct3D11 4,182
Silk.NET.Direct3D12 5,210
Silk.NET.DXGI 1,123
Silk.NET.Direct3D.Compilers 10,127

The other overload kinds are fine: ref T, ref T* and ref readonly T use fixed, spans forward to them (and there is no Span<ComPtr<T>> overload), this ComPtr<T> receivers are by value, and SilkMarshal.GuidPtrOf<T>() points to type-associated native memory.

Suggested fix: in the generated overloads, pin the parameter instead of calling GetAddressOf():

fixed (ComPtr<TI1>* ppDevicePtr = &ppDevice)
{
    return CreateDevice(..., (ID3D11Device**) ppDevicePtr, ...);
}

fixed on a ref pins the containing object when it is on the heap, and costs almost nothing when it is on the stack.

GetPinnableReference() (ref *GetAddressOf()) has the same problem: fixed (T** p = someComPtrField) pins nothing.

Until then, a note in the docs would help: never pass a ComPtr<T> that lives on the heap to a ref/out ComPtr<T> overload.

主要言語
C#
スター
5.2k
フォーク
481
平均マージ
1日 20時間
マージ済み PR(30日)
5

環境構築

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

dotnet/Silk.NET のほかの issue

dotnet/Silk.NET の issue をすべて見る

似ている issue

C# の issue をもっと見る

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。