You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C#非托管内存分配引用类型数组触发AccessViolationException问题求助

Hey there, let's dig into why you're hitting that AccessViolationException and how to fix this issue with unmanaged arrays of reference types.

Why the AccessViolationException Happens

The core issue here is that you're trying to trick the CLR into treating unmanaged memory as a managed reference-type array—and the CLR really doesn't like that. Here's the breakdown:

  • CLR expects managed references to point to the managed heap: When you store a reference to your unmanaged Test object in the array, the CLR sees that as a normal managed object reference. The GC scans these references to track live objects, but your unmanaged "objects" aren't in the managed heap and aren't registered with the GC. When the GC tries to validate or interact with these references (even implicitly), it can stumble into invalid memory, causing the crash.
  • Unmanaged objects lack proper CLR object metadata: While you wrote the type pointer to unmanaged memory, CLR-managed objects have a full object header (sync block index, type handle, etc.) that's initialized by the runtime during object creation. Your manual setup skips this critical initialization, so when the CLR tries to access array[0].Value, it's trying to parse memory that doesn't match its expected structure.
  • Reference-type arrays have hidden CLR requirements: Managed arrays have a specific memory layout (method table pointer, length, then elements) that your InitializeArray method only partially replicates. The CLR relies on these internal details for operations like bounds checking and GC scanning, which breaks when working with unmanaged memory pretending to be a managed array.

Your workaround with structs works because structs are value types—when you store a struct containing an IntPtr in the array, the CLR doesn't treat that pointer as a managed object reference. It just sees a raw value, so the GC leaves it alone, avoiding the invalid memory access.

Solutions to Fix the Issue

Here are a few solid approaches to get this working safely:

1. Stick with Struct Wrappers (Your Proven Workaround)

Formalize the struct wrapper to encapsulate access to your unmanaged objects, so you avoid exposing raw unmanaged references to the CLR's type system:

public struct UnmanagedTest
{
    private IntPtr _objectPtr;

    // Create from an existing unmanaged Test object
    public static UnmanagedTest From(Test unmanagedTest)
    {
        return new UnmanagedTest { _objectPtr = Unmanaged<Test>.Find(unmanagedTest) };
    }

    // Access properties safely by reading/writing directly to unmanaged memory
    public int Value
    {
        get
        {
            // Calculate the offset of the Value field (adjust based on your Test class layout)
            int fieldOffset = IntPtr.Size; // Skip the type pointer in your unmanaged object
            return Marshal.ReadInt32(_objectPtr + fieldOffset);
        }
        set
        {
            int fieldOffset = IntPtr.Size;
            Marshal.WriteInt32(_objectPtr + fieldOffset, value);
        }
    }

    // Get the wrapped Test instance if needed
    public Test GetInstance() => Unmanaged<Test>.Create(_objectPtr);
}

Then use it with your unmanaged array:

var array = UnmanagedArray<UnmanagedTest>.New(2);
array[0] = UnmanagedTest.From(test);
Console.WriteLine(array[0].Value); // No crash!

2. Avoid Mimicking Managed Arrays Entirely

Instead of forcing unmanaged memory to act like a T[], manage the array manually using raw IntPtr operations. This eliminates any CLR assumptions about the memory:

public static class UnmanagedArrayHelper<T> where T : class
{
    public static IntPtr Create(int count)
    {
        // Header: 2*IntPtr.Size (method table + length) + element pointers
        int headerSize = 2 * IntPtr.Size;
        int totalSize = headerSize + count * IntPtr.Size;
        IntPtr arrayHandle = Marshal.AllocHGlobal(totalSize);

        // Write array length (skip method table if we don't need to cast to T[])
        Marshal.WriteInt32(arrayHandle + headerSize - sizeof(int), count);

        // Initialize all element pointers to zero
        for (int i = 0; i < count; i++)
        {
            Marshal.WriteIntPtr(arrayHandle + headerSize + i * IntPtr.Size, IntPtr.Zero);
        }

        return arrayHandle;
    }

    public static void SetElement(IntPtr arrayHandle, int index, T obj)
    {
        int headerSize = 2 * IntPtr.Size;
        IntPtr elementPtr = arrayHandle + headerSize + index * IntPtr.Size;
        Marshal.WriteIntPtr(elementPtr, Unmanaged<T>.Find(obj));
    }

    public static T GetElement(IntPtr arrayHandle, int index)
    {
        int headerSize = 2 * IntPtr.Size;
        IntPtr elementPtr = arrayHandle + headerSize + index * IntPtr.Size;
        IntPtr objPtr = Marshal.ReadIntPtr(elementPtr);
        return Unmanaged<T>.Create(objPtr);
    }

    public static void Destroy(IntPtr arrayHandle)
    {
        Marshal.FreeHGlobal(arrayHandle);
    }
}

Usage example:

IntPtr arrayHandle = UnmanagedArrayHelper<Test>.Create(2);
UnmanagedArrayHelper<Test>.SetElement(arrayHandle, 0, test);
Test retrieved = UnmanagedArrayHelper<Test>.GetElement(arrayHandle, 0);
Console.WriteLine(retrieved.Value);

Unmanaged<Test>.Destroy(test);
UnmanagedArrayHelper<Test>.Destroy(arrayHandle);

3. Use a Managed Array for Struct Wrappers

If you don't need the entire array to be in unmanaged memory, use a normal managed array of your struct wrapper. This lets the GC handle the array itself while you manage the unmanaged objects:

// Managed array of struct wrappers
UnmanagedTest[] managedArray = new UnmanagedTest[2];
managedArray[0] = UnmanagedTest.From(test);

Console.WriteLine(managedArray[0].Value);

// Clean up unmanaged objects manually
Unmanaged<Test>.Destroy(test);

Key Notes to Remember

  • Unmanaged objects don't run constructors: If your Test class has constructor logic, you'll need to manually invoke it using DynamicMethod or reflection on the unmanaged memory pointer.
  • Nested references are risky: If Test contains other reference-type fields (like string), those fields will be treated as managed references by the CLR. This can lead to GC corruption unless you also wrap those nested references in structs or manage them entirely in unmanaged memory.
  • Always clean up unmanaged memory: Make sure you call Destroy for every unmanaged object and array to avoid memory leaks.

内容的提问来源于stack exchange,提问作者Feralnex

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.04 10:40:39