c# - 如何在非托管内存中实例化 C# 类? (可能的?)

标签 c# .net garbage-collection clr unmanaged-memory

更新:现在有一个“有效”的公认答案。您永远、永远、永远、永远都不应该使用它。 曾经


首先让我声明我是一名游戏开发者,以此作为我的问题的序言。有一个合法的 - 如果非常不寻常 - 与性能相关的原因想要这样做。


假设我有一个这样的 C# 类:

class Foo
{
    public int a, b, c;
    public void MyMethod(int d) { a = d; b = d; c = a + b; }
}

没什么好看的。请注意,它是一种仅包含值类型的引用类型。

在托管代码中我想要这样的东西:

Foo foo;
foo = Voodoo.NewInUnmanagedMemory<Foo>(); // <- ???
foo.MyMethod(1);

NewInUnmanagedMemory 函数会是什么样子?如果不能在 C# 中完成,是否可以在 IL 中完成? (或者可能是 C++/CLI?)

基本上:有没有一种方法——无论多么笨拙——将一些完全任意的指针转换为对象引用。而且 - 除了让 CLR 爆炸 - 该死的后果。

(提出我的问题的另一种方式是:“我想为 C# 实现自定义分配器”)

这引出了后续问题:垃圾收集器在面对指向托管内存之外的引用时会做什么(特定于实现,如果需要)?

并且,与此相关,如果 Foo 将引用作为成员字段,会发生什么情况?如果它指向托管内存怎么办?如果它只指向在非托管内存中分配的其他对象怎么办?

最后,如果这是不可能的:为什么?


更新:以下是到目前为止的“缺失部分”:

#1:如何将 IntPtr 转换为对象引用?尽管无法验证 IL(请参阅评论),这可能是可能的。到目前为止,我还没有运气。该框架似乎非常小心地防止这种情况发生。

(能够在运行时获取非 blittable 托管类型的大小和布局信息也很好。同样,框架试图使这成为不可能。)

#2:假设问题一可以解决 - 当 GC 遇到指向 GC 堆之外的对象引用时,它会做什么?它会崩溃吗?安东·泰希,in his answer ,估计会的。考虑到该框架对防止 #1 的谨慎程度,它似乎确实有可能。确认这一点的东西会很好。

(或者,对象引用可以指向 GC 堆内的固定内存。这会有什么不同吗?)

基于此,我倾向于认为这种 hack 想法是不可能的 - 或者至少不值得付出努力。但我很想得到有关 #1 或 #2 或两者的技术细节的答案。

最佳答案

我一直在尝试在非托管内存中创建类。这是可能的,但有一个我目前无法解决的问题 - 你不能将对象分配给引用类型的字段 - 请参阅底部的编辑 - ,所以你可以在您的自定义类中只有结构字段。 这是邪恶的:

using System;
using System.Reflection.Emit;
using System.Runtime.InteropServices;

public class Voodoo<T> where T : class
{
    static readonly IntPtr tptr;
    static readonly int tsize;
    static readonly byte[] zero;

    public static T NewInUnmanagedMemory()
    {
        IntPtr handle = Marshal.AllocHGlobal(tsize);
        Marshal.Copy(zero, 0, handle, tsize);
        IntPtr ptr = handle+4;
        Marshal.WriteIntPtr(ptr, tptr);
        return GetO(ptr);
    }

    public static void FreeUnmanagedInstance(T obj)
    {
        IntPtr ptr = GetPtr(obj);
        IntPtr handle = ptr-4;
        Marshal.FreeHGlobal(handle);
    }

    delegate T GetO_d(IntPtr ptr);
    static readonly GetO_d GetO;
    delegate IntPtr GetPtr_d(T obj);
    static readonly GetPtr_d GetPtr;
    static Voodoo()
    {
        Type t = typeof(T);
        tptr = t.TypeHandle.Value;
        tsize = Marshal.ReadInt32(tptr, 4);
        zero = new byte[tsize];

        DynamicMethod m = new DynamicMethod("GetO", typeof(T), new[]{typeof(IntPtr)}, typeof(Voodoo<T>), true);
        var il = m.GetILGenerator();
        il.Emit(OpCodes.Ldarg_0);
        il.Emit(OpCodes.Ret);
        GetO = m.CreateDelegate(typeof(GetO_d)) as GetO_d;

        m = new DynamicMethod("GetPtr", typeof(IntPtr), new[]{typeof(T)}, typeof(Voodoo<T>), true);
        il = m.GetILGenerator();
        il.Emit(OpCodes.Ldarg_0);
        il.Emit(OpCodes.Ret);
        GetPtr = m.CreateDelegate(typeof(GetPtr_d)) as GetPtr_d;
    }
}

如果您关心内存泄漏,您应该在完成类(class)后始终调用 FreeUnmanagedInstance。 如果你想要更复杂的解决方案,你可以试试这个:

using System;
using System.Reflection.Emit;
using System.Runtime.InteropServices;


public class ObjectHandle<T> : IDisposable where T : class
{
    bool freed;
    readonly IntPtr handle;
    readonly T value;
    readonly IntPtr tptr;

    public ObjectHandle() : this(typeof(T))
    {

    }

    public ObjectHandle(Type t)
    {
        tptr = t.TypeHandle.Value;
        int size = Marshal.ReadInt32(tptr, 4);//base instance size
        handle = Marshal.AllocHGlobal(size);
        byte[] zero = new byte[size];
        Marshal.Copy(zero, 0, handle, size);//zero memory
        IntPtr ptr = handle+4;
        Marshal.WriteIntPtr(ptr, tptr);//write type ptr
        value = GetO(ptr);//convert to reference
    }

    public T Value{
        get{
            return value;
        }
    }

    public bool Valid{
        get{
            return Marshal.ReadIntPtr(handle, 4) == tptr;
        }
    }

    public void Dispose()
    {
        if(!freed)
        {
            Marshal.FreeHGlobal(handle);
            freed = true;
            GC.SuppressFinalize(this);
        }
    }

    ~ObjectHandle()
    {
        Dispose();
    }

    delegate T GetO_d(IntPtr ptr);
    static readonly GetO_d GetO;
    static ObjectHandle()
    {
        DynamicMethod m = new DynamicMethod("GetO", typeof(T), new[]{typeof(IntPtr)}, typeof(ObjectHandle<T>), true);
        var il = m.GetILGenerator();
        il.Emit(OpCodes.Ldarg_0);
        il.Emit(OpCodes.Ret);
        GetO = m.CreateDelegate(typeof(GetO_d)) as GetO_d;
    }
}

/*Usage*/
using(var handle = new ObjectHandle<MyClass>())
{
    //do some work
}

我希望它能对你的道路有所帮助。

编辑:找到引用类型字段的解决方案:

class MyClass
{
    private IntPtr a_ptr;
    public object a{
        get{
            return Voodoo<object>.GetO(a_ptr);
        }
        set{
            a_ptr = Voodoo<object>.GetPtr(value);
        }
    }
    public int b;
    public int c;
}

编辑:更好的解决方案。只需使用 ObjectContainer<object>而不是 object等等。

public struct ObjectContainer<T> where T : class
{
    private readonly T val;

    public ObjectContainer(T obj)
    {
        val = obj;
    }

    public T Value{
        get{
            return val;
        }
    }

    public static implicit operator T(ObjectContainer<T> @ref)
    {
        return @ref.val;
    }

    public static implicit operator ObjectContainer<T>(T obj)
    {
        return new ObjectContainer<T>(obj);
    }

    public override string ToString()
    {
        return val.ToString();
    }

    public override int GetHashCode()
    {
        return val.GetHashCode();
    }

    public override bool Equals(object obj)
    {
        return val.Equals(obj);
    }
}

关于c# - 如何在非托管内存中实例化 C# 类? (可能的?),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/10800063/

相关文章:

java - 包含线程或 ExecutorServices 的对象的垃圾收集

Android 垃圾收集器在主线程上运行?

c# - 如何使用 XmlSerializer 来处理不同的命名空间版本?

c# - 在类内部时设置属性或字段?

java - 从 Java 调用 .NET DLL

.net - 如何判断类型 A 是否可隐式转换为类型 B

c# - .NET 中的堆栈与堆

c# - 为什么C#/xna中的垃圾回收不会自动处理渲染目标?

c# - 在触发对撞机中使用 IsTouching()

c# - 需要实现 MessageHeaders.WriteHeaderContents 的示例