Доступ к Delphi DLL, вызывающей исключение иногда - PullRequest
1 голос
/ 16 декабря 2011

Когда я вызываю метод 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</p> <p>Attempted to read or write protected memory. This is often an indication that other memory is corrupt.

Кто-нибудь знает, почему я получаю только это исключение иногда ? Почему иногда это проходит гладко?

Ответы [ 2 ]

14 голосов
/ 16 декабря 2011

У вас явно есть несоответствие между кодом 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;
2 голосов
/ 24 января 2012

Для меня маршаллинг строки 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» ничего не меняет.

...