C# 无法获取托管类型的地址、大小或声明指向托管类型的指针

C# 无法获取托管类型的地址、大小或声明指向托管类型的指针,c#,pointers,unsafe,C#,Pointers,Unsafe,我已经做了一些研究,但我现在还不明白为什么我仍然会犯这个错误。我有一个具有以下属性的结构: struct Account { //private attributes private double mBalance; private int mAccountNumber; private string mName; private string mDateCreated; } 我正努力做到以下几点: class BankManager { //p

我已经做了一些研究,但我现在还不明白为什么我仍然会犯这个错误。我有一个具有以下属性的结构:

struct Account
{
    //private attributes
    private double mBalance;
    private int mAccountNumber;
    private string mName;
    private string mDateCreated;
}
我正努力做到以下几点:

class BankManager
{
    //private attributes
    private unsafe Account *mAccounts;
    private unsafe bool *mAccountsAvailable;
    private int mNumberAccounts;
}
即使将我的类帐户转换为结构,在类BankManager中使用“不安全”属性,并告诉编译器它可以使用不安全代码(在properties->Build中),我仍然会遇到这个错误

*mAccounts

你知道为什么吗?我很确定我在结构中使用的所有类型在c#中都有指向的指针是合法的。提前谢谢

对于包含可以有指针的类型的结构,您是错误的,因为
字符串是不能有指针引用的托管类型。

使用
私有不安全固定字符mName[126]

字符串是托管类型,非固定数组也是如此。

字符串是.NET中的引用类型,对于结构指针不可blittable。有关要执行的操作的值类型列表,请参见


除非您有特殊的业务需求,否则您应该坚持使用托管内存,以实现可维护性和一般的安全性。

Account类中的字符串会导致此问题。要了解原因,您需要了解垃圾收集器是如何工作的。它通过跟踪对对象的引用来发现垃圾。mName和mDateCreated就是这样的引用。mBalance和mAccountNumber不是,这些字段是值类型。最重要的是,BankManager.mAccounts字段不是,而是指针

因此,编译器可以预先告知垃圾收集器将永远无法看到字符串引用。因为这样做的唯一方法是遍历mAccount字段,而不是引用

唯一的解决方法是严格限制自己的值类型。对字符串执行此操作的唯一方法是使用Marshal.StringToCoTaskMemUni()在非托管内存中分配它们,并将IntPtr存储在字段中。垃圾收集器现在无法触及它,无法移动它。现在,您还需要释放该字符串


显然,这是不实际的,而且容易导致泄漏,这种问题在C程序中非常常见。不知道您为什么要这样做,但请记住,对对象的引用已经是一个简单的指针,因此您自己使用指针不会获得任何东西。

托管数据不会停留在固定位置,因为复制收集器可以移动对象。这同样适用于托管装箱值类型。托管未绑定值类型只能存在于堆栈上或其他对象内部。它们只有在堆栈中时才具有固定位置

为了创建一个堆分配结构,它有一个固定的位置,您可以从中获取一个将继续有效的指针,您必须在非托管内存中分配它。但是,一旦您在非托管内存中分配了它,您就不能再将托管指针放入其中(也就是说,您不能使用字符串),因为垃圾收集器不知道这些指针,所以在压缩期间移动托管对象时不会更新它们

例如,这是一件有效(但不一定是好事)的事情:

[StructLayout(LayoutKind.Sequential, Pack=1)]
public unsafe struct Account {
    public int a;
    public char* mName;
}
public class BankManager {
    private unsafe Account* mAccounts;
    public unsafe int GetA() {
        return mAccounts->a;
    }
    public unsafe BankManager() {
        mAccounts = (Account*)Marshal.AllocHGlobal(sizeof(Account));
    }
    unsafe ~BankManager() {
        if (mAccounts != null) {
             Marshal.FreeHGlobal((IntPtr)mAccounts);
             mAccounts = null;
        }
    }
}
这里我们在非托管内存中分配了结构。这允许我们持有一个指向它的指针,我们知道它不会改变或移动。我们必须在完成结构后手动释放它。需要对mAccounts->mName执行相同的手动alloc/free和marshalling,因为这是非托管char*(c样式字符串)的工作方式


我使结构具有打包的顺序布局,以使此代码的行为更接近其C对应的行为,因为像上面这样的代码通常仅在与需要特定结构布局的本机C DllImport入口点进行互操作时使用。

为什么要使用指针?看起来,
BankManager
将有一个
集合
帐户
s。这可能会有帮助:注意:对于任何想查看地址以确保了解引擎盖下发生的事情的人,请观看这两个视频,而不是为此绞尽脑汁。(视频1:youtube.com/watch?v=h6aXzd1nTXQ)(视频2:youtube.com/watch?v=mvieNUe9Urs)。这给了我想知道的一切。我来自C++背景,当我不能用表窗口随意查看内存地址时,我感到非常沮丧。在讨论内存时,直观地了解背景中发生的事情是非常有用的。好吧,我知道字符串是可以管理的,但是字符是允许的。因此,当我执行上述操作时,我得到:public char Name{get{return mName;}set{mName=value;}}},错误为“您不能使用非固定表达式中包含的固定大小缓冲区。请尝试使用固定语句。”感谢提供详细信息!这个答案比公认的答案更有益于我,因为它描述了更大的设计问题。我的特殊问题与包含int[]数组属性的结构有关,该属性不可Blitttable,并导致与OP相同的错误。