C++ DLL 到 Delphi:如何将指向数据的指针从 C++ DLL 传递到 Delphi 程序?

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

我想将数据存储在 DLL 中并将其引用和大小传递给 Delphi 程序。

我创建了一个 C++ DLL 项目和一个 Delphi 应用程序项目。然后我通过 LoadLibraryGetProcAddress.

显式加载 DLL

但是,我无法使用返回的引用访问数据。

源代码如下:

C++ DLL 调用函数:

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 来电者:

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;

您应该从 DLL 中导出 C 函数。并决定是否要 DLL 函数将数据复制到传递的缓冲区指针中,或者只是返回指向数据的指针。

我构建了一个显示 DLL 复制数据的示例。正如您在问题中所做的那样,我使用了动态数组。 DLL 内部的函数接收复制数据的指针(即 Delphi 动态数组)和动态数组的长度。 DLL里面的函数复制了一些数据,最大长度为指定的最大长度,return实际长度。

我还使用 Ansi char 来传递数据,您似乎喜欢这样。也可以通过 Unicode。

DLL源代码:

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]);
}

简单控制台模式 Delphi 应用程序使用该 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.

这段代码中有很多错误。

在 C++ 方面;

  • data 参数正在按值传递,因此重新分配它的值只会在函数局部发生,不会影响来电者在所有。您需要通过 reference/pointer 传递参数。您正在尝试使用 bufferLength 参数执行此操作,但您没有正确为其分配值。

  • char* 上使用 sizeof() 不会让您获得所指向的字符串数据的正确长度。参见 Find the size of a string pointed by a pointer

Delphi 方:

  • 在获取指向它的有效负载的指针之前,您没有为动态数组分配任何内存。

  • 您的 C++ 代码是作为 non-static class 方法实现的,但是您的 Delphi 代码没有考虑方法的隐式 this 参数(即 Delphi class 方法中的 Self 参数)。它需要一个对象来调用方法。

由于编译器的差异,跨 DLL 边界公开对 C++ 对象的直接访问不是一个好主意。您应该 re-implement DLL 函数作为平面 C-style 函数。您仍然可以使用 exportClass,但只能在 DLL 内部使用,并且必须在 Delphi 端抽象访问它。

话虽如此,试试这样的东西:

// 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;