如何在非托管内存中实例化C#类?(可能吗?)
更新:现在有一个公认的答案“有效”。你不应该,永远,永远,永远使用它。永远。如何在非托管内存中实例化C#类?(可能吗?),c#,.net,garbage-collection,clr,unmanaged-memory,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&
首先,让我先说明一下我是一名游戏开发者。想要这样做有一个合理的——如果是非常不寻常的——与性能相关的原因
假设我有这样一个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);
没什么特别的。请注意,它是只包含值类型的引用类型
在托管代码中,我希望有如下内容:
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);
Foo-Foo;
foo=Voodoo.NewInUnmanagedMemory();// 那样的事是不可能的。您可以在不安全的上下文中访问托管内存,但所述内存仍然是托管的,并且受GC的约束
为什么?
简单性和安全性
但现在我想起来了,我认为可以将托管和非托管与C++/CLI混合使用。但我不确定,因为我从来没有用C++和CLI工作过。< /P> < P>你可以用C++编写代码,用P/Unjk调用.NET,或者你可以编写管理C++的代码,让你完全可以从.NET语言内部访问本机API。但是,在托管端,只能使用托管类型,因此必须封装非托管对象
举个简单的例子:允许您在Windows堆上分配内存。返回的句柄在.NET中用处不大,但在调用需要缓冲区的本机Windows API时可能需要它。这是不可能的
但是,您可以使用托管结构并创建此结构类型的指针。此指针可以指向任何位置(包括非托管内存)
问题是,为什么要在非托管内存中拥有一个类?无论如何,你不会得到GC特性。您可以使用指向struct的指针。我不知道如何在非托管堆中保存C#类实例,甚至在C++/CLI中也不知道
“我想为C#实现一个自定义分配器”
GC是CLR的核心。只有微软(或者是Mono的Mono团队)可以取代它,这需要付出巨大的开发成本。GC是CLR的核心,与GC或托管堆混在一起会使CLR崩溃——如果你非常幸运的话,很快就会崩溃
当遇到指向托管内存之外的引用时,垃圾收集器会做什么(如果需要,具体实现)
它以特定于实现的方式崩溃;) 纯C#方法
因此,有几个选择。最简单的方法是在不安全的结构上下文中使用new/delete。第二种方法是使用内置的封送服务来处理非托管内存(代码如下所示)。然而,这两种方法都处理结构(尽管我认为后一种方法非常接近您想要的)。我的代码有一个限制,即您必须始终使用结构,并使用intptr作为引用(使用ChunkAllocator.ConvertPointerToStructure获取数据,使用ChunkAllocator.StoreStructure存储更改的数据)。这显然很麻烦,所以如果使用我的方法,您最好真正想要性能。但是,如果您只处理值类型,那么这种方法就足够了
迂回:CLR中的类
类在其分配的内存中有一个8字节的“前缀”。四个字节用于多线程的同步索引,四个字节用于标识其类型(基本上是虚拟方法表和运行时反射)。这使得处理未管理的内存变得很困难,因为这些内存是CLR特定的,并且同步索引在运行时可能会更改。有关运行时对象创建的详细信息以及引用类型的内存布局概述,请参阅。还可以查看更深入的解释
警告
通常情况下,事情很少像yes/no这样简单。引用类型的真正复杂性与垃圾收集器在垃圾收集期间如何压缩分配的内存有关。如果您能够以某种方式确保垃圾收集不会发生或不会影响相关数据(请参阅),那么您可以将任意指针转换为对象引用(只需将指针偏移8个字节,然后将该数据解释为具有相同字段和内存布局的结构;可能需要使用)。我会尝试非虚拟方法,看看它们是否有效;它们应该(特别是如果你把它们放在结构上的话)但是虚拟方法是不行的,因为你必须放弃虚拟方法表
一个人不只是走进魔多
简单地说,这意味着不能在非托管内存中分配托管引用类型(类)。您可以在C++中使用托管引用类型,但那些将受垃圾收集的影响。与基于struct
的方法相比,过程和代码更痛苦。那我们该怎么办?当然,回到我们开始的地方
有一条秘密的路
我们可以自己勇敢地面对Shelob的巢穴。不幸的是,这是我们的道路必须分开的地方,因为我对这一点不太了解。我将为您提供一个或-也许或实际上。这相当复杂,并且回避了一个问题:您是否可以尝试其他优化?缓存一致性和高级算法是一种方法,对于性能关键的代码,明智地应用P/Invoke也是一种方法。您还可以仅为关键方法/类应用上述结构的内存分配
祝你好运,如果你找到更好的选择,请告诉我们
附录:源代码
ChunkAllocator.cs
使用系统;
使用System.Collections.Generic;
使用System.Linq;
使用系统文本;
使用System.Runtime.InteropServices;
名称空间MemAllocLib
{
公共密封类ChunkAllocator:IDisposable
{
IntPtr m_chunkStart;
int m_offset;//已分配内存的偏移量
只读int m_大小;
公共ChunkAllocator(int memorySize=1024)
{
if(memorySize<1)
通过
float Position {
get {return Creatures[_index].Position;}
set {Creatures[_index].Position = value;}
};
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;
}
}
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;
}
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);
}
}