3

I want to change size of buffer array in Delphi program if C++ DLL function copies longer array. Delphi Code:

function sendUserAllocatedArray(AllocatedArrayPtr: PAnsiChar; Length: Integer): Integer; cdecl;
external 'DLLlibrary.dll';

var
  myCharPtr : PAnsiChar;
  size : integer;  
  UserAllocatedArray: array[0..10] of AnsiChar;
  arrayPtr: PAnsiChar;
begin
    UserAllocatedArray :=  'test123';
    arrayPtr := UserAllocatedArray;
    size := sendUserAllocatedArray(arrayPtr, Length(UserAllocatedArray));   
end

C++ DLL:

extern "C" __declspec(dllexport) int sendUserAllocatedArray(char* data, int length);

int sendUserAllocatedArray(char* data, int length)
{
    char char_array[] = "this array length is more than 10";
    datLength = sizeof(char_array);
    if(datLength < length)
        strcpy_s(data, length, char_array);
    else
        ....;   

    return length;
}

So i need to allocate more space if bigger buffer needed before DLL function copy the data to array. Do i need handler. How can it be done.

3
  • 5
    The receiving function is guessing a buffer size - this is pointless, don't do it. The C DLL needs to provide a method that can return the size of buffer required. Often this is implemented using the same function with a branch for the special case of data being null. length should then pass as a pointer instead of by value and sendUserAllocatedArray should populate it with the length of buffer it will require. The caller can then allocate an appropriate buffer and call sendUserAllocatedArray again, this time with a valid buffer passed in data. Commented Jul 21, 2020 at 17:36
  • 3
    @J... We can't see the code in the .... section. But since the function already outputs the copied length as a return value, it is possible for it to return the required length without changing that parameter to a pointer. The caller can simply compare the returned length with the input length, and if the returned length is larger than retry with a larger buffer. Some Win32 APIs work this way. Commented Jul 21, 2020 at 18:52
  • @RemyLebeau Yes, good point - that works too. Commented Jul 21, 2020 at 18:56

2 Answers 2

5

Given the code you have shown, the only way to do what you want (without changing the DLL function's signature) is if the DLL function returns the required buffer length if the input buffer is too small, eg:

extern "C" __declspec(dllexport) int sendUserAllocatedArray(char* data, int length);

int sendUserAllocatedArray(char* data, int length)
{
    char char_array[] = "this array length is more than 10";
    int datLength = sizeof(char_array);
    if ((data) && (datLength <= length))
        memcpy(data, char_array, length);
    return datLength;
}

Then the Delphi code can do this:

function sendUserAllocatedArray(AllocatedArrayPtr: PAnsiChar; Length: Integer): Integer; cdecl; external 'DLLlibrary.dll';

var
  myCharPtr : array of AnsiChar;
  size : integer;  
  UserAllocatedArray: array[0..10] of AnsiChar;
  arrayPtr: PAnsiChar;
begin
  UserAllocatedArray := 'test123';
  size := sendUserAllocatedArray(UserAllocatedArray, Length(UserAllocatedArray));
  if size <= Length(UserAllocatedArray) then
  begin
    arrayPtr := UserAllocatedArray;
  end else
  begin
    SetLength(myCharPtr, size);
    arrayPtr := PAnsiChar(myCharPtr);
    StrLCopy(arrayPtr, UserAllocatedArray, size-1);
    size := sendUserAllocatedArray(arrayPtr, size);
  end;
  // use arrayPtr up to size chars as needed...
end;

Which can be simplified to this:

function sendUserAllocatedArray(AllocatedArrayPtr: PAnsiChar; Length: Integer): Integer; cdecl; external 'DLLlibrary.dll';

var
  size : integer;  
  UserAllocatedArray: array of AnsiChar;
begin
  SetLength(UserAllocatedArray, 10);
  StrLCopy(PAnsiChar(UserAllocatedArray), 'test123', Length(UserAllocatedArray)-1);
  repeat
    size := sendUserAllocatedArray(PAnsiChar(UserAllocatedArray), Length(UserAllocatedArray));
    if size <= Length(UserAllocatedArray) then Break;
    SetLength(UserAllocatedArray, size);
  until False;
  // use UserAllocatedArray up to size chars as needed...
end;

Or this:

function sendUserAllocatedArray(AllocatedArrayPtr: PAnsiChar; Length: Integer): Integer; cdecl; external 'DLLlibrary.dll';

var
  size : integer;  
  UserAllocatedArray: array of AnsiChar;
begin
  size := sendUserAllocatedArray(nil, 0);
  SetLength(UserAllocatedArray, size);
  StrLCopy(PAnsiChar(UserAllocatedArray), 'test123', size-1);
  size := sendUserAllocatedArray(PAnsiChar(UserAllocatedArray), size);
  // use UserAllocatedArray up to size chars as needed...
end;
Sign up to request clarification or add additional context in comments.

Comments

2

It is widely used solution in APIs (which fills passed memory array up with contents) to interpret the memory array input parameter NIL value as a mode switch to calculate the necessary memory needs.

This fuction should:

  1. Pass the memory needs back as a result.
  2. Check whether the memory pointer NIL or not.
  3. Check the passed memory array size and throw an exception in the right cases. (Comment : C has NOT exception mechanism! In case of C you must use special return values to inform the caller about the invalid input parameter value / processing errors)
  4. Of course, if you call C/C++ DLL function, the call convension must be set. Most of the cases cdecl/stdcall.

The example code for C DLL function:

unit Unit3;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;

type
  TDLLFunction = function ( buffer_ : pointer; memSize_ : integer ) : integer; cdecl;

  TForm3 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
  private
    { Private declarations }
    fDLLFunction : TDLLFunction;

  public
    { Public declarations }
  end;

var
  Form3: TForm3;

implementation

{$R *.dfm}

function testDLLFunction( mem_ : pchar; memSize_ : integer ) : integer; cdecl;
const
  CONST_text_Viered : array [0..3] of char = '1234';
begin
  result := ( length( CONST_text_Viered ) + 1 )*sizeOf( char );
  if ( mem_ <> NIL ) then
    if ( memSize_ >= result ) then
      strPCopy( mem_, CONST_text_Viered )
    else
      result := -1;
end;


procedure TForm3.Button1Click(Sender: TObject);
var
  memSize : integer;
  memArray : pchar;
begin
  memSize := fDLLFunction( NIL, 0 );
  getMem( memArray, memSize );
  try
    if ( fDLLFunction( memArray, memSize ) >= 0 ) then
    begin
      // Do some usefull think with memArray
    end else
      showMessage( 'Unsufficient memory error!' );
  finally
    freeMem( memArray );
  end;
end;

procedure TForm3.FormCreate(Sender: TObject);
begin
  fDLLFunction := @testDLLFunction;
end;

end.

Example code for C++ DLL function:

unit Unit3;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;

type
  TDLLFunction = function ( buffer_ : pointer; memSize_ : cardinal ) : cardinal; stdcall;

  TForm3 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
  private
    { Private declarations }
    fDLLFunction : TDLLFunction;

  public
    { Public declarations }
  end;

var
  Form3: TForm3;

implementation

{$R *.dfm}

type
  EUnsufficientMemoryError = class ( Exception )
    public 
      constructor Create( currentSize_, minimumSize_ : cardinal );
  end;

constructor EUnsufficientMemoryError.Create( currentSize_, minimumSize_ : cardinal );
begin
  inherited Create( 'Unsufficient memory! Current size: ' + intToStr( currentSize_ ) + ' Minimum size: ' + intToStr( minimumSize_ ) );
end;

function testDLLFunction( mem_ : pchar; memSize_ : cardinal ) : cardinal; stdcall;
const
  CONST_text_Viered : array [0..3] of char = '1234';
begin
  result := ( length( CONST_text_Viered ) + 1 )*sizeOf( char );
  if ( mem_ <> NIL ) then
    if ( memSize_ >= result ) then
      strPCopy( mem_, CONST_text_Viered )
    else
      raise EUnsufficientMemoryError.Create( memSize_, result );
end;


procedure TForm3.Button1Click(Sender: TObject);
var
  memSize: cardinal;
  memArray : pchar;

begin
  memSize := fDLLFunction( NIL, 0 );
  getMem( memArray, memSize );
  try
    try
      fDLLFunction( memArray, memSize );
      // Do some useful think with memArray
    except
      on e : EUnsufficientMemoryError do
        //...
        ;
    end;
  finally
    freeMem( memArray );
  end;
end;

procedure TForm3.FormCreate(Sender: TObject);
begin
  fDLLFunction := @testDLLFunction;
end;

end.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.