私は型の配列をMarshal.StructureToPtr
とMarshal.Copy
を使ってbyte
の配列にシリアライズする一般的な方法を持っています。完全なコードは:構造体の配列をbyte []にシリアライズする - コードで何が間違っていますか?
internal static byte[] SerializeArray<T>(T[] array) where T : struct
{
if (array == null)
return null;
if (array.Length == 0)
return null;
int position = 0;
int structSize = Marshal.SizeOf(typeof(T));
byte[] rawData = new byte[structSize * array.Length];
IntPtr buffer = Marshal.AllocHGlobal(structSize);
foreach (T item in array)
{
Marshal.StructureToPtr(item, buffer, false);
Marshal.Copy(buffer, rawData, position, structSize);
position += structSize;
}
Marshal.FreeHGlobal(buffer);
return rawData;
}
完全に99.99%の時間で動作します。しかし、私のWindows 7ユーザーのうちの特定の入力データの場合、このコードは次のような非.NET例外を発生させます。
システムコールに渡されるデータ領域が小さすぎます。 ( HRESULTからの例外:0x8007007A)。
残念ながら、私は、デバッガをアタッチするために、ユーザーのマシンへのアクセスを持っていない、と私は私のユーザーとまったく同じ入力データを扱う場合でも、問題を再現することができていません。これは1人のユーザのマシンでのみ発生し、特定の入力データでのみ発生しますが、マシン上では同じ入力データで毎回発生するため、ランダムではありません。
アプリケーションは.NET 4.5をターゲットとしています。
誰でもこのコードで何か問題が見えることはありますか?私の唯一の推測では、Marshal.SizeOf
が報告しているデータ構造と実際のデータ構造のサイズとの間に何らかの不一致があり、構造体に割り当てられているメモリが不十分であるということです。それが重要な場合は
が、ここでエラーが発生したときに連載されている構造である(それはOCRの結果の文字位置の表現です):あなたはすべてのフィールドが一定の大きさでなければなりません見ることができるように
public struct CharBox
{
internal char Character;
internal float Left;
internal float Top;
internal float Right;
internal float Bottom;
}
すべて時間がかかるので、それぞれのシリアル化する管理されていないメモリの単一の固定長セグメントの私の最初の割り当てstruct
は問題ではありません(すべきでしょうか?)。
シリアライズの代替方法や改良された方法を歓迎しますが、私はこの特定のバグを掘り下げることにもっと興味があります。ありがとう!
更新 TnTnMnのはchar
はblittable型タイプではないことを私に指摘する おかげで、私は彼らが正しくマーシャリングされたかどうかを確認するために、入力にUnicode文字を探しました。彼らはそうではありません。 (16進数)CharBox { 0x2022, .15782328, .266239136, .164901689, .271627158 },
シリアル化
あるべき:
22 20 00 00(文字*)
6D 9C 21 3E(左)
7F 50 88 3E(
FD)トップDB 28 3E(右)
B7 12 8B 3E(下)
(*私は明示的なレイアウトを使用していなかったので、4バイトにパディングしました。私は今、不必要にデータサイズを11%増やすことで、自分自身に不満を抱いています...)
その代わりに、それはのようにシリアライズされる:
95 00 00 00(キャラクター)
6D 9C 21 3E(左)
7F 50 88 3E(トップ)
FD DB28 3E(右)
B7 12 8B 3E(下)
したがって、char
0x2022を0x95としてマーシャリングしています。それが起こると、0x2022 Unicodeと0x95 ANSIが両方とも弾丸文字です。したがってこれはランダムではなく、むしろすべてをANSIにマーシャリングしています。これは、CharSet
を指定しないと標準的な手順です。
これは、意図しない動作が発生していることを確認し、さらに、どのような条件(つまり、構造体内のUnicode文字)がエラーにつながる可能性があるという良い動作理論を示しています。
説明していないのは、これが例外を発生させる理由と、それがなぜこの1人のユーザー以外のどのマシンでも発生しない理由です。前者に関しては、unciodeとANSIとのサイズの相違は、エラーメッセージ( "システムコールに渡されるデータ領域が小さすぎます")と一貫性があると思いますが、アンマネージバッファchar
の4つのフル・バイトを収容するためのサイズであり、必要以上に小さく、小さくはない。 CLRやOSが2のために意図されたエリアに1バイトしか書き込まれず、4のために十分に大きく書かれてしまうのはなぜでしょうか?
後者に関しては、おそらくユーザーが他の人よりも.NETの下位バージョンにいる可能性があります。これは、Windows 7の更新プログラムがすべてダウンロードされていない可能性があります。しかし、私はちょうど新しいWindows 7のインストールと.NET 4.5(アプリケーションがサポートする最も低いバージョン)でVMでそれを試して、まだエラーを再現できません。私はそれが4.5.1か何かの場合に、彼女が持っている.NETバージョンを正確に見つけようとしています。それでも、これは長い間のようだ。
確かに知っている唯一の方法は、(既存のデータに同じパディングを維持するために)と、必要な場合にのみchar
にキャストして、それが変わるかどうかを確認int
にCharacter
メンバーを変更することになりそうです結果はユーザーのマシン上に表示されます。これは、正確にエラーの原因となっているものをJohnが示唆しているように、別のMarshal
呼び出しを例外ハンドラにラップする良い機会にもなります。
これはかなり低い優先順位の機能なので、引き続き発生しても安全に失敗させることができます。
報告する皆さんありがとう。
'CharBox'は、SerializeArray()関数が呼び出されているのと同じDLLまたはEXEにありますか?そうでなければ、私は思うに '内部 'があなたの問題です。 – John
同じモジュール内にあります。 –
OK、次のステップは、どのシステムコールがその例外をスローしているのかを特定することです。私たちがマーシャルを使うとき、私たちは宗教的に各呼び出しの周りにtry..catchステートメントを使います。それから、次にどこを見るかが分かります。この一般的なエラーには、さまざまな理由が考えられます。 – John