Jean_D 1 Posted July 31 I have created a DLL with one exported function using the latest version of Delphi (12.1). The function takes one parameter: a record type variable. library MyDLL; uses System, SysUtils; type TMyRecord = record MyString: AnsiString; MyInteger: Integer; end; function FillRecord(var Rec: TMyRecord): Boolean; stdcall; export; begin Rec.MyString := 'Hello from Delphi'; Rec.MyInteger := 42; Result := True; end; exports FillRecord; begin end. In my C++ Builder 6.0 application, I have declared the following: struct TMyRecord { char *MyString; int MyInteger; }; extern "C" __declspec(dllimport) bool __stdcall FillRecord(TMyRecord *Rec); When calling the 'FillRecord' function from my C++ Builder application, I do not get the expected results: TMyRecord iMyRec; Memo1->Lines->Clear(); Memo1->Lines->Add(Format("Address: %p", ARRAYOFCONST((&iMyRec)))); if (FillRecord(&iMyRec)) { String iData = iMyRec.MyString; Memo1->Lines->Add("iMyRec.MyString: " + iData); int iNumber = iMyRec.MyInteger; Memo1->Lines->Add("iMyRec.MyInteger: " + IntToStr(iNumber)); } else { Memo1->Lines->Add("Error calling FillRecord"); } I am expecting: iMyRec.MyString: Hello from Delphi iMyRec.MyInteger: 42 But I am getting: iMyRec.MyString: H iMyRec.MyInteger: 42 I am drawing a blank when trying to figure out what I am doing wrong. Any inputs/suggestions to solve my issue would be greatly appreciated. Thank you Share this post Link to post
corneliusdavid 220 Posted July 31 Either include the ShareMem unit in both Delphi and C++ so both sides understand Delphi's strings, or pass the string as a pointer (e.g. PAnsiChar) from the Delphi unit--which is what the C++ code is expecting. Share this post Link to post
Jean_D 1 Posted July 31 Thank you very much. Passing the string as a pointer from the Delphi unit did fix my issue. Share this post Link to post
Remy Lebeau 1436 Posted July 31 (edited) 5 hours ago, Jean_D said: When calling the 'FillRecord' function from my C++ Builder application, I do not get the expected results: The C++ code is not creating a valid AnsiString object for the Delphi code to manipulate. The struct would have needed to look more like this instead: struct DECLSPEC_DRECORD TMyRecord { AnsiString MyString; int MyInteger; }; But, that still would not have worked correctly in your case, because the internal structure of AnsiString changed in 2009, so passing an AnsiString object between C++Builder 6 and Delphi 12.1 would cause Undefined Behavior, It it never safer to pass non-POD types across a DLL boundary, unless very strict prerequisites are met (ie, sharing a common RTL, sharing the same memory manager, etc), which is not the case in this example. To do what you are attempting, you would have to allocate AND free the string memory inside of the DLL, and then pass that memory around as needed, eg: library MyDLL; uses System, SysUtils; type TMyRecord = record MyString: PAnsiChar; MyInteger: Integer; end; function FillRecord(var Rec: TMyRecord): Boolean; stdcall; begin Rec.MyString := StrNew(PAnsiChar('Hello from Delphi')); Rec.MyInteger := 42; Result := True; end; procedure DisposeRecord(var Rec: TMyRecord); stdcall; begin StrDispose(Rec.MyString); Rec.MyString := nil; end; exports FillRecord, DisposeRecord; begin end. struct DECLSPEC_DRECORD TMyRecord { char* MyString; int MyInteger; }; extern "C" __declspec(dllimport) bool __stdcall FillRecord(TMyRecord *Rec); extern "C" __declspec(dllimport) void __stdcall DisposeRecord(TMyRecord *Rec); TMyRecord iMyRec = {}; Memo1->Lines->Clear(); Memo1->Lines->Add(Format("Address: %p", ARRAYOFCONST((&iMyRec)))); if (FillRecord(&iMyRec)) { AnsiString iData = iMyRec.MyString; Memo1->Lines->Add("iMyRec.MyString: " + iData); int iNumber = iMyRec.MyInteger; Memo1->Lines->Add("iMyRec.MyInteger: " + IntToStr(iNumber)); DisposeRecord(&iMyRec); } else { Memo1->Lines->Add("Error calling FillRecord"); } Edited July 31 by Remy Lebeau Share this post Link to post