Is it possible to *safely* return a TCHAR* from a function?

1.7k views Asked by At

I've created a function that will convert all the event notification codes to strings. Pretty simple stuff really.

I've got a bunch of consts like

const _bstr_t DIRECTSHOW_MSG_EC_ACTIVATE("A video window is being activated or deactivated.");
const _bstr_t DIRECTSHOW_MSG_EC_BUFFERING_DATA("The graph is buffering data, or has stopped buffering data.");
const _bstr_t DIRECTSHOW_MSG_EC_BUILT("Send by the Video Control when a graph has been built. Not forwarded to applications.");
.... etc....

and my function

TCHAR* GetDirectShowMessageDisplayText( int messageNumber )
{
    switch( messageNumber )
    {
        case EC_ACTIVATE: return DIRECTSHOW_MSG_EC_ACTIVATE;
        case EC_BUFFERING_DATA: return DIRECTSHOW_MSG_EC_BUFFERING_DATA;
        case EC_BUILT: return DIRECTSHOW_MSG_EC_BUILT;
... etc ...

No big deal. Took me 5 minutes to throw together.

... but I simply don't trust that I've got all the possible values, so I want to have a default to return something like "Unexpected notification code (7410)" if no matches are found.

Unfortunately, I can't think of anyway to return a valid pointer, without forcing the caller to delete the string's memory ... which is not only nasty, but also conflicts with the simplicity of the other return values.

So I can't think of any way to do this without changing the return value to a parameter where the user passes in a buffer and a string length. Which would make my function look like

BOOL GetDirectShowMessageDisplayText( int messageNumber, TCHAR* outBuffer, int bufferLength )
{
    ... etc ...

I really don't want to do that. There must be a better way.

Is there?

I'm coming back to C++ after a 10 year hiatus, so if it's something obvious, don't discount that I've overlooked it for a reason.

9

There are 9 answers

8
Matti Virkkunen On

C++? std::string. It's not going to destroy the performance on any modern computer.

However if you have some need to over-optimize this, you have three options:

  1. Go with the buffer your example has.
  2. Have the users delete the string afterwards. Many APIs like this provide their own delete function for deleting each kind of dynamically allocated return data.
  3. Return a pointer to a static buffer which you fill in with the return string on each call. This does have some drawbacks, though, in that it's not thread safe, and it can be confusing because the returned pointer's value will change the next time someone calls the function. If non-thread-safety is acceptable and you document the limitations, it should be all right though.
2
AudioBubble On

Just declare use a static string as a default result:

TCHAR* GetDirectShowMessageDisplayText( int messageNumber )
{
  switch( messageNumber )
  {
     // ...
     default:
       static TCHAR[] default_value = "This is a default result...";
       return default_value;
  }
}

You may also declare "default_value" outside of the function.

UPDATE:

If you want to insert a message number in that string then it won't be thread-safe (if you are using multiple threads). However, the solution for that problem is to use thread-specific string. Here is an example using Boost.Thread:

#include <cstdio>
#include <boost/thread/tss.hpp>

#define TCHAR char // This is just because I don't have TCHAR...

static void errorMessageCleanup (TCHAR *msg)
{
    delete []msg;
}

static boost::thread_specific_ptr<TCHAR> errorMsg (errorMessageCleanup);

static TCHAR *
formatErrorMessage (int number)
{
    static const size_t MSG_MAX_SIZE = 256;
    if (errorMsg.get () == NULL)
        errorMsg.reset (new TCHAR [MSG_MAX_SIZE]);
    snprintf (errorMsg.get (), MSG_MAX_SIZE, "Unexpected notification code (%d)", number);
    return errorMsg.get ();
}

int
main ()
{
    printf ("Message: %s\n", formatErrorMessage (1));
}

The only limitation of this solution is that returned string cannot be passed by the client to the other thread.

2
Timo Geusch On

If you are returning a point to a string constant, the caller will not have to delete the string - they'll only have to if you are new-ing the memory used by the string every time. If you're just returning a pointer to a string entry in a table of error messages, I would change the return type to TCHAR const * const and you should be OK.

Of course this will not prevent users of your code to attempt to delete the memory referenced by the pointer but there is only so much you can do to prevent abuse.

2
AshleysBrain On

Perhaps have a static string buffer you return a pointer to:

std::ostringstream ss;
ss << "Unexpected notification code (" << messageNumber << ")";
static string temp = ss.str(); // static string always has a buffer
return temp.c_str(); // return pointer to buffer

This is not thread safe, and if you persistently hold the returned pointer and call it twice with different messageNumbers, they all point to the same buffer in temp - so both pointers now point to the same message. The solution? Return a std::string from the function - that's modern C++ style, try to avoid C style pointers and buffers. (It looks like you might want to invent a tstring which would be std::string in ANSI and std::wstring in unicode, although I'd recommend just going unicode-only... do you really have any reason to support non-unicode builds?)

3
zwol On

There's no good answer here, but this kludge might suffice.

const char *GetDirectShowMessageDisplayText(int messageNumber)
{
  switch(messageNumber)
  {
     // ...
     default: {
       static char defaultMessage[] = "Unexpected notification code #4294967296";
       char *pos = defaultMessage + sizeof "Unexpected notification code #" - 1;
       snprintf(pos, sizeof "4294967296" - 1, "%u", messageNumber);
       return defaultMessage;
     }
  }
}

If you do this, callers must be aware that the string they get back from GetDirectShowMessageText might be clobbered by a subsequent call to the function. And it's not thread safe, obviously. But those might be acceptable limitations for your application.

0
Georg Fritzsche On

You already use _bstr_t, so if you can just return those directly:

_bstr_t GetDirectShowMessageDisplayText(int messageNumber);

If you need to build a different message at runtime you can pack it into a _bstr_t too. Now the ownership is clear and the use is still simple thanks to RAII.
The overhead is negligible (_bstr_t uses ref-counting) and the calling code can still use _bstr_ts conversion to wchar_t* and char* if needed.

1
Puppy On

You return some sort of self-releasing smart pointer or your own custom string class. You should follow the interface as it's defined in std::string for easiest use.

class bstr_string {
    _bstr_t contents;
public:
    bool operator==(const bstr_string& eq);
    ...
    ~bstr_string() {
        // free _bstr_t
    }
};

In C++, you never deal with raw pointers unless you have an important reason, you always use self-managing classes. Usually, Microsoft use raw pointers because they want their interfaces to be C-compatible, but if you don't care, then don't use raw pointers.

0
jalf On

The simple solution does seem to be to just return a std::string. It does imply one dynamic memory allocation, but you'd probably get that in any case (as either the user or your function would have to make the allocation explicitly)

An alternative might be to allow the user to pass in an output iterator which you write the string into. Then the user is given complete control over how and when to allocate and store the string.

1
zwol On

On the first go-round I missed that this was a C++ question rather than a plain C question. Having C++ to hand opens up another possibility: a self-managing pointer class that can be told whether or not to delete.

class MsgText : public boost::noncopyable
{
   const char* msg;
   bool shouldDelete;

public:
   MsgText(const char *msg, bool shouldDelete = false)
     : msg(msg), shouldDelete(shouldDelete)
   {}
   ~MsgText()
   {
     if (shouldDelete)
       free(msg);
   }
   operator const char*() const
   {
     return msg;
   }
};

const MsgText GetDirectShowMessageDisplayText(int messageNumber)
{
  switch(messageNumber)
  {
    case EC_ACTIVATE:
      return MsgText("A video window is being activated or deactivated.");
    // etc
    default: {
      char *msg = asprintf("Undocumented message (%u)", messageNumber);
      return MsgText(msg, true);
    }
  }
}

(I don't remember if Windows CRT has asprintf, but it's easy enough to rewrite the above on top of std::string if it doesn't.)

Note the use of boost::noncopyable, though - if you copy this kind of object you risk double frees. Unfortunately, that may cause problems with returning it from your message-pretty-printer function. I'm not sure what the right way to deal with that is, I'm not actually much of a C++ guru.