MrClear
MrClear

Reputation: 139

c++ dll returning string to delphi

Delphi declaration of external c++ dll function.

       Const dllname = 'NavServer.dll';
function FindNavMeshPath(MapID : integer; StartX : Single   ; StartY : Single   ; StartZ : Single   ; EndX : Single ; EndY : Single ; EndZ : Single): PansiChar; stdcall; external dllname;

extern "C" NAVSERVER_API const char*  __stdcall FindNavMeshPath(const int MapID, const float StartX, const float StartY, const float StartZ, const float EndX, const float EndY, const float EndZ)
{                               //const char*
    const char* str1;
    if (pathSize > 0)
    {
        std::stringstream ss;

        for (int i = 0;i < pathSize; i++)
        {
            ss << i;
        }
        ss << "dadsaassaasd";
        std::string tmp;
        ss >> tmp;
        str1 = tmp.c_str();
                                //ss.str();
        return str1;
                ///tmp.data();
    }
    else
    {
        return "Path return no points.";
    }

c++ dll function, This all works as intended until a specific length of str1 then Delphi returns garbage. I am needing to return potentially very long strings. Any suggestions would be appreciated thanks !

Upvotes: 0

Views: 635

Answers (1)

Remy Lebeau
Remy Lebeau

Reputation: 595402

The problem is that the DLL function is returning a pointer to data that is freed automatically when the function exits, thus a dangling pointer is returned to the caller (ie, Delphi). Any use of that pointer by the caller is thus undefined behavior.

The DLL needs to allocate memory dynamically for the returned pointer, and then export a second function that the caller can use to free the memory when done using it, eg:

const
  dllname = 'NavServer.dll';

function FindNavMeshPath(MapID : integer; StartX : Single; StartY : Single; StartZ : Single; EndX : Single; EndY : Single; EndZ : Single): PAnsiChar; stdcall; external dllname;
procedure FreeMeshPath(Path: PAnsiChar); stdcall; external dllname;

var
  Path: PAnsiChar;
begin
  Path := FindNavMeshPath(...);
  if Path <> nil then
  try
    ...
  finally
    FreeMeshPath(Path);
  end;
end;
extern "C" NAVSERVER_API const char*  __stdcall FindNavMeshPath(const int MapID, const float StartX, const float StartY, const float StartZ, const float EndX, const float EndY, const float EndZ)
{
    char* str1;
    if (pathSize > 0)
    {
        std::ostringstream oss;

        for (int i = 0;i < pathSize; i++)
        {
            oss << i;
        }
        oss << "dadsaassaasd";

        std::string tmp = oss.str();
        size_t needed = tmp.size() + 1;

        str1 = new(nothrow) char[needed];
        if (str1)
            std::copy_n(tmp.c_str(), needed, str1);
    }
    else
    {
        static const char *errorMsg = "Path return no points.";
        static const size_t needed = std::strlen(errorMsg) + 1;

        str1 = new(nothrow) char[needed];
        if (str1)
            std::copy_n(errorMsg, needed, str1);
    }
    return str1;
}

extern "C" NAVSERVER_API void __stdcall FreeMeshPath(char *Path)
{
    delete[] Path;
}

Alternatively, have the DLL use an OS-provided memory manager, like LocalAlloc() or CoTaskMemAlloc(), so that the caller can then free the returned memory directly using the appropriate ...Free() function, eg:

const
  dllname = 'NavServer.dll';

function FindNavMeshPath(MapID : integer; StartX : Single; StartY : Single; StartZ : Single; EndX : Single; EndY : Single; EndZ : Single): PAnsiChar; stdcall; external dllname;

var
  Path: PAnsiChar;
begin
  Path := FindNavMeshPath(...);
  if Path <> nil then
  try
    ...
  finally
    LocalFree(Path);
  end;
end;
extern "C" NAVSERVER_API const char*  __stdcall FindNavMeshPath(const int MapID, const float StartX, const float StartY, const float StartZ, const float EndX, const float EndY, const float EndZ)
{
    char* str1;
    if (pathSize > 0)
    {
        std::ostringstream oss;

        for (int i = 0;i < pathSize; i++)
        {
            oss << i;
        }
        oss << "dadsaassaasd";

        std::string tmp = oss.str();
        size_t needed = tmp.size() + 1;

        str1 = static_cast<char*>(LocalAlloc(LMEM_FIXED, needed));
        if (str1)
            std::copy_n(tmp.c_str(), needed, str1);
    }
    else
    {
        static const char *errorMsg = "Path return no points.";
        static const size_t needed = std::strlen(errorMsg) + 1;

        str1 = static_cast<char*>(LocalAlloc(LMEM_FIXED, needed));
        if (str1)
            std::copy_n(errorMsg, needed, str1);
    }
    return str1;
}

Alternatively, make the caller allocate its own memory buffer that is passed into the DLL to be filled in, and then the caller can free the buffer when done using it. This typically requires the caller to call the DLL function twice - once to calculate the necessary buffer size, and then again to fill the buffer after it has been allocated, eg:

const
  dllname = 'NavServer.dll';

function FindNavMeshPath(MapID : integer; StartX : Single; StartY : Single; StartZ : Single; EndX : Single; EndY : Single; EndZ : Single; PathBuffer: PAnsiChar; PathBufferSize: integer): integer; stdcall; external dllname;

var
  Path: AnsiString;
  Size: Integer;
begin
  Size := FindNavMeshPath(..., nil, 0);
  if Size <> -1 then
  begin
    SetLength(Path, Size-1);
    FindNavMeshPath(..., PAnsiChar(Path), Size);
    ...
  end;
end;
extern "C" NAVSERVER_API int  __stdcall FindNavMeshPath(const int MapID, const float StartX, const float StartY, const float StartZ, const float EndX, const float EndY, const float EndZ, char *PathBuffer, int PathBufferSize)
{
    if (pathSize > 0)
    {
        std::ostringstream oss;

        for (int i = 0;i < pathSize; i++)
        {
            oss << i;
        }
        oss << "dadsaassaasd";

        std::string tmp = oss.str();
        size_t needed = tmp.size() + 1;

        if (!PathBuffer)
            return needed;

        if (PathBufferSize < needed)
            return -1;

        std::copy_n(tmp.c_str(), needed, PathBuffer);
        return needed-1;
    }
    else
    {
        static const char *errorMsg = "Path return no points.";
        static const size_t needed = std::strlen(errorMsg) + 1;

        if (!PathBuffer)
            return needed;

        if (PathBufferSize < needed)
            return -1;

        std::copy_n(errorMsg, needed, PathBuffer);
        return needed-1;
    }
}

Upvotes: 2

Related Questions