简体   繁体   中英

C++ DLL to Delphi: How to pass a pointer to data from C++ DLL to Delphi program?

I want to store data in a DLL and pass its reference and size to the Delphi program.

I created a C++ DLL project and a Delphi application project. Then I load the DLL explicitly by LoadLibrary and GetProcAddress .

However, I couldn't access the data using the returned reference.

Following are the source codes:

C++ DLL callee function:

void __stdcall exportClass::getDataReference(char* data, int* bufferLength)
{   
    // char* charArray: is member of exportClass
    charArray = "Pass this Array of Char By Reference";
    data = charArray;
    bufferLength = sizeof(charArray) / sizeof(charArray[0]);
}

Delphi caller:

procedure callDllFunction();
var
  dynamicCharArray : array of AnsiChar;
  pData: PAnsiChar;
  length: Integer;
  pInt: ^Integer;
begin
  pData := @dynamicCharArray[0];
  length := 10;
  pInt := @length;
  explicitDllLoaderClass.callGetDataReference(pData, pInt);
  SetLength(dynamicCharArray, length);
end;

There are numerous mistakes in this code.

On the C++ side;

  • The data parameter is being passed by value , so reassigning its value will only be local to the function and won't affect the caller at all. You need to pass the parameter by reference/pointer instead. You are attempting to do that with the bufferLength parameter, but you are not assigning a value to it correctly.

  • using sizeof() on a char* will not get you the correct length of the string data being pointed at. See Find the size of a string pointed by a pointer

On the Delphi side:

  • you are not allocating any memory for the dynamic array before taking a pointer to it's payload.

  • your C++ code is implemented as a non-static class method, but your Delphi code is not accounting for the method's implicit this parameter (ie the Self parameter in Delphi class methods). It needs an object to call the method on.

Due to compiler differences, it is not a good idea to expose direct access to C++ objects across the DLL boundary. You should re-implement the DLL function as a flat C-style function instead. You can still use your exportClass , but internally to the DLL only, and will have to abstract access to it on the Delphi side.

With that said, try something more like this:

// export this
void* __stdcall getExportClassObj()
{
    return new exportClass;
}

// export this
void __stdcall freeExportClassObj(void *obj)
{
    delete (exportClass*) obj;
}

// export this
void __stdcall getDataReference(void *obj, char* &data, int &bufferLength)
{
    ((exportClass*)obj)->getDataReference(data, bufferLength);
}

// do not export this
void __stdcall exportClass::getDataReference(char* &data, int &bufferLength)
{
    // char* charArray: is member of exportClass
    charArray = "Pass this Array of Char By Reference";
    data = charArray;
    bufferLength = strlen(charArray);
}
type
  GetExportClassObjFunc = function: Pointer; stdcall;
  FreeExportClassObjProc = procedure(obj: Pointer); stdcall;
  GetDataReferenceProc = procedure(obj: Pointer; var data: PAnsiChar; var bufferLength: Integer); stdcall;

getExportClassObj: GetExportClassObjFunc;
freeExportClassObj: FreeExportClassObjProc;
getDataReference: GetDataReferenceProc;

...

procedure callDllFunction();
var
  dynamicCharArray : array of AnsiChar;
  pData: PAnsiChar;
  length: Integer;
  Obj; pointer;
begin
  Obj := getExportClassObj();
  try
    getDataReference(Obj, pData, length);
    // use pData up to length chars as needed...
    SetLength(dynamicCharArray, length);
    Move(pData^, PAnsiChar(dynamicCharArray)^, length);
    ...
  finally
    freeDataReference(Obj);
  end;
end;

You should export a C function out of your DLL. And decide if you want the DLL function copy data into the passed buffer pointer or just give back the pointer to the data.

I build an example showing the DLL copying the data. I used a dynamic array as you've done in your question. The function inside DLL receive the pointer where to copy data (That is the Delphi dynamic array) and the length of the dynamic array. The function inside the DLL copy some data, up to the maximum length specified, and return the actual length.

I also used Ansi char to pass data as you seems to like this. Could could as well pass Unicode.

DLL source code:

BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
                     )
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}

extern "C" __declspec(dllexport) void __stdcall getDataReference(char* data, int* bufferLength)
{
    char charArray[] = "Pass this Array of Char By Reference";
    if ((data == NULL) || (bufferLength == NULL) || (*bufferLength <= 0))
        return;
    #pragma warning(suppress : 4996)
    strncpy(data, charArray, *bufferLength);
    *bufferLength = sizeof(charArray) / sizeof(charArray[0]);
}

And simple console mode Delphi application consuming that DLL:

program CallMyDll;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  Winapi.Windows,
  System.SysUtils;

type
    TGetDataReference = procedure (Data         : PAnsiChar;
                                   BufferLength : PInteger); stdcall;

var
    DllHandle        : THandle;
    GetDataReference : TGetDataReference;

procedure callDllFunction();
var
    DynamicCharArray : array of AnsiChar;
    DataLength       : Integer;
    S                : String;
begin
    DataLength := 1000;
    SetLength(DynamicCharArray, DataLength);
    GetDataReference(PAnsiChar(DynamicCharArray), @DataLength);
    SetLength(DynamicCharArray, DataLength);
    S := String(PAnsiChar(DynamicCharArray));
    WriteLn(S);
end;

begin
    try
        DllHandle := LoadLibrary('D:\FPiette\Cpp\MyDll\Debug\MyDll.dll');
        if DllHandle = 0 then
            raise Exception.Create('DLL not found');
        @GetDataReference := GetProcAddress(DllHandle, '_getDataReference@8');
        if @getDataReference = nil then
            raise Exception.Create('getDataReference not found');
        callDllFunction();
    except
        on E: Exception do
            Writeln(E.ClassName, ': ', E.Message);
    end;
    WriteLn('Hit RETURN...');
    ReadLn;
end.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM