2017-01-26 4 views
0

私は型の配列をMarshal.StructureToPtrMarshal.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にキャストして、それが変わるかどうかを確認intCharacterメンバーを変更することになりそうです結果はユーザーのマシン上に表示されます。これは、正確にエラーの原因となっているものをJohnが示唆しているように、別のMarshal呼び出しを例外ハンドラにラップする良い機会にもなります。

これはかなり低い優先順位の機能なので、引き続き発生しても安全に失敗させることができます。

報告する皆さんありがとう。

+0

'CharBox'は、SerializeArray()関数が呼び出されているのと同じDLLまたはEXEにありますか?そうでなければ、私は思うに '内部 'があなたの問題です。 – John

+0

同じモジュール内にあります。 –

+0

OK、次のステップは、どのシステムコールがその例外をスローしているのかを特定することです。私たちがマーシャルを使うとき、私たちは宗教的に各呼び出しの周りにtry..catchステートメントを使います。それから、次にどこを見るかが分かります。この一般的なエラーには、さまざまな理由が考えられます。 – John

答えて

0

私はあなたの既存の方法論に明白な誤りは見られないので、私はその前に何も提供していません。しかし、あなたが述べているので:

を私はあなたの検討のためにこれを投げるしたいシリアライズ

を行うための代替または改良された方法を歓迎します。 MemoryMappedViewAccessorを使用して、構造体の配列からバイト配列への変換を実行します。もちろんこれにはMemoryMappedFileを作成する必要があります。

internal static byte[] SerializeArray<T>(T[] array) where T : struct 
    { 
    int unmananagedSize = Marshal.SizeOf(typeof(T)); 

    int numBytes = array.Length * unmananagedSize; 
    byte[] bytes = new byte[numBytes]; 

    using (MemoryMappedFile mmf = MemoryMappedFile.CreateNew("fred", bytes.Length)) 
     { 
     using (MemoryMappedViewAccessor accessor = mmf.CreateViewAccessor(0, bytes.Length, MemoryMappedFileAccess.ReadWrite)) 
      { 

      accessor.WriteArray<T>(0, array, 0, array.Length); 
      accessor.ReadArray<byte>(0, bytes, 0, bytes.Length); 

      } 
     } 

    return bytes; 
    } 

internal static T[] DeSerializeArray<T>(byte[] bytes) where T : struct 
    { 
    int unmananagedSize = Marshal.SizeOf(typeof(T)); 

    int numItems = bytes.Length/unmananagedSize; 
    T[] newArray = new T[numItems]; 

    using (MemoryMappedFile mmf = MemoryMappedFile.CreateNew("fred", bytes.Length)) 
     { 
     using (MemoryMappedViewAccessor accessor = mmf.CreateViewAccessor(0, bytes.Length, MemoryMappedFileAccess.ReadWrite)) 
      { 

      accessor.WriteArray<byte>(0, bytes, 0, bytes.Length); 
      accessor.ReadArray<T>(0, newArray, 0, newArray.Length); 

      } 
     } 
    return newArray; 
    } 

ご利用状況に応じて、メモリマップトファイルのために(私は「フレッド」を使用)ユニークな名前のためのメカニズムを提供する必要があるかもしれません。

+0

それは独創性のためにアップヴォートを得ることができます!私はそれを試してみる、ありがとう。それでも私がここで間違っていることを(あるいはフレームワークにバグがあるかどうか)確かめることを望んでいます。 –

+0

これは共有メモリセグメントです!残酷な......そして好奇心が強い/パフォーマンスについては疑わしい。 – John

+0

@john、私はWin 10/64でいくつかのテストを実行しました。 32ビットアセンブリとしてコンパイルされたMMF技法では、3.32ミリ秒対1.75ミリ秒のOPバージョンと10000個のアイテムを処理するのに約2倍の時間がかかりました。 64ビットアセンブリとしてコンパイルされたとき、MMF技法は、元のバージョン1.48msと1.56msとの間で非常にわずかに外れた。 Char fikledがblittable整数にマップされるプロパティに変更された場合、OPメソッドは以前の半分の時間を要し、MMF時間は同じままです。 – TnTinMn

0

私はまだ効果がある解決策を見つけましたが、私はまだなぜそれがわかりません。

ここは私が変更したものです。 CharBoxは今です:

[StructLayout(LayoutKind.Explicit, CharSet = CharSet.Unicode)] 
public struct CharBox 
{ 
    [FieldOffset(0)] 
    internal int Character; 

