12

我已经进行了相当多的研究,但现在我仍然不知道为什么我仍然会收到此错误。我有一个具有以下属性的结构:

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;
}

即使将我的类 Account 转换为结构,对 BankManager 类中的属性使用“不安全”,并告诉编译器它可以使用不安全代码(在属性 -> 构建中),我仍然在

*mAccounts

关于为什么的任何想法?我很确定我在结构中使用的所有类型在 c# 中都有指向的指针是合法的。提前致谢!

4

5 回答 5

27

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

所以编译器可以预先告诉垃圾收集器将永远无法看到字符串引用。因为这样做的唯一方法是通过 mAccount 字段而不是参考。

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

显然这不切实际并且容易导致泄漏,这种问题在 C 程序中很常见。不知道你为什么要追求这个,但请记住,对对象的引用已经是一个简单的指针,所以你自己使用指针不会获得任何东西。

于 2012-11-09T00:44:11.607 回答
3

字符串是 .NET 中的引用类型,对于结构指针来说是不可 blittable 的。请参阅Blittable 和 Non-Blittable 类型以获取您想要执行的值类型的列表。

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

于 2012-11-08T22:57:01.863 回答
1

使用private unsafe fixed char mName[126];
字符串是托管类型,非固定数组也是如此。

于 2012-11-08T22:52:48.853 回答
0

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

于 2012-11-08T22:50:51.577 回答
0

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

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

例如,这是一个有效的(尽管不一定是好的)事情:

[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 执行相同的手动分配/释放和编组,因为它是非托管 char*(c 样式字符串)的方式。

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

于 2013-08-21T18:03:37.207 回答