маршал без знака char *, возвращающий функцию из dll, в C # - PullRequest
5 голосов
/ 19 января 2012

У меня есть следующий заголовок функции в нативной DLL:

unsigned char* Version_String()

Я пытаюсь вызвать его из проекта C #, я попробовал следующий вызов (как было найдено по другим подобным вопросам здесь):

[DllImport("BSL430.dll", CharSet=CharSet.Ansi)]
public extern static UIntPtr Version_String();

И я продолжаю получать следующее исключение:

Попытка чтения или записи в защищенную память. Это часто указывает на то, что другая память повреждена.

Следующая попытка была следующей, и я получил то же исключение:

[DllImport("BSL430.dll", CharSet=CharSet.Ansi)]
[return : MarshalAs(UnmanagedType.LPStr)]
public extern static string Version_String();

Я не могу обойти эту проблему. Любая помощь будет принята с благодарностью!

Edit:

Я не могу дать здесь код DLL, так как он подпадает под NDA, но функция, которую я вызываю, выглядит так:

unsigned char versionString[50];

__declspec(dllexport) unsigned char* Version_String()
{
    if(check_hardware_stuff())
    {
      strcpy((char *) versionString, "version_string_bla_bla");
      versionString[5] = stuff;
    }
    else if (other_check())
    {
      //will return empty string, that should be filled with '\0'
    }
    else
    {
      strcpy( (char *) versionString, "ERROR" );
    }
  return versionString;
}

Мне не особо нравится реализация DLL, но мне нужно использовать ее "как есть". Я получаю исключение, когда пытаюсь вызвать VersionString(), независимо от того, что я делаю с возвращаемым значением.

1 Ответ

8 голосов
/ 19 января 2012

Обновление

После просмотра обновленного вопроса, различных комментариев и кода вашей нативной функции, вероятно, возникает исключение при вызове check_hardware_stuff().Это достаточно просто для отладки.Я бы заменил вашу функцию на такую:

unsigned char versionString [50];

__declspec(dllexport) unsigned char* Version_String()
{
    strcpy(versionString, "testing");
    return versionString;
}

Если это не помогло, то я предполагаю, что ошибка возникает в DllMainвашей DLL.Отладьте это, поместив вышеуказанную функцию в простую ванильную DLL, которая больше ничего не делает.

Оригинальный ответ

Соглашение о вызовах является наиболее очевидной проблемой.Ваш нативный код, скорее всего, использует cdecl, но по умолчанию p / invoke - stdcall.Измените свою подпись p / invoke, чтобы она была такой:

[DllImport("BSL430.dll", CallingConvention=CallingConvention.Cdecl)]
public extern static IntPtr Version_String();

Вы можете спокойно пропустить параметр CharSet, поскольку ни один из параметров не имеет текста, поскольку вы возвращаете возвращаемое значение как указатель.

Редактировать: Ганс правильно указывает в комментариях, что, поскольку нет параметров, несоответствие соглашения о вызовах не имеет значения.Так что это не проблема.

Вызовите Marshal.PtrToStringAnsi для преобразования в строку .net.

string version = Marshal.PtrToStringAnsi(Version_String());

Поскольку PtrToStringAnsi ожидает IntPtr параметр Я бы порекомендовал вам использовать IntPtr в качестве типа возврата вашей подписи p / invoke.

Все это предполагает, что память, возвращаемая из вашей нативной функции, выделяется и освобождается в нативной DLL.Если он выделен в куче, и вы ожидаете, что вызывающая сторона освободит его, то у вас небольшая проблема.Как вы освобождаете память из C #, поскольку у вас нет доступа к куче встроенной библиотеки DLL?

Простое решение состоит в использовании общей кучи COM для выделения памяти.Вызовите CoTaskMemAlloc, чтобы выделить буфер для строки.Затем объявите возвращаемое значение типа string, и маршаллер p / invoke освободит выделитель COM.

[DllImport("BSL430.dll", CallingConvention=CallingConvention.Cdecl)]
public extern static string Version_String();
...
string version = Version_String();

Конечно, это применимо только в том случае, если вы возвращаете выделенную память кучи, которую ожидаетевызывающая сторона для освобождения.

...