2

P/Invoke 呼び出しを介して小さな zlib ラッパーを作成しています。64 ビット ターゲット (64 ビット C# ビルド、64 ビット DLL) では完全に動作しますが、32 ビット ターゲット (32 ビット C# ビルド、32 ビット DLL) では AccessViolationException がスローされます。

例外をスローする C# シグネチャとコードは次のとおりです。

[DllImport(Program.UnmanagedDll, CallingConvention = CallingConvention.Cdecl)]
private static extern ZLibResult ZLibDecompress(byte[] inStream, uint inLength, byte[] outStream, ref uint outLength);

internal enum ZLibResult : byte {
        Success = 0,
        Failure = 1,
        InvalidLevel = 2,
        InputTooShort = 3
}

internal static ZLibResult Decompress(byte[] compressed, out byte[] data, uint dataLength) {
    var len = (uint) compressed.Length;
    fixed (byte* c = compressed) {
        var buffer = new byte[dataLength];
        ZLibResult result;
        fixed (byte* b = buffer) {
            result = ZLibDecompress(c, len, b, &dataLength);
        }
        if(result == ZLibResult.Success) {
            data = buffer;
            return result;
        }
        data = null;
        return result;
    }
}

そして、これが C コードです (MinGW-w64 でコンパイル):

#include <stdint.h>
#include "zlib.h"

#define ZLibCompressSuccess         0
#define ZLibCompressFailure         1

__cdecl __declspec(dllexport) uint8_t ZLibDecompress(uint8_t* inStream, uint32_t inLength,
                                                     uint8_t* outStream, uint32_t* outLength)
{
    uLongf oL = (uLongf)*outLength;
    int result = uncompress(outStream, &oL, inStream, inLength);
    *outLength = (uint32_t)oL;
    if(result == Z_OK)
        return ZLibCompressSuccess;
    return ZLibCompressFailure;
}

すべてを調べましたが、64 ビット ビルドではなく 32 ビット ビルドでアクセス違反が発生する理由がわかりません。ZLibDecompress は、C アプリから呼び出された場合は同じストリームを正常に解凍しますが、C# アプリから呼び出された場合はアクセス違反をスローします。

なぜこれが起こっているのか誰にも分かりますか?

編集: コードを更新しましたが、32 ビット ビルドでは引き続きアクセス違反が発生しますが、64 ビットでは発生しません。

C# コード:

[DllImport(Program.UnmanagedDll, CallingConvention = CallingConvention.Cdecl)]
private static extern ZLibResult ZLibDecompress(
    [MarshalAs(UnmanagedType.LPArray)]byte[] inStream, uint inLength,
    [MarshalAs(UnmanagedType.LPArray)]byte[] outStream, ref uint outLength);

internal static ZLibResult Decompress(byte[] compressed, out byte[] data, uint dataLength) {
    var buffer = new byte[dataLength];
    var result = ZLibDecompress(compressed, (uint)compressed.Length, buffer, ref dataLength);
    if(result == ZLibResult.Success) {
        data = buffer;
        return result;
    }
    data = null;
    return result;
}

C コード:

__declspec(dllexport) uint8_t __cdecl ZLibDecompress(uint8_t* inStream, uint32_t inLength,
                                 uint8_t* outStream, uint32_t* outLength) {
    uLongf oL = (uLongf)*outLength;
    int result = uncompress(outStream, &oL, inStream, inLength);
    *outLength = (uint32_t)oL;
    if(result == Z_OK)
        return ZLibCompressSuccess;
    return ZLibCompressFailure;
}
4

3 に答える 3

4
    fixed (byte* b = buffer) {
        result = ZLibDecompress(c, len, b, &dataLength);
    }

いいえ、それはうまくいきません。fixedキーワードは、オブジェクトを移動するガベージ コレクターが問題を引き起こさないようにする高度に最適化された方法を提供します。(ドキュメントにあるように) オブジェクトを固定するのではなく、b変数をガベージ コレクターに公開することで行います。次に、バッファを参照していることを確認し、b移動時の値を更新しますbuffer

ただし、この場合は機能しません。値のコピーbZlibDecompress() に渡されました。ガベージ コレクターはそのコピーを更新できません。ZLibDecompress() の実行中に GC が発生すると、結果は悪くなります。ネイティブ コードはガベージ コレクション ヒープの整合性を破壊し、最終的に AV を引き起こします。

fixedを使用することはできません。GCHandle.Alloc() を使用してバッファーを固定する必要があります。

しかし、それもしないでください。あなたは助けすぎています。pinvoke マーシャラーは、必要に応じてオブジェクトをピン留めする機能を既に備えています。instreamおよびoutstream引数を byte* ではなく byte[] として宣言します。そして、特別なことをせずに配列を直接渡します。また、outlength引数を宣言する必要がありますref int

于 2012-06-30T09:42:10.667 に答える
1

64 ビットでは、Windows 用の ABI が 1 つしかない (cdecl/stdcall がない) ため、32 ビットの問題は呼び出し規約にあるようです。パラメーター ポインターが間違ったレジスタに移動し、ネイティブ関数が間違ったメモリ領域にアクセスしています。

この問題を解決するには:

  1. ネイティブ関数の行をコメントアウトしてみてください(クラッシュするかどうかを確認してください-はい、呼び出し規約ではありません)

  2. 呼び出し規約「cdecl/stdcall」で遊んでみてください

  3. すべてを確認するには、ポインター値をダンプして、ネイティブ/マネージド関数で一致するかどうかを確認してください。

編集:

次に、ポインタの問題です。C# で配列を割り当てています (したがって、配列はマネージド ヒープに存在します)。「[MarshalAs(UnmanagedType.LPArray)]」属性を使用してマーシャリングする必要があります。

[DllImport(Program.UnmanagedDll, CallingConvention = CallingConvention.Cdecl)]
private static extern ZLibResult ZLibDecompress(
     [MarshalAs(UnmanagedType.LPArray)] byte[] inStream,
     uint inLength,
     [MarshalAs(UnmanagedType.LPArray)] byte[] outStream,
     ref UInt32 outLength);

[In,Out] 修飾子も役立つ場合があります。

そして、はい、Hans が言うように、ポインターを固定し、それらがガベージ コレクションされるのを許可しません。

byte[] theStream = new byte[whateveyouneed];
// Pin down the byte array
GCHandle handle = GCHandle.Alloc(theStream, GCHandleType.Pinned); 
IntPtr address = handle.AddrOfPinnedObject();

それを IntPtr として渡します。

于 2012-06-30T08:47:46.460 に答える
0

実際の問題は、バグのある DLL を生成する MinGW-w64 が原因でした。zlib をビルドするときに -ftree-vectorize を gcc に渡していましたが、これは 32 ビット CLR が気に入らないコードを生成していました。あまり積極的でない最適化オプションを使用した後、コードは正常に実行されました。

于 2012-07-07T11:40:25.223 に答える