Доступ к Delphi DLL вызывает исключение иногда
Когда я вызываю метод Dll, иногда выдается исключение, а иногда нет.
Я называю это так:
public class DllTest
{
[DllImport(@"MyDll.dll")]
public extern static string MyMethod(string someStringParam);
}
class Program
{
static void Main(string[] args)
{
DllTest.MyMethod("SomeString");
}
}
И иногда я получаю исключение:
AccessViolationException
Attempted to read or write protected memory. This is often an indication that other memory is corrupt.
Кто-нибудь знает, почему я иногда получаю это исключение? Почему иногда это проходит гладко?
2 ответа
У вас явно есть несоответствие между кодом p/invoke и кодом Delphi. Вы не показали код Delphi, но кода C# достаточно, чтобы знать, как должен выглядеть код Delphi.
Ваш DllImport
Атрибут использует значения по умолчанию для соглашения о вызовах и набора символов. Это означает, что соглашение о вызовах stdcall
и набор символов ANSI. Вы не указали никаких атрибутов маршаллинга, поэтому необходимо использовать маршаллинг по умолчанию.
Поэтому ваш код Delphi должен выглядеть так:
function MyMethod(someStringParam: PChar): PChar; stdcall;
begin
Result := ??;
end;
А теперь вот проблема. Маршаллер p/invoke обрабатывает string
возвращать значение очень особым образом. Предполагается, что маршаллер p/invoke отвечает за освобождение памяти возвращаемого значения. И он должен использовать тот же распределитель, что и нативный код. Предположение, сделанное маршаллером, заключается в том, что будет использоваться общий распределитель COM.
Таким образом, правило состоит в том, что собственный код должен распределять память с помощью COM-распределителя, вызывая CoTaskMemAlloc
, Держу пари, что ваш код этого не делает, и это, безусловно, приведет к ошибкам.
Вот пример того, как вы могли бы создать встроенную функцию Delphi, которая работает с сигнатурой C# в вашем коде.
function MyMethod(someStringParam: PChar): PChar; stdcall;
var
Size: Integer;
begin
Size := SizeOf(Char)*(StrLen(someStringParam)+1);//+1 for zero-terminator
Result := CoTaskMemAlloc(Size);
Move(someStringParam^, Result^, Size);
end;
Хотя вы могли бы принять этот подход, я рекомендую альтернативу. Маршал все ваши строки как BSTR
на стороне C# и WideString
на стороне Delphi. Это совпадающие типы, которые также выделяются распределителем COM. Обе стороны точно знают, что делать с этими типами, и облегчат вашу жизнь.
К сожалению, вы не можете вернуть WideString
из функции Delphi через границу взаимодействия, потому что Delphi использует другой ABI для возвращаемых значений функции. Более подробную информацию об этой проблеме можно найти в моем вопросе. Почему WideString не может использоваться как функция, возвращающая значение для взаимодействия?
Чтобы обойти это, мы можем объявить тип возвращаемого значения из кода Delphi как TBStr
, Ваш код будет выглядеть так:
C#
[DllImport(@"MyDll.dll")]
[return: MarshalAs(UnmanagedType.BStr)]
private static extern string MyMethod(
[MarshalAs(UnmanagedType.BStr)]
string someStringParam
);
Delphi
function MyMethod(someStringParam: WideString): TBStr; stdcall;
begin
Result := SysAllocString(POleStr(someStringParam));
end;
Для меня маршаллинг Delphi WideString в строку.Net с использованием UnmanagedType.BStr работает очень хорошо в случае параметров In и Out. Но это не работает в случае функции, возвращающей строки. У меня есть функции Delphi -
function WS(val: WideString): WideString; stdcall;
begin
result := val;
end;
procedure WS1(out result: widestring); stdcall;
begin
result := 'ABCDE';
end;
и корреспондент.Net декларации -
[DllImport(@"my.dll", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Unicode)]
[return: MarshalAs(UnmanagedType.BStr)]
static extern string WS(
[MarshalAs(UnmanagedType.BStr)]
string val
);
[DllImport("my.dll", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Unicode)]
static extern void WS1(
[MarshalAs(UnmanagedType.BStr)]
out string res);
вызов WS1() работает просто отлично, а WS() вызывает исключение. И исключение зависит от того, какие модули включены в проект Delphi. Если включены "SysUtils" или "Classes" - приложение.Net вызывает SEHException "Внешний компонент сгенерировал исключение", если оба модуля исключены, приложение отображает диалоговое окно с сообщением об ошибке "Ошибка выполнения 203 в 009C43B4" и завершает его выполнение., Кстати, использование модуля "ShareMem" ничего не меняет.