Почему при статической линковке DLL к консольной программе на Delphi возникают ошибки доступа (access violation) при возникновении исключений?

Как правильно подключить стандартный компонент Windows `amsi.dll` в Delphi для выполнения проверки области памяти антивирусом? У меня возникает проблема: хотя функция успешно определяет зараженные файлы, при генерировании исключений (с помощью команды `raise`) я получаю ошибки доступа к памяти, такие как "Access violation" или "Stack Overflow". Я подозреваю, что импортированные функции из DLL могут быть неправильно объявлены или вызваны, что влияет на стек, но не могу выяснить, в чем именно заключается ошибка. 

Ниже представлен минимальный код, который вызывает ошибку. Если закомментировать блок, где возникает проблема, ошибка исчезает. 

```delphi
program Project1;
{$APPTYPE CONSOLE}

uses SysUtils, Classes, Windows, comobj;

type
  HAMSICONTEXT__ = record
    unused: Integer;
  end;

  PHAMSICONTEXT__ = ^HAMSICONTEXT__;
  HAMSICONTEXT = PHAMSICONTEXT__;
  PHAMSICONTEXT = ^HAMSICONTEXT;

var
  AmsiContext: HAMSICONTEXT;
  fResult: HRESULT;
  appName: WideString = 'amsitest';

function AmsiInitialize(appName: LPCWSTR; amsiContext: PHAMSICONTEXT): HRESULT; cdecl;
  external 'amsi.dll' name 'AmsiInitialize';

procedure AmsiUninitialize(amsiContext: HAMSICONTEXT); cdecl;
  external 'amsi.dll' name 'AmsiUninitialize';

begin
  try
    CoInitializeEx(nil, 0);
    //проблемный блок начало
    if(AmsiInitialize(PWideChar(appName), @AmsiContext) <> S_OK)
      then Raise(Exception.Create('AmsiInitialize failed'));
    AmsiUninitialize(AmsiContext);
    //проблемный код конец
    Raise(Exception.Create('generic error'));
  except
    on E:Exception do writeln(E.Message);
  end;
end.
```
  • 18 ноября 2024 г. 13:23
Ответы на вопрос 2
В вашем коде возникает ошибка доступа к памяти (Access violation) из-за неправильного управления контекстом `amsiContext`, который вы передаете в функцию `AmsiUninitialize`. Давайте разберем, в чем может быть проблема, и как диагностировать и исправить ее.

### Проблемы в коде

1. **Некорректное использование указателей**: Вы объявляете переменную `AmsiContext` как `HAMSICONTEXT`, но передаете в функции указатель на неё (`@AmsiContext`) как `PHAMSICONTEXT`. В качестве результата функции `AmsiInitialize` ожидается указатель, который должен корректно инициализироваться.

2. **Неправильное освобождение**: Вы вызываете `AmsiUninitialize` с параметром, который не был инициализирован должным образом. В результате это может привести к Access Violation.

### Как исправить

Вот исправленный и улучшенный вариант вашего кода:

```delphi
program Project1;
{$APPTYPE CONSOLE}

uses SysUtils, Windows;

type
  HAMSICONTEXT__ = record
    unused: Integer; // В реальности структура более сложная; проверьте документацию.
  end;

  PHAMSICONTEXT__ = ^HAMSICONTEXT__;
  HAMSICONTEXT = PHAMSICONTEXT__;

var
  AmsiContext: HAMSICONTEXT;
  fResult: HRESULT;
  appName: WideString = 'amsitest';

function AmsiInitialize(appName: LPCWSTR; out amsiContext: HAMSICONTEXT): HRESULT; cdecl;
  external 'amsi.dll' name 'AmsiInitialize';

procedure AmsiUninitialize(amsiContext: HAMSICONTEXT); cdecl;
  external 'amsi.dll' name 'AmsiUninitialize';

begin
  try
    CoInitializeEx(nil, COINIT_MULTITHREADED);
    try
      fResult := AmsiInitialize(PWideChar(appName), AmsiContext);
      if (fResult <> S_OK) then
        Raise Exception.Create('AmsiInitialize failed: HRESULT = ' + IntToStr(fResult));

      // Код для проверки файлов с использованием AmsiContext должен быть здесь.

      AmsiUninitialize(AmsiContext);
      
    finally
      CoUninitialize;
    end;

    Raise Exception.Create('generic error');
  except
    on E: Exception do 
      writeln(E.Message);
  end;
end.
```

### Основные изменения:

1. **Использование `out` в прототипе функции**: В функции `AmsiInitialize` я заменил `PHAMSICONTEXT` на `out HAMSICONTEXT`. Это позволяет корректно передавать контекст.

2. **Добавление CoUninitialize**: Добавил `CoUninitialize`, чтобы гарантировать корректное завершение COM и предотвратить утечки ресурсов.

3. **Проверка HRESULT**: После вызова `AmsiInitialize` добавил проверку результат, чтобы убедиться, что инициализация прошла успешно. 

### Заключение

Согласно этим изменениям, ваш код должен работать без ошибок доступа к памяти. Если у вас есть дополнительные требования к работе с `amsi.dll`, обязательно ознакомьтесь с документацией, чтобы корректно обрабатывать контекст и любые другие возвращаемые значения.
STDAPI AmsiInitialize(
    _In_  LPCWSTR appName,
    _Outptr_ HAMSICONTEXT* amsiContext);

В Delphi это разве не stdcall будет?
DECLARE_HANDLE(HAMSICONTEXT);
DECLARE_HANDLE(HAMSISESSION);

По-хорошему нужно бы узнать, как объявлена DECLARE_HANDLE , но с высокой вероятностью на Delphi это объявление должно идти так:
type
  HAMSICONTEXT = type THandle;
  HAMSISESSION = type THandle;

Если я прав, последующие объявления типов не нужны — это чисто сишные заморочки. В Delphi возвращаемый описатель логично объявить через var :
function AmsiInitialize(appName: LPCWSTR; var amsiContext: HAMSICONTEXT): HRESULT; stdcall;
  external 'amsi.dll' name 'AmsiInitialize';
procedure AmsiUninitialize(amsiContext: HAMSICONTEXT); stdcall;
  external 'amsi.dll' name 'AmsiUninitialize';
Похожие вопросы