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

时间:2012-11-08 22:41:38

标签: c# pointers unsafe

我已经做了相当多的研究,但现在我仍然坚持为什么我仍然会收到这个错误。我有一个具有以下属性的结构:

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中的属性使用“unsafe”,并告诉编译器它可以使用不安全的代码(在属性 - > Build中),我仍然会收到此错误

*mAccounts

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

5 个答案:

答案 0 :(得分:27)

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

因此,编译器可以预先告知垃圾收集器永远不会能够看到字符串引用。因为唯一的方法是通过mAccount字段而不是参考。

唯一可以解决的问题是严格限制自己的价值类型。对字符串执行此操作的唯一方法是使用Marshal.StringToCoTaskMemUni()将它们分配到非托管内存中,并将IntPtr存储在字段中。它现在从垃圾收集器无法触及,无法被它移动。你现在也有释放那个字符串的负担。

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

答案 1 :(得分:3)

字符串是.NET中的引用类型,对于struct指针是非blittable的。有关您要执行的操作的值类型列表,请参阅Blittable and Non-Blittable Types

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

答案 2 :(得分:1)

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

答案 3 :(得分:0)

包含可以包含指针的类型的struct错误,因为string是托管类型,不能有指针引用。

答案 4 :(得分:0)

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

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

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

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

这里我们在非托管内存中分配了struct。这允许我们保持指向它的指针,我们知道它不会改变或移动。完成后我们必须手动释放结构。对于mAccounts-> mName,需要进行相同的手动alloc / free和marshalling,因为它是非托管char *(c样式字符串)的方式。

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