delphi XE7中生成的DLL,返回用于delphi 2007的字符串数组

delphi XE7中生成的DLL,返回用于delphi 2007的字符串数组,delphi,cloud,delphi-xe7,Delphi,Cloud,Delphi Xe7,我正在尝试为Delphi2007中的项目创建一个dll,它使用XE7中的TAmazonStorageService组件 type TAnsiCharArray = array of PAnsiChar; function ListBuckets(const PrivateKEY: PAnsiChar; const PublicKEY: PAnsiChar; out ArrayBuckets: TAnsiCharArray;

我正在尝试为Delphi2007中的项目创建一个dll,它使用XE7中的TAmazonStorageService组件

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;
但是,我有几个内存泄漏,或者数组的所有索引都返回最后一个字符串

以下是我的功能:

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;
我搜索了一点,但在unicode dll中没有找到关于返回数组的内容,或者可能我搜索错了

有人能帮忙吗


提前感谢。

该函数根本无法安全调用。您有以下问题:

  • 除非共享内存管理器,否则您将在一个模块中分配内存,并在另一个模块中销毁它。那是违反规定的
  • 当函数返回时,返回的字符串指针(两个
    out
    参数)无效。换句话说,一旦函数返回,这些指针指向的东西就不再存在了
  • 您正在跨模块边界传递Delphi动态数组。Delphi动态数组对互操作无效
除此之外,您的函数
try/finally
实现不正确。你必须遵循众所周知的标准模式,这在无数地方都可以看到。我不认为这是重复这一点的地方

你需要重新设计。一些选择:

  • 让调用方分配内存,并将其传递给DLL进行填充。这将限制您预先决定分配多少内存
  • 使用基于枚举的方法。调用DLL以分配保存状态的不透明指针。然后重复调用另一个函数,传递不透明指针,并生成一个字符串。当没有更多数据时,调用finalization函数进行整理。使用
    WideString
    返回字符串,因为该字符串是从共享堆中分配的
  • 将返回的信息序列化为,例如JSON。然后在单个字符串中返回,再次使用
    WideString
    利用共享COM堆
  • 让可执行文件向DLL提供回调函数。然后DLL可以分配一个数组并将其传递给回调函数。回调函数必须获取数组的副本
  • 声明一个接口,该接口封装可以保存数据的结构。将该接口传递给DLL。跨模块边界使用接口是安全的
  • 使用COM安全数组返回数组
  • 避免使用单独的DLL并对Delphi2007模块中的所有内容进行编码
    我不确定这一点,问题是@DavidHeffernan:既然widestring是CCOMBSTR(一个引用计数的COM对象)的包装器,那么返回一个没有预初始化内存的对象难道不安全吗?(我知道这与“最佳实践”背道而驰,我永远不会做这样的事情(如果我做了,我的PM会对我大喊大叫),但只有从理论上讲,它在现实生活中不安全吗?(我对COM不是很熟悉,但我猜它有自己的对象引用计数。再说一次:我不确定。)@mg30rg
    WideString
    BSTR
    的包装,而不是
    CComBSTR
    的包装。普通
    BSTR
    不包含引用计数。跨模块边界使用
    out WideString
    参数是可以的。