Jump to content
  • Advertisement
Sign in to follow this  
squirrel_of_death

returning char * across DLL == memory leak : best strategy?

This topic is 4838 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

I've been learning a lot these last days about DLL's, and the issues involved. Since I'm now converting my string & parameters to char *'s, I have come across something I had not considered. I had not thought about this before, but it turns out that std::string.c_str() allocates the char * string internally, and so if you have a function returning this c_str(), then at the end of the function, the string goes out of scope, and you return a pointer to garbage. And so, the solution would be to allocate memory, do a strcpy, and return that. However, doing this accross a DLL boundary creates a memory leak, as the caller cannot delete [] this c-string. Next up is to create a basic wrapper for a char array, which can be created and deleted dynamically, passed & returned from a function, where the destructor deletes the char array, when the user wants to, and hence solves the memory leak. But, I've not read this approach anywhere, and wonder if I'm taking things too far, there's something simpler I've missed. Thanks again guys. [Edited by - squirrel_of_death on May 2, 2006 8:13:08 PM]

Share this post


Link to post
Share on other sites
Advertisement
Quote:
Next up is to create a basic wrapper for a char array, which can be created and deleted dynamically, passed & returned from a function, where the destructor deletes the char array, when the user wants to, and hence solves the memory leak.


You allready mentioned one of these earlier in your post. It's called std::string. Why not return that from the function?

There can be some memory management issues that arise from this if you're using different versions of the CRT (because this also causes seperate heaps). To avoid this problem, make sure you're building both the EXE and DLL with the same runtime (Single or Multithreaded, Debug or Non).

Since I've honestly never run into this issue (what few "DLL"s I've made were linux .so files, which I guess allways link to the same runtime or something) you could read This thread titled "Passing std::string over DLL boundaries".

Share this post


Link to post
Share on other sites
You can just have the receiver of the const char * internally allocate a std::string to manage the string. It's only the interface where you have to worry about allocation issues not the implementation.

Share this post


Link to post
Share on other sites
*edit : to monkey * the problem with std::string ( as far as I understand ) is due to memory management, and in the thread you mentioned, the general advice is to avoid STL over DLL boundaries for this reason. I don't want to be dependant on runtime libraries, or compilers for future usage of my work, so I'm working these things out right now.

For example, if I make a little single threaded test.exe, I would like to be able to use my DLL in it. As I've said, I am new to this area, so any helpful comments will be welcome.

[Edited by - squirrel_of_death on May 2, 2006 8:16:16 PM]

Share this post


Link to post
Share on other sites
Quote:
Original post by SiCrane
You can just have the receiver of the const char * internally allocate a std::string to manage the string. It's only the interface where you have to worry about allocation issues not the implementation.


could you please elaborate a little? If you mean the DLL internally allocate a string, yes I'm already doing that for the DLL string processing. I am talking about the return journey to the caller, where I would like to return a c-string. If I allocate this c-string inside the DLL, the caller cannot delete it. If I return str.c_str(), then the caller will recieve a char * to garbage, as str has gone out of scope.

Share this post


Link to post
Share on other sites
Sorry, I thought you were concerned about passing strings to the DLL not returning them. C DLLs often use one of two methods, with the second being more common:

One way is to use a call back function. The client provides a function pointer that gets called while the string is in scope. Then the client can copy the string.

Another way is the the client allocates a buffer and gives the DLL a char * and a max length parameter that the DLL can fill.

Another option is passing structures that include a char array with a size equal to a maximum valid string length, like the Windows MAX_PATH define.

Share this post


Link to post
Share on other sites
Quote:
Original post by squirrel_of_death
the problem with std::string ( as far as I understand ) is due to memory management, and in the thread you mentioned, the general advice is to avoid STL over DLL boundaries for this reason. I don't want to be dependant on runtime libraries, or compilers for future usage of my work, so I'm working these things out right now.

For example, if I make a little single threaded test.exe, I would like to be able to use my DLL in it. As I've said, I am new to this area, so any helpful comments will be welcome.


Mkay, looks like you want a smart pointer then. A funky smart pointer.

Assuming the DLL exports these functions:

char * CreateName( ... );
void FreeName( char * );

Where FreeName cleans up the return of CreateName, we could write a class:

class NameHandle {
char * data;
public:
NameHandle( ... ) {
data = CreateName( ... );
}
~NameHandle( ... ) {
FreeName( data );
}
char * GetData() const {
return data;
}
};



Normally I would use boost::shared_ptr/shared_array, but these allocate memory (for reference counting) and as such wouldn't play well accross DLL boundries with mismatched runtimes either.

Share this post


Link to post
Share on other sites
My usual approach is to provide a client side wrapper that takes care of all the interfacing stuff. Here's a trivial example with a class Representing a single line address:
// base classes
template < typedef MEMORY_TYPE, typedef DELETE_FUNCTION >
struct OwnedMemory
{
MEMORY_TYPE value;
DELETE_FUNCTION deleteFunction;
};

