Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Obtaining a plain char* from a string in D?

Tags:

string

d

I'm having an absolute hell of a time trying to figure out how to get a plain, mutable C string (a char*) from a D string (a immutable(char)[]) to that I can pass the character data to legacy C code. toStringz doesn't work, as I get an error saying that I "cannot implicitly convert expression (toStringz(this.fileName())) of type immutable(char)* to char*". Do I need to recreate a new, mutable array of char and copy the characters over?

like image 246
Mark LeMoine Avatar asked Jun 18 '11 04:06

Mark LeMoine


3 Answers

If you can change the header of the D interface of that legacy C code, and you are sure that legacy C code will not modify the string, you could make it accept a const(char)*, e.g.

char* strncpy(char* dest, const(char)* src, size_t count);
//                        ^^^^^^^^^^^^
like image 89
kennytm Avatar answered Nov 16 '22 02:11

kennytm


Yeah, it's not pretty, because the result is immutable.

This is why I always return a mutable copy of new arrays in my code. There's no point in making them immutable.

Solutions:

You could just do

char[] buffer = (myString ~ '\0').dup; //Concatenate a null terminator, then dup

then use buffer.ptr as the pointer.

However:

This wastes a string. A better approach might be:

char[] buffer = myString.dup;
buffer ~= '\0'; //Hopefully this doesn't reallocate

and using buffer.ptr afterwards.


Another solution is to use a method like this one:

char* toStringz(in char[] s)
{
    string result;
    if (s.length > 0 && s[$ - 1] == '\0') //Is it null-terminated?
    { result = s.dup; }
    else { result = new char[s.length + 1]; result[0 .. s.length][] = s[]; }
    return result.ptr;
}

This one is the most efficient but also the longest.

(Edit: Whoops, I had a typo in the if; fixed it.)

like image 30
user541686 Avatar answered Nov 16 '22 02:11

user541686


If you want to pass a mutable char* to a C function, you're going to need to allocate a mutable char[]. string isn't going to work, because it's immutable(char)[]. You can't alter immutable variables, so there is no way to pass a string to a function (C or otherwise) which needs to alter its elements.

So, if you have a string, and you need to pass it to a function which takes a char[], then you can use to!(char[]) or dup and get a mutable copy of it. In addition, if you want to pass it to a C function, you're going to need to append a '\0' to it so that it's zero-terminated. The easiest way to do that is just to do ~= '\0' on the char[], but the more efficient way would probably be to do something like this:

auto cstring = new char[](str.length + 1);
cstring[0 .. str.length] = str[];
cstring[$ - 1] = '\0';

In either case, you then pass cstring.ptr to the C function that you're calling.

If you know that the C function that you're calling isn't going to alter the string, then you can either do as KennyTM suggests and alter the C function's signature in D to take a const(char)*, or you can cast the string. e.g.

auto cstring = toStringz(str);
cfunc(cast(char*)cstring.ptr);

Altering the C function's signature would be more correct and less error-prone though.

It sounds like we may be altering std.conv.to to be smart enough to turn strings into zero-terminated strings when cast to char*, const(char)*, etc. So, once that's done, getting a zero-terminated mutable string should be easier, but for the moment, you pretty much just need to copy the string and append a '\0' to it so that it's zero-terminated. But regardless, you're never going to be able to pass a string to a C function which needs to modify it, because a string can't be mutated.

like image 30
Jonathan M Davis Avatar answered Nov 16 '22 02:11

Jonathan M Davis