私はかなりの調査を行いましたが、なぜまだこのエラーが発生するのかについて今は行き詰まっています。次の属性を持つ構造体があります。
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の属性に「unsafe」を使用し、コンパイラに安全でないコードを使用できることを伝えた後でも([プロパティ]-> [ビルド])、このエラーが発生します。
*mAccounts
理由について何かアイデアはありますか?構造体で使用しているすべての型は、c#でのポインターを持つことが合法であると確信しています。前もって感謝します!
Accountクラスの文字列がこの問題の原因です。その理由を理解するには、ガベージコレクタがどのように機能するかを理解する必要があります。オブジェクトへの参照を追跡することでゴミを発見します。 mNameとmDateCreatedはそのような参照です。 mBalanceとmAccountNumberはnotであり、これらのフィールドは値型です。そして、最も重要なことは、BankManager.mAccountsフィールドはそうではなく、ポインターです。
したがって、コンパイラは、ガベージコレクタが文字列参照を表示できるようになることを事前に通知できます決して。これを行う唯一の方法は、mAccountフィールドを調べることであり、参照ではないためです。
これに対する唯一の解決策は、値型に厳密に制限することです。文字列に対してこれを行う唯一の方法は、たとえばMarshal.StringToCoTaskMemUni()を使用して文字列をunmanagedメモリに割り当て、IntPtrをフィールドに格納することです。現在、ガベージコレクターからは手の届かないところにあり、移動することはできません。これで、その文字列を解放する負担も発生します。
明らかに、これは実用的ではなく、リークを引き起こす傾向があります。これは、Cプログラムで非常に一般的な問題です。なぜこれを追求しているのかはわかりませんが、オブジェクトへの参照はすでに単純なポインターであるため、自分でポインターを使用しても何も得られないことに注意してください。
文字列は.NETの参照型であり、構造体ポインタに対しては分割できません。実行したい値タイプのリストについては、 BlittableおよびNon-Blittableタイプ を参照してください。
特別なビジネス要件がない限り、保守性と一般的な健全性のためにマネージメモリを使用する必要があります。
使用する private unsafe fixed char mName[126];
文字列はマネージタイプであり、固定されていない配列も同様です。
string
はポインタ参照を持つことができないマネージ型であるため、ポインタを持つことができる型を含む構造体については間違っています。
コピーコレクターは物事を移動できるため、管理対象データは固定された場所にとどまりません。これは、マネージボックス化された値型にも同様に当てはまります。管理されたボックス化されていない値型は、スタックまたは他のオブジェクト内にのみ存在できます。スタック内にある場合にのみ、固定された場所があります。
有効であり続けるポインタを取得できる固定位置を持つヒープ割り当て構造体を作成するには、それをnmanagedメモリに割り当てる必要があります。 ただし、アンマネージメモリに割り当てると、ガベージコレクタがそれらのポインタを認識しないため、マネージポインタをその中に配置できなくなります(つまり、文字列を使用できません)。圧縮中に管理対象オブジェクトを移動しても、それらは更新されません。
たとえば、これは有効な(必ずしも良いとは限りませんが)ことです。
[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;
}
}
}
ここでは、アンマネージメモリに構造体を割り当てました。これにより、変更または移動しないことがわかっているポインタを保持できます。構造体が使い終わったら、手動で構造体を解放する必要があります。管理されていないchar *(cスタイルの文字列)であるため、mAccounts-> mNameに対して同じ手動のalloc/freeおよびmarshallingを実行する必要があります。
上記のようなコードは通常、特定の構造体レイアウトを期待するネイティブC DllImportエントリポイントとの相互運用を行う場合にのみ使用されるため、構造体にシーケンシャルレイアウトをパックさせて、このコードの動作をCカウンターパートに近づけました。