class SingleLineAddressBase
{

public:

virtual ~SingleLineAddress(){}
virtual OwnedMemory< char const *, void (* __stdcall)(char const *) > address() const = 0;
virtual unsigned int houseNumber() const = 0;
virtual char const * roadName() const = 0;

};

// dll
template < typename MEMORY_TYPE >
void __stdcall Free(MEMORY_TYPE const * memory)
{
free(memory);
}

class SingleLineAddressImpl
:
public SingleLineAddressBase
{

public:

SingleLineAddressImpl(unsigned int houseNumber, std::string roadName);
virtual OwnedMemory< char const *, void (* __stdcall)(char const *) > address() const;
virtual unsigned int houseNumber() const;
virtual char const * roadName() const;

private:

unsigned int houseNumber_;
std::string roadName_;

};

SingleLineAddressImpl::SingleLineAddressImpl(unsigned int houseNumber, std::string roadName)
{
}

virtual OwnedMemory< char const *, void (* __stdcall)(char const *) > SingleLineAddressImpl::address() const
{
std::string address = boost::lexical_cast< std::string >(houseNumber_) + ' ' + roadName_;
// return a chunk of dll-allocated memory, plus a dll-resident function to free it
OwnedMemory returnValue;
returnValue.value = strdup(address.c_str());
returnValue.deleteFunction = &Free< char >;
return returnValue;
}

virtual unsigned int SingleLineAddressImpl::houseNumber() const
{
// fine, builtin types don't need any special treatment
return houseNumber_;
}

virtual char const * SingleLineAddressImpl::roadName() const
{
// just return the string allocated pointer. The interface must copy this value immediately
return roadName_.c_str();
}

extern "C"
{
__declspec(dllexport) SingleLineAddress * __stdcall createSingleLineAddress(unsigned int houseNumber, char const * roadName)
{
return new SingleLineAddressImpl(houseNumber, roadName);
}
__declspec(dllexport) void __stdcall deleteSingleLineAddress(SingleLineAddress * singleLineAddress)
{
delete singleLineAddress;
}
}

// client:
class SingleLineAddress
{

public:

SingleLineAddress(unsigned int houseNumber, std::string roadName);
~SingleLineAddress();
std::string address() const;
unsigned int houseNumber() const;
std::string roadName() const;

private:

SingleLineAddressBase * implementation_;

static SingleLineAddressBase * createSingleLineAddress(unsigned int houseNumber, std::string roadName);
static void deleteSingleLineAddress(SingleLineAddressBase * singleLineAddress);

};

SingleLineAddress::SingleLineAddress(unsigned int houseNumber, std::string roadName)
:
implementation_(createSingleLineAddress(houseNumber, roadName))
{
}

SingleLineAddress::~SingleLineAddress()
{
deleteSingleLineAddress(implementation_);
}

std::string SingleLineAddress::address() const
{
// do the actual work
OwnedMemory< char const *, void (* __stdcall)(char const *) > address = implementation_->address();
// copy the return value into our own allocated memory
std::string returnAddress(address.value);
// free the dll allocated memory using the dll-resident function
address.deleteFunction(address.value);
return returnAddress;
}

unsigned int SingleLineAddress::houseNumber() const
{
// fine, builtin types don't need any special treatment
return implementation_->houseNumber();
}

std::string SingleLineAddress::roadName() const
{
// construct a new string from the given value
// we own the newly allocated memory
// the memory that was passed to us is still owned by the dll and
// will be cleaned up by the string class
return std::string(implementation_->roadName());
}

SingleLineAddressBase * SingleLineAddress::createSingleLineAddress(unsigned int houseNumber, std::string roadName)
{
typedef SingleLineAddressBase * (* __stdcall CreateFunction)(unsigned int, char const *);
static CreateFunction create = Dll::getFunction< CreateFunction >("createSingleLineAddress");
return create(houseNumber, roadName.c_str());
}

void SingleLineAddress::deleteSingleLineAddress(SingleLineAddressBase * singleLineAddress)
{
typedef void (* __stdcall DestroyFunction)(SingleLineAddressBase *);
static DestroyFunction destroy = Dll::getFunction< DestroyFunction >("deleteSingleLineAddress");
destroy(singleLineAddress);
}
*has not been passed through a compiler

There may be a few gremlins in there that can bite you, it's been a while since I've done this stuff and I'm working from memory.

Inside the Dll you can deal with SingleLineAddressImpl objects directly without worrying about the fact that it's a class used by both a dll and an executable. Inside the executable you make use of SingleLineAddress as if it were any normal class and it handles all interfacing with the dll transparently. Except for construction and deletion any time data is passed across the boundary a pointer to source-side memory is passed and immediately copied destination-side. If the memory was specially allocated it is passed along with a pointer to a function to delete it.

Enigma

Share this post


Link to post
Share on other sites
Enigma, your code is pretty amazing, thanks for taking the time! Your ideas make a lot of sense, and it is surprising that beneath the syntax, the ideas are also quite straightforward. Very cool.

cheers!

[Edited by - squirrel_of_death on May 2, 2006 8:00:41 PM]

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!