views:

82

answers:

5

Hi,

How can I convert std::string to LPCSTR while preserving '\0' characters?

I want to use the result on OPENFILENAME.lpstrFilter which requires the filter to contain '\0' as delimiters.

std::string.c_str() seems to strip and trim '\0'

Thanks in advance!

==========================

(How do I properly add a comment to the responses like a reply post in a forum?)

After reading your comments, I went on to double check this snippet:

std::string filter = "Terrain Configuration (*.tcfg)\0*.tcfg\0\0";
const char* f1 = "Terrain Configuration (*.tcfg)\0*.tcfg\0\0";
const char* f2 = filter.c_str();
for(int i = 0; i < 50; i++)
{
 char c1 = *(f1 + i); // works
 char c2 = *(f2 + i); // doesn't work. beyond the first \0, it's garbage.
}

Am I mistaken on how c_str() or LPCSTR works?

A: 

It depends on further life-cycle of std::string.

For example you can append terminal zero manually:

std::string test("init");
test += '\0';

This causes length to be increased by one. Or just create another instance of std::string with zero appended

Dewfy
+6  A: 

C_STR doesn't strip the NUL characters. The error is likely in the way you are constructing the STD::string.

Alexander Rafferty
c_str does NOT strip the null, but be cautious, it returns a pointer which is still owned by the string class, so you must make sure to copy the contents
Armen Tsirunyan
Please see edited question with the snippet. It's in the same scope, isn't it?
Jake
Regardless, Alexander's point is that in your snippet the filter std::string is not being initialised with your full input because the constructor will stop reading at the first nul. As per Steve's answer below you need to use `std::string filter("Terrain Configuration (*.tcfg)\0*.tcfg\0\0", 40);` I think.
Rup
ah... i see. Thanks for point that out.
Jake
@Rup, @Jake - see latest edit for that option
Steve Townsend
+2  A: 

The length() of your string is what you need to use to ensure all characters are included, provided it was properly constructed in the first place to include the embedded \0 characters.

For an LPCSTR valid while cString is in scope:

#include <boost/scoped_array.hpp>
#include <string>
#include <algorithm>

string source("mystring");
boost::scoped_array<char> cString(new char[source.length() + 1]);

std::copy(source.begin(), source.end(), cString.get());
cString[source.length()] = 0;

LPCSTR rawStr = cString.get();

Without scoped_array:

#include <string>
#include <algorithm>

string source("mystring");
LPSTR rawStr = new char[source.length() + 1];

std::copy(source.begin(), source.end(), rawStr);
rawStr[source.length()] = 0;

// do the work here

delete[] rawStr;

EDIT: In your filter init, the constructor of string only includes data up to the first \0 char. This makes sense - how else would it not to stop copying data when all it has in hand is const char *? Try this with:

const char f3[] = "Terrain Configuration (*.tcfg)\0*.tcfg\0\0";
std::string filter(f3, sizeof(f3));
Steve Townsend
hmm... I could copy one char at a time but just hoping there's a faster way.
Jake
@Jake - `std::copy` removes the need for a bye-by-byte copy loop. If you remove the `scoped_array` above it might be clearer, just new directly into `rawStr`. See edit.
Steve Townsend
A: 

You should check out this link which details how to convert all the different Microsoft string from one to another. There are also ATL and MFC macros which handle string conversion too. See here.

wheaties
He's not converting to/from Unicode, though, just from a std::string to a const char*.
Rup
+5  A: 

Generally speaking, std::string handles embedded '\0' characters correctly.

However, when interfacing with const char*, extreme care must be taken.

In this case, the problem already occurs during constructing the string with:

std::string filter = "Terrain Configuration (*.tcfg)\0*.tcfg\0\0"; // wrong

Here you construct a std::string from a const char*. As the constructor doesn't know how many characters to use for constructing the std::string, it stops at the first '\0'.

One could copy the characters one by one oneself, but there is a better way: use the contructor that requires two iterators to the char array, one with the begin and one with the end:

const char filter_[] = "Terrain Configuration (*.tcfg)\0*.tcfg\0\0"; 
std::string filter(filter_, filter_ + sizeof(filter_));

It looks horrible, but hopefully you won't do this that frequently in your code.

Update: Or with appropiate definitions of begin() and end() templates (as per James Kanze's comment):

const char filter_[] = "Terrain Configuration (*.tcfg)\0*.tcfg\0\0"; 
std::string filter(begin(filter_), end(filter_));
Sjoerd
That's sizeof(filter_) - 1 if the string constant already has the
James Kanze
Sorry, not used to this interface:That's sizeof(filter_) - 1 if the string constant alreadycontains the double \0. Initializing an array with a stringconstant automatically appends a '\0'.And of course, this use of sizeof will break when the char ischanged to wchar_t. Better would be to use the usual templatefunctions begin(filter_), end(filter_), e.g.: std::string( begin( filter_ ), end( filter_ ) );
James Kanze
@James Kanze: Thanks for pointing at the begin() and end() templates, I didn't know them yet.
Sjoerd
@James Kanze: wrt the -1, I considered it harmless to have three '\0' at the end, and didn't want to complicate the answer too much. My choice would have been to omit the second '\0' at the end of the string constant, with a comment that the compiler will add a second one.
Sjoerd