DLL, созданная в delphi XE7, возвращает массив строк для использования в Delphi 2007.

Я пытаюсь создать dll, использующую компонент TAmazonStorageService в XE7 для наших проектов в Delphi 2007.

Однако у меня несколько утечек памяти, или весь индекс массива возвращает последнюю строку.

Вот моя функция:

function ListBuckets(const PrivateKEY: PAnsiChar; const PublicKEY: PAnsiChar; 
                       out ArrayBuckets: TArray<PAnsiChar>; 
                       out Error: PAnsiChar): Boolean; stdcall;
var
  AmazonConn: TAmazonConnectionInfo;
  AmazonS3: TAmazonStorageService;
  ResponseInfo: TCloudResponseInfo;
  BucketsList: TStrings;

  I: Integer;
begin
  Result := True;

  AmazonConn := TAmazonConnectionInfo.Create(nil);

  AmazonConn.AccountName := string(PublicKEY); { AccessKeyID }
  AmazonConn.AccountKey := string(PrivateKEY); { SecretAccessKeyID }

  AmazonS3 := TAmazonStorageService.Create(AmazonConn);

  ResponseInfo := TCloudResponseInfo.Create;

  Error := '';

  try
    BucketsList := AmazonS3.ListBuckets(ResponseInfo);
    if not Assigned(BucketsList) then
    begin
      Result := False;

      Error := PAnsiChar(AnsiString(ResponseInfo.StatusMessage));
    end
    else
    begin
      SetLength(ArrayBuckets, BucketsList.Count);

      for I := 0 to BucketsList.Count - 1 do
        ArrayBuckets[I] := PAnsiChar(AnsiString(BucketsList.Strings[I]));
    end;
  finally
    BucketsList.Free;
    ResponseInfo.Free;
    AmazonS3.Free;
    AmazonConn.Free;
  end;
end;

exports ListBuckets;

И вот как я «пытаюсь» использовать эту функцию. Должен работать в Delphi XE7 и Delphi 2007.

type
  TAnsiCharArray = array of PAnsiChar;

function ListBuckets(const PrivateKEY: PAnsiChar; const PublicKEY: PAnsiChar;
                       out ArrayBuckets: TAnsiCharArray; 
                       out MensagemErro: PAnsiChar): Boolean; stdcall;
                       external 'Test.dll';

function ListBucketsDelphi(const PrivateKEY: string; const PublicKEY: string;
                           out StringListBuckets: TStringList;
                           out Error: string): Boolean;
var
  vAnsiPrivateKEY: PAnsiChar;
  vAnsiPublicKEY: PAnsiChar;
  vAnsiError: PAnsiChar;
  vStringArray: TAnsiCharArray;
  I: Integer;
begin
{$IFDEF UNICODE}
  vAnsiPrivateKEY := PAnsiChar(RawByteString(PrivateKEY));
  vAnsiPublicKEY := PAnsiChar(RawByteString(PublicKEY));
{$ELSE}
  vAnsiPrivateKEY := PAnsiChar(PrivateKEY);
  vAnsiPublicKEY := PAnsiChar(PublicKEY);
{$ENDIF}

  //StringListBuckets need to be created before...

  Result := ListBuckets(vAnsiPrivateKEY, vAnsiPublicKEY, vStringArray, vAnsiMensagemErro);

  if not (Result) then    
    Error := string(vAnsiError);

  try
    if Result then
    begin
      for I := Low(vStringArray) to High(vStringArray) do
        StringListBckets.Append(vStringArray[I]);
    end;
  except
    Result := False;
    Error := '"StringListBuckets" not created.';
  end;
end;

Я немного поискал, но ничего не нашел о возврате массивов в юникодной dll, или, может быть, я неправильно ищу.

Кто-нибудь может помочь?

Заранее спасибо.


person Rash    schedule 09.10.2014    source источник


Ответы (1)


Эта функция вообще не может быть безопасно вызвана. У вас есть следующие проблемы:

  • Если вы не делитесь диспетчером памяти, вы выделяете память в одном модуле и уничтожаете ее в другом. Это против правил.
  • Вы возвращаете указатели на строки (ваши два параметра out), которые недействительны при возврате функции. Другими словами, вещи, на которые указывают эти указатели, больше не существуют после возврата функции.
  • Вы передаете динамические массивы Delphi через границу модуля. Динамические массивы Delphi не подходят для взаимодействия.

Кроме того, ваши функции try/finally реализованы некорректно. Вы должны следовать хорошо известному стандартному шаблону, который можно увидеть в бесчисленном количестве мест. Я не думаю, что это место, чтобы повторять это.

Вам нужен полный редизайн. Некоторые варианты:

  1. Позвольте вызывающей стороне выделить память и передать ее DLL для заполнения. Это заставит вас заранее решить, сколько памяти выделить.
  2. Используйте подход, основанный на перечислении. Вызовите DLL, чтобы выделить непрозрачный указатель, который содержит состояние. Затем многократно вызывайте другую функцию, передавая непрозрачный указатель и получая одну строку. Когда данных больше нет, вызовите функцию финализации, чтобы привести их в порядок. Используйте WideString, чтобы вернуть строку, так как она выделена из общей кучи.
  3. Сериализуйте возвращенную информацию, например, в JSON. Затем верните это в одной строке, снова используя WideString, чтобы воспользоваться общей кучей COM.
  4. Попросите исполняемый файл предоставить функцию обратного вызова для DLL. Затем DLL может выделить массив и передать его функции обратного вызова. Функция обратного вызова должна получить копию массива.
  5. Объявите интерфейс, обертывающий структуру, которая может содержать данные. Передайте этот интерфейс в DLL. Интерфейсы безопасны для использования вне границ модулей.
  6. Используйте безопасный массив COM, чтобы вернуть массив.
  7. Избегайте использования отдельной библиотеки DLL и кодируйте все в своем модуле Delphi 2007.
person David Heffernan    schedule 09.10.2014
comment
Я не уверен в этом, вопрос адресован @DavidHeffernan: поскольку widestring является оболочкой для CCOMBSTR, которая является подсчитываемой ссылкой COM, не безопасно ли возвращать объект без предварительно инициализированной памяти? (Я знаю, что это противоречит передовой практике, и я бы никогда этого не сделал (если бы я сделал, мой менеджер по программированию накричал бы на меня), но только теоретически разве это не безопасно в реальных сценариях? (Я не совсем знаком с COM, но мое догадка имеет собственный счетчик ссылок на свои объекты. Опять же: я не уверен.) - person mg30rg; 10.10.2014
comment
@mg30rg WideString является оболочкой BSTR, а не CComBSTR. И обычный BSTR не считается ссылкой. Можно использовать out WideString параметров за пределами модуля. - person David Heffernan; 10.10.2014