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 应用程序项目。然后我通过 LoadLibrary
和 GetProcAddress
.
显式加载 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;
我想将数据存储在 DLL 中并将其引用和大小传递给 Delphi 程序。
我创建了一个 C++ DLL 项目和一个 Delphi 应用程序项目。然后我通过 LoadLibrary
和 GetProcAddress
.
但是,我无法使用返回的引用访问数据。
源代码如下:
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;