Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Calling a C Function with C-style Strings

I'm struggling to call mktemp in D:

import core.sys.posix.stdlib;
import std.string: toStringz;
auto name = "alpha";
auto tmp = mktemp(name.toStringz);

but I can't figure out how to use it so DMD complains:

/home/per/Work/justd/fs.d(1042): Error: function core.sys.posix.stdlib.mktemp (char*) is not callable using argument types (immutable(char)*)

How do I create a mutable zero-terminated C-style string?

I think I've read somewhere that string literals (const or immutable) are implicitly convertible to zero (null)-terminated strings.

like image 565
Nordlöw Avatar asked Nov 30 '13 15:11

Nordlöw


2 Answers

For this specific problem:

This is because mktemp needs to write to the string. From mktemp(3):

The last six characters of template must be XXXXXX and these are replaced with a string that makes the filename unique. Since it will be modified, template must not be a string constant, but should be declared as a character array.

So what you want to do here is use a char[] instead of a string. I'd go with:

import std.stdio;

void main() {
    import core.sys.posix.stdlib;

    // we'll use a little mutable buffer defined right here
    char[255] tempBuffer;
    string name = "alphaXXXXXX"; // last six X's are required by mktemp
    tempBuffer[0 .. name.length] = name[]; // copy the name into the mutable buffer
    tempBuffer[name.length] = 0; // make sure it is zero terminated yourself

    auto tmp = mktemp(tempBuffer.ptr);
    import std.conv;
    writeln(to!string(tmp));
}

In general, creating a mutable string can be done in one of two ways: one is to .dup something, or the other is to use a stack buffer like I did above.

toStringz doesn't care if the input data is mutable, it always returns immutable (apparently...). But it is easy to do it yourself:

auto c_str = ("foo".dup ~ "\0").ptr;

That's how you do it, .dup makes a mutable copy, and appending the zero terminator yourself ensures it is there.

string name = "alphaXXXXXX"; // last six X's are required by mktemp
auto tmp = mktemp((name.dup ~ "\0").ptr);
like image 140
Adam D. Ruppe Avatar answered Nov 26 '22 10:11

Adam D. Ruppe


In addition to Adam's great answer, there's also std.utf.toUTFz, in which case you can do

void main()
{
    import core.sys.posix.stdlib;
    import std.conv, std.stdio, std.utf;

    auto name = toUTFz!(char*)("alphaXXXXXX");
    auto tmp = mktemp(name);
    writeln(to!string(tmp));
}

std.utf.toUTFz is std.string.toStringz's more capable cousin as it will generate null-terminated UTF-8, UTF-16, and UTF-32 strings (as opposed to just UTF-8) as well as any level of constness. The downside is that it's more verbose for cases where you just want immutable(char)*, because you have to specify the return type.

However, if efficiency is a concern, Adam's solution is likely better simply because it avoids having to allocate the C-string that you pass to mktemp on the heap. toUTFz is shorter though, so if you don't care about the efficiency cost of allocating the C-string on the heap (and most programs probably won't), then toUTFz is arguably better. It depends on the requirements of your particular program.

like image 32
Jonathan M Davis Avatar answered Nov 26 '22 11:11

Jonathan M Davis