2012-06-30 2 views
2

Я пишу небольшую обертку zlib через вызовы P/Invoke. Он отлично работает на 64-битной цели (64-разрядная C#-сборка, 64-разрядная DLL), но бросает AccessViolationException на 32-разрядную цель (32-разрядную C# -струю, 32-разрядную DLL).AccessViolationException в вызове P/Invoke

Вот 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; 
} 

Я просмотрел все, и может Невозможно понять, почему нарушение доступа будет происходить на 32-битной сборке, а не на 64-битной сборке. ZLibDecompress отлично работает при распаковке одного и того же потока при вызове из приложения C, но при вызове из моего приложения C# выдает нарушение доступа.

Кто-нибудь знает, почему это может произойти?

EDIT: Обновлен мой код, все еще получая нарушение прав доступа на 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; 
} 
+0

Вы используете 32-битный MinGW или пытаетесь перекрестно скомпилировать? Вы пытались использовать свою библиотеку из C-кода? – Mario

+0

Я строю 32-разрядную DLL, используя MinGW-w64, и да, функции работают отлично без кучи при вызове в программе C. –

+0

Невозможно увидеть проблему прямо сейчас. Вы пробовали использовать 32-битный MinGW? Хотя я не ожидал никакой разницы. Вам не хватает ключевого слова 'unsafe', но кроме этого ... вы действительно можете пропустить весь материал указателя и просто передать массивы, которые, как я думаю (что делает« небезопасным »устаревшим), могут исключить еще один возможный источник ошибок. – Mario

ответ

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

Нет, это не сработает.Правильное ключевое слово обеспечивает оптимизированный способ обеспечения того, чтобы движущиеся объекты сборщика мусора не вызывали проблем. Он не делает этого, привязывая объект (как говорит документация), он делает это, подвергая переменную b сборщику мусора. Затем он видит, что он ссылается на буфер и обновляет значение b, когда он перемещается buffer.

Это, однако, не может работать в этом случае, копия значения b была передана ZlibDecompress(). Сборщик мусора не может обновить эту копию. Результат будет неудовлетворительным, когда GC происходит, когда ZLibDecompress() запущен, собственный код уничтожит целостность собранной мусора, и это в конечном итоге приведет к AV.

Вы не можете использовать фиксированный, вы должны использовать GCHandle.Alloc(), чтобы вывести буфер.

Но не делайте этого, вы слишком много помогаете. Маршрутизатор pinvoke уже очень хорошо разбирает объекты, когда это необходимо. Объявите аргументы instream и outstream как byte [] вместо байта *. И передавайте массивы напрямую, не делая ничего особенного. Кроме того, аргумент outlength должен быть объявлен ref int.

+0

Спасибо за понимание, я запомню, что исправление не привязывает объект. Я попытался передать массивы напрямую и использовать GCHandle.Alloc(), но я все еще получаю 32-битное специфическое нарушение прав доступа. Опять же, нативная функция отлично работает при вызове из программы C, я очень смущен, почему это не работает сейчас. –

+0

Обратите внимание на последний абзац в сообщении. –

+0

У меня есть, посмотрите мой обновленный пример. –

1

В 64-битном есть только один ABI для Windows (не Cdecl/STDCALL), поэтому проблема для 32-битных, кажется, быть с вызывающими соглашениями. Указатели параметров переходят в неправильные регистры, а собственная функция обращается к неправильной области памяти.

Чтобы решить эту проблему:

  1. Попробуйте закомментировать строки в собственной функции (см, если он выходит из строя - это да, это не соглашение о вызовах)

  2. Попробуйте играть с вызовом соглашения «cdecl/stdcall»

  3. Чтобы проверить все, попробуйте сбросить значения указателя и посмотреть, совпадают ли они в собственных/управляемых функциях.

EDIT:

Тогда это проблема с указателями. Вы выделяете массивы в 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] может также помочь.

И да, как говорит Ганс, прикрепите указатели и не позволяйте им собирать мусор.

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

, а затем передать его как IntPtr.

+0

Спасибо, никогда не думал об использовании stdcall. В любом случае отключить управление именами экспортируемых функций __stdcall? Я знаю, что Windows API использует stdcall и не имеет искаженных имен функций. –

+0

Название mangling применимо только для кода C++. extern «C» до того, как имя функции отключит манипуляцию. Но я думаю, вы это знаете. –

+0

Не обращайте внимания на название mangling, исправил его, передав --kill-at на компоновщик MinGW. Я все еще получаю нарушение доступа с помощью stdcall или cdecl, указатели для inStream и outStream точно такие же, как в управляемом и собственном коде, и комментируя строку, которая вызывает uncompress() в коде C, останавливает сбой. Какие-нибудь дальнейшие идеи? –

0

Актуальная проблема была вызвана MinGW-w64, генерирующим багги DLL. Я пропустил -tree-vectorize для gcc при создании zlib, который генерировал код, который 32-битная CLR не понравилась. После использования менее агрессивных вариантов оптимизации код прошел нормально.

Смежные вопросы