    [FieldOffset(4)] 
    internal float Left; 

    [FieldOffset(8)] 
    internal float Top; 

    [FieldOffset(12)] 
    internal float Right; 

    [FieldOffset(16)] 
    internal float Bottom; 

    // Assists with error reporting 
    public override string ToString() 
    { 
     return $"CharBox (Character = {this.Character}, Left = {this.Left}, Top = {this.Top}, Right = {this.Right}, Bottom = {this.Bottom})"; 
    } 
} 

そして、実際の方法は以下のようになります。

internal static byte[] SerializeArray<T>(T[] array) where T : struct 
    { 
     if (array.IsNullOrEmpty()) 
      return null;    

     int position = 0; 
     int structSize = Marshal.SizeOf(typeof(T)); 

     if (structSize < 1) 
     { 
      throw new Exception($"SerializeArray: invalid structSize ({structSize})"); 
     } 

     byte[] rawData = new byte[structSize * array.Length]; 
     IntPtr buffer = IntPtr.Zero; 

     try 
     { 
      buffer = Marshal.AllocHGlobal(structSize); 
     } 
     catch (Exception ex) 
     { 
      throw new Exception($"SerializeArray: Marshal.AllocHGlobal(structSize={structSize}) failed. Message: {ex.Message}"); 
     } 

     try 
     { 
      int i = 0; 
      int total = array.Length; 
      foreach (T item in array) 
      { 
       try 
       { 
        Marshal.StructureToPtr(item, buffer, false); 
       } 
       catch (Exception ex) 
       { 
        throw new Exception($"SerializeArray: Marshal.StructureToPtr failed. item={item.ToString()}, index={i}/{total}. Message: {ex.Message}"); 
       } 

       try 
       { 
        Marshal.Copy(buffer, rawData, position, structSize); 
       } 
       catch (Exception ex) 
       { 
        throw new Exception($"SerializeArray: Marshal.Copy failed. item={item.ToString()}, index={i}/{total}. Message: {ex.Message}"); 
       } 

       i++; 
       position += structSize; 
      } 
     } 
     catch 
     { 
      throw; 
     } 
     finally 
     { 
      try 
      { 
       Marshal.FreeHGlobal(buffer); 
      } 
      catch (Exception ex) 
      { 
       throw new Exception($"Marshal.FreeHGlobal failed (buffer={buffer}. Message: {ex.Message}"); 
      } 
     } 

     return rawData; 
    } 

私はエラーの詳細を取得するために期待していたが、代わりに、ユーザーはそれが警告なしに働いていたことを報告しました。

受賞者だった一つ以上のその実質的な変化は、あったようSerializeArrayに対するすべての変更は、単により詳細な報告のためにあった。

(私が使用しているだろう intcharを変更
  • がありましたが、このstructは他の場所で使用され、以前は4バイトのパディングを使用していたため、既存のデータとの互換性を維持したいと考えていました。

  • LayoutKind.Explicitstructレイアウトを設定し、明示的にFieldOffsetを設定します。私の推測ではExplicitにレイアウトやUnicodeにCharSetを設定することが持っているだろうということですstruct

にもうchar年代があるので、確かに、おそらく何もしなかった - と

  • StructLayoutCharSet.Unicodeを指定しますCharacterをもう一度charにするのに十分であったが、実際に働いているので、試行錯誤して顧客の時間を無駄にしてはいけない。うまくいけば、他の誰かが何が起こったかについて意見を述べることができますが、おそらくMSDNにこれを投稿して、CLR神の1つがある程度の洞察力を持っていることを期待しています。

    ありがとうございます。charの問題を強調表示し、これらを変更しようとしている間違いがありましたので、特にTnTnManありがとうございます。

  • +0

    今後の参考として、あなたの構造にblittable型しか含まれていないので、 'gchnd = GCHandle.Alloc(array、GCHandleType.Pinned)'を使ってGCHandleインスタンスを得ることができます。次に、バイト配列をロードするために単一の 'Marshal.Copy(gchnd.AddrOfPinnedObject、rawData、0、rawData.Length)'を実行できます。各項目をコピーする必要はありません。同じテクニックを使用して、配列をバイト配列から再作成することができます。 – TnTinMn

    +0

    ああ、あなたに感謝しています。おそらくそれがすべての最良のアプローチであるため、答えとして投稿することが自由であれば。 –

    関連する問題