0

I have the following C++ function that exports a struct with a char* field in it, but the string value is not as expected when used in Delphi, although it is null-terminated.

typedef struct _MyStruct{
    char* name;
    // other fields
}MyStruct,*PMyStruct;

extern "C" __declspec(dllexport) __cdecl MyTestStr(PMyStruct _PMyStruct)
{
    std::string str = "string";
    std::vector<char> cstr(str.begin(), str.end);
    cstr.push_back('\0');//null-terminated
    _PMyStruct->name = cstr.data();    
}
type
  PMyStruct = ^MyStruct;
  MyStruct= record
    name : PAnsiChar;
        // other fields
  end;

procedure MyTestStr(_PMyStruct: PMyStruct); cdecl; external 'mytest.dll' name 'MyTestStr';    

procedure TestMyRec();
var
  _MyStruct: MyStruct;
begin
  MyTestStr(@_MyStruct); 
  showmessage(_MyStruct.name);
  // here the result is just 'YYYYYYYYYYYYYYYYYY' not 'string'
end;
2
  • Which Delphi version? Commented Oct 12, 2018 at 9:56
  • @MartynA i'm using the Tokyo version . Commented Oct 12, 2018 at 10:01

2 Answers 2

1

_PMyStruct->name=cstr.data(); just makes pointer onto string body. But after function call local object std::string should be disposed. So you have got pointer to some memory address with unpredictable contents, this might cause AV if memory does not belong to application more.

Seems you have to allocate memory and call function that copies needed data into this memory address. Free this memory when needed.

Sign up to request clarification or add additional context in comments.

3 Comments

thank you , you mean using new ... delete in my c++ part and get-rid of the vector ?
@Sdean You will still need to co-ordinate when the memory is released if you do that.
Only one side must be responsible for memory allocation and freeing - or dll - in this case you have to provide additional function to free memory, -or application - imho better option. Note that many of WinAPI functions should be called twice - 1) to determine memory size and 2) real work with preallocated memory buffer
0

Change the definition of _MyStruct::name to const char *, and just assign the literal to it.

Note that names starting _ followed by an uppercase letter are reserved for the implementation, so your whole program has undefined behaviour.

You don't need to typedef struct.

struct MyStruct
{ 
    const char* name; // mutable pointer to constant char(s)
    // other fields
};

using PMyStruct = * MyStruct;

extern "C" __declspec(dllexport) __cdecl void MyTestStr(PMyStruct pMyStruct)
{
    pMyStruct->name = "string";
}

In the general case, it is inadvisable to pass owning pointers across dll boundaries. Instead the caller should allocate, and the function copy into that allocation. This is the pattern used across the Win32Api. You either return the size, or take int * parameters to write the sizes into

C++ Dll

extern "C" __declspec(dllexport) __cdecl void MyTestStr(PMyStruct pMyStruct = nullptr, int * firstname_size = nullptr, int * lastname_size = nullptr)
{
    if (pMyStruct)
    {
        std::strncpy(pMyStruct->firstname, "string", pMyStruct->firstname_len);
        std::strncpy(pMyStruct->lastname, "other string", pMyStruct->lastname_len);
    }
    if (firstname_size) { *firstname_size = 7; }
    if (lastname_size) { *lastname_size = 13; }
}

Delphi exe

type
  PInteger = ^Integer;
  PMyStruct = ^MyStruct;
  MyStruct= record
    firstname : PAnsiChar;
    firstname_len : Integer;
    lastname : PAnsiChar;
    lastname_len : Integer;
    // other fields
  end;

procedure MyTestStr(pMyStruct: PMyStruct; firstname_len : PInteger; lastname_len : PInteger); cdecl; external 'mytest.dll' name 'MyTestStr';   

procedure TestMyRec();
var
  myStruct: MyStruct;
begin
// If you don't know how much memory you will need, you have to ask
  MyTestStr(nil, @myStruct.firstname_len, @myStruct.lastname_len); 
  GetMem(myStruct.firstname, myStruct.firstname_len);
  GetMem(myStruct.lastname, myStruct.lastname_len);

  MyTestStr(@myStruct); 

  // Use myStruct

  FreeMem(myStruct.firstname);
  FreeMem(myStruct.lastname);
end;

17 Comments

thank you , but what about the null-termination , will my delphi part read it as 'string' ?
string literals are null terminated
indeed i use the vector to convert an std::string into char*
std::string has a member data() that returns a null terminated char *. It also has a member c_str() that returns a null terminated const char *. But the std::string has to outlast the usage on the Delphi side in either case
thank you but what if i've other fields : struct MyStruct { char * const name; int name_len; char * const lastname; int lastname_len; // other fields }; cause i see that MyTestStr function now returns the length of the string
|

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.