2

I am writing a library for mixed-languages and so we have to stick to a C interface. I need to call this API function:

void getproperty(const char * key, /* in  */ 
                 char * value      /* out */) {

I need to set value with the contents of an std::string.

std::string stdString = funcReturningString();
// set value to contents of stdString

I've seen lots of answers on possible strategies, but none for this specific use-case and neither were any considered the "best" or "standard." We are using C++11, for what its worth. Some of the answers vaguely recommended copying the contents of the string to a buffer and using that. But how to do it? I would like some more opinions on this conversation so I can finally pick a strategy and stand by it.

Thank you!

UPDATE: Here is the solution I used

I am using uint32_t to keep consistent with the other API functions. bufferSize is a reference because FORTRAN calls everything by reference.

void getproperty(const char * key,            /* in  */ 
                 const uint32_t & bufferSize, /* in  */
                 char * value,                /* out */
                 uint32_t & error             /* out */) 
{
  std::string stdString = funcReturningString();

  if (bufferSize < str.size() + 1) {
    // Set the error code, set an error message, and return
  }

  strcpy(value, stdString.c_str());
}

I chose strcpy() to keep everything standard and I chose a bufferSize parameter so that I wouldn't have to worry about some of problems of double indirection mentioned in the comments.

  • What's wrong with `stdString.c_str()` ? – P0W Feb 18 '14 at 05:26
  • @P0W, It doesn't return a `char *`. – chris Feb 18 '14 at 05:26
  • `strString.c_str()`will return a const char*. Maybe try [this](http://stackoverflow.com/questions/347949/convert-stdstring-to-const-char-or-char?rq=1), it's plain c++ but i don't know why you need c++11 here – demonking Feb 18 '14 at 05:27
  • It returns a const * char, producing an invalid conversion error from const * char to char *. –  Feb 18 '14 at 05:27
  • 1
    This has been asked probably a million times. Use `&str[0]`. – Rapptz Feb 18 '14 at 05:30
  • Copy the `const char*` to a `char *` using `strcpy()` function, an extra overhead though. – rendon Feb 18 '14 at 05:30
  • Does your API have a way to ask for the length of a value, without calling getproperty? – user253751 Feb 18 '14 at 05:31
  • 6
    I'm going to just assume that the name of that member, `getproperty()`, is a strong indicator that the non-constness of that pointer is no accident. If so, this is a hideously written function as no length parameter is specified to prevent buffer overruns, and the author should be flogged. – WhozCraig Feb 18 '14 at 05:33
  • Why are you accepting a `uint32_t` by reference? Types smaller than a pointer are more efficient to pass by value. – Billy ONeal Feb 18 '14 at 15:55

4 Answers4

10

It seems like the getproperty API call is flawed. There is no way for the caller to state the length of value. This means the implementation of getproperty can't safely write into value because it has no idea how long it is. The getproperty method either needs to be changed to take a char** value or to take the length of the value buffer as a parameter. Let's assume the latter is done

void getproperty(const char* key, char* value, size_t valueLength);

Now in order to copy the contents of the value returned from funcReturningString we just need to use strncpy

std::string stdString = funcReturningString();
strlcpy(value, stdString.c_str(), valueLength);

Note that this is still flawed. If valueLength == 0 or its less than the length of the string then this won't work. It'd probably be best to change getproperty to also return an error code of some sort so that failures like this can be communicated to the caller

JaredPar
  • 733,204
  • 149
  • 1,241
  • 1,454
  • 1
    +1 for pointing out `getproperty`'s flaw. – Mark Garcia Feb 18 '14 at 05:33
  • +1. Consider `strlcpy` or `strcpy_s` rather than `strncpy` to force null termination in the library function. – Billy ONeal Feb 18 '14 at 05:37
  • What would happen if we tried to write too many characters into value? In other words, the caller specifies a valueLength that is smaller than the string to be written? –  Feb 18 '14 at 05:43
  • 1
    @jakeliquorblues then it would truncate the result to the size specified. If you want to support arbitrary sizes then using a `char**` parameter is probably more appropriate. Or at least providing an error code that indicates the buffer isn't big enough so that the caller can call back with a bigger one – JaredPar Feb 18 '14 at 05:45
  • @BillyONeal ah yes, didn't realize that was a portable API call. Switched – JaredPar Feb 18 '14 at 05:45
  • To wrap up this question, can a guru explain the logic and value behind the double pointer option? I don't quite see how the double pointer solution would work. –  Feb 18 '14 at 05:50
  • 2
    @jakeliquorblues: the double indirection allows `getproperty` to allocate the necessary space instead of writing to a buffer already allocated by the caller. You'd use it like: `char *result; getproperty("name", &result);`. `getproperty` would use something like `malloc` to allocate space, write its address to `result`, then copy the data to the buffer it points at. – Jerry Coffin Feb 18 '14 at 05:55
  • Just be aware that such double indirection is dangerous to use on some platforms like Windows where you can often get in situations where allocations in one module cannot be free in another (the allocator state data is per-CRT instance. This comes up more often than you'd think. – Sean Middleditch Feb 18 '14 at 06:19
  • So strlcpy is not a standard function. Can I safely use it for Windows and Linux deployment? –  Feb 18 '14 at 06:37
  • @jakeliquorblues so frustrating. you can use `strncat` you just need to manually null terminate in case the dest buffer isn't big enough – JaredPar Feb 18 '14 at 06:39
1

A simple approach would be:

strcpy(value, stdString.c_str());

But you need to be careful of buffer overruns. Ideally the caller to this function should also give the size of the buffer it is providing to ensure you don't write past the end of it. One way of doing this is to pass an extra parameter buffer_size and to use

strncpy(value, stdString.c_str(), buffer_size);
if (buffer_size > 0) value[buffer_size-1] = 0;

Otherwise, shorten the stdString first before copying.

sj0h
  • 902
  • 4
  • 4
0

If you need to get modifiable copy of value you should allocate memory yourself, also type of value should be pointer-to-pointer.

void getproperty(const char * key, /* in  */ 
                 char ** value     /* out */) {
  if (value == NULL) {
      // ERROR
      return;
  }

  const std::string& stdString = funcReturningString();

  // if this function is called from C 
  // you need to use 'malloc' instead of 'new' operator
  *value = malloc(stdString.size() + 1); // +NUL
  if (*value == NULL) {
      // ERROR mem alloc
      return;
  }

  memcpy(*value, stdString.c_str(), stdString.size() + 1); // +NUL
}

Then you should call it like that (on C):

char* value = NULL;
getproperty("key", &value);
// use
...
// free it
free(value);
loentar
  • 5,111
  • 1
  • 24
  • 25
  • 1
    Why not `copy(stdString.begin(), stdString.end(), *value)` rather than `memcpy`? (Type safety is good) – Billy ONeal Feb 18 '14 at 05:36
  • why not, it could be used too. – loentar Feb 18 '14 at 05:38
  • If this is going to target Windows then you generally need to free in the same DLL that you allocate. This is because the linkage with the c-runtime may be with some combination of static/dynamic, single/multi-threaded. In each case it affects how the heap is managed. So you'd need a `ReleaseMemory()` function. – Peter R Feb 18 '14 at 05:45
  • Yes, you're right. Probably you should add function like `void freeproperty(char* value)`. – loentar Feb 18 '14 at 05:47
-1

If this is going to target Windows then you generally need to free in the same DLL that you allocate. This is because the linkage with the c-runtime may be with some combination of static/dynamic, single/multi-threaded. In each case it affects how the heap is managed.

It is very bad if there would be many C runtime libraries.
In principle, the One Definition Rule says there should be the only one malloc and only one free in the program... And it is good to have them :-)
memcpy may be prefered by performance reasons. Also, why use iterators and algorithm copy instead of memcpy when you really need memory to be copied? And what the "type safety" it provide?

user3277268
  • 165
  • 3
  • Well neither the C nor the C++ standard acknowledge the existence of DLLs/SOs, so the ODR can't actually be applied across them reliably… (and as well-behaving DLLs/SOs only export a limited API the ODR is normally not a problem) – MFH Feb 18 '14 at 09:39
  • Do you think it is some weird? The dlls and Sos are in use, but out of the standard. ? :-) Really they all are the object files and used to form a program which should be valid and be not ill... – user3277268 Feb 18 '14 at 23:53
  • And again, if ever standard say nothing about dlls, it still require the ODR. And as there are "no problem", no problem are above using malloc/free... Also, the behavior and use of malloc and free is defined by the particular statements of standards. I think, the DLL can have its own memory manager (do you manage the memory exclusive locally in DLLs?) :-), but the malloc/free should still be usable as necessary... – user3277268 Feb 19 '14 at 00:44
  • What's weird about it? DLLs/SOs are something that is inherently platform specific (just like the actual binary format, not to mention the assembler generated by the compiler) and can therefor not be defined in the standard. At least for DLLs your assumption of them being 'just' object files is false, as DLLs are not linked but just loaded during program execution and allow access via exported entry points/functions. Yes the standard requires ODR, but as DLLs are NOT linked there will never happen an ODR violation - DLLs and EXEs are fundamentally separated from each other… – MFH Feb 19 '14 at 08:23
  • It is ludicrous. See above about malloc/free and try to explain why they not work as required while the ODR is not broken and implementation is also not broken (Or ask Microsoft for the cause of so incorrect behavior of their implementation). DLLs are libraries and are linked. No matter are they linked or loaded, the program are required to not break the ODR, the way it really formed (from EXEs, DLLs and other ocx) doesn't play a role. – user3277268 Feb 20 '14 at 04:11
  • For example , the Windows API provides the GlobalAlloc function, which in some circumstances may be used for a basis of a custom allocator, but ... The fact of presence "Global" in the name doesn't guarrantее its unicity (if the malloc are broken, why the GlobalAlloc does not may be?...). :-) And then a properties and conventions of a such custom allocator are so far discuss... – user3277268 Feb 20 '14 at 05:20
  • *DLLs are libraries and are linked.* **this is wrong**, you may want to have a look at how Windows actually handles DLLs… DLLs are blackboxes that export a specific set of functions, everything NOT exported is hidden from the caller => no linking of symbols occurs here (this contrasts the traditional behavior used in Unix shared objects!) => no violation of ODR when the DLL and the EXE define a symbol like "malloc"… – MFH Feb 20 '14 at 10:38
  • (Do you know what means letters D L L? ) Libraries are blackholes that export a specfic set of... symbols... :-) Everything not exported (...). No violation of ODR where there are multiple definitions. Lol. – user3277268 Feb 21 '14 at 09:05
  • I'm done here, you don't know how DLLs work on Windows that's for sure… And no static libraries are not Blackboxes and you will get ODR violations with duplicated symbols… – MFH Feb 21 '14 at 20:02