Search code examples
delphiclouddelphi-xe7

DLL made in delphi XE7 returning array of string for use in Delphi 2007


I'm trying to make a dll that uses the TAmazonStorageService component in XE7 for our projects in Delphi 2007.

However, I am having several memory leaks, or all the index of the array returns the last string.

Here is my function:

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;

And here is how im "trying" to use this function. Needs to work in Delphi XE7 and 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;

I searched a bit but found nothing about returning arrays in a unicode dll, or maybe i am searching wrong.

Can someone help?

Thanks in advance.


Solution

  • That function cannot be safely called at all. You have the following problems:

    • Unless you share a memory manager, you are allocating memory in one module, and destroying it in another. That's against the rules.
    • You are returning pointers to strings (your two out parameters) that are invalid when the function returns. In other words, the things that these pointers point to are no longer there once the function returns.
    • You are passing Delphi dynamic arrays across a module boundary. Delphi dynamic arrays are not valid for interop.

    On top of that, your functions try/finally is implemented incorrectly. You must follow the well known, standard pattern, that can be seen in countless places. I don't think this is the place to repeat that.

    You need a complete redesign. Some options:

    1. Let the caller allocate memory, and pass that to the DLL to populate. This would constraint you to decide up front how much memory to allocate.
    2. Use an enumeration based approach. Make a call to the DLL to allocate an opaque pointer that holds the state. Then call another function, repeatedly, passing the opaque pointer, and yielding a single string. When there are no more data, call a finalization function to tidy up. Use WideString to return the string since this is allocated off a shared heap.
    3. Serialize the returned information to, for instance, JSON. Then return that in a single string, again using WideString to take advantage of the shared COM heap.
    4. Have the executable provide a callback function to the DLL. Then the DLL can allocate an array and pass that to the callback function. The callback function must take a copy of the array.
    5. Declare an interface that wraps a structure that can hold the data. Pass that interface to the DLL. Interfaces are safe to use across module boundaries.
    6. Use a COM safe array to return the array.
    7. Avoid using a separate DLL and code everything in your Delphi 2007 module.