digitalmars.D.learn - exern (C) linkage problem
- Charles Hixson (45/45) Jul 18 2010 I'm trying to link a C routine to a D program, passing string
- bearophile (4/5) Jul 18 2010 I think C and D char[] don't go well together. D arrays are 2-word long ...
- Charles Hixson (3/8) Jul 18 2010 Thanks, changing everything to char pointers and passing, e.g.,
- Lars T. Kyllingstad (16/71) Jul 19 2010 Since bearophile already answered with a solution to your problem, I'll
- bearophile (18/25) Jul 19 2010 The C code has to use those string pointers with lot of care.
- bearophile (17/20) Jul 19 2010 You can use just a struct too:
- Lars T. Kyllingstad (6/31) Jul 20 2010 Good point. Actually, I think there should be a CString type in Phobos,...
- bearophile (22/25) Jul 20 2010 In that code, for further safety, I'd like to make it not possible (with...
- Lars T. Kyllingstad (14/47) Jul 20 2010 Yes. I think we should stop using char* when interfacing with C code
I'm trying to link a C routine to a D program, passing string parameters, but I keep getting segmentation errors. As you can see, these are simple test routines, so the names don't reflect current status, but merely where I intend to arrive...but I've hit severe roadblocks. (FWIW, I've tried including -fpic in the gcc command, and it didn't appear to make any difference.) Makefile: biblio: biblio.d sqlitebase.o dmd biblio.d sqlitebase.o -ofbiblio sqlitebase.o: sqlitebase.c sqlitebase.h gcc -c sqlitebase.c biblio.d: import std.stdio; //extern (C) void dbdefine (char[] str); extern (C) void dbdefine (char[] inStr, ref char[255] outStr); void main() { char[255] retVal; char[] msg = cast(char[])"Hello from C\0"; dbdefine (msg, retVal); writeln ("Hello, World"); } sqlitebase.h: //void dbdefine (char str[]); void dbdefine (char inStr[], char outStr[255]); sqlitebase.c: #include "sqlitebase.h" //void dbdefine (char str[]) void dbdefine (char inStr[], char outStr[255]) { //int i = 0; //while (str[i] != 0) i++; //printStr (i, str); //^^--segmentation fault--^^ // printf ("%s/n", str); //^^--warning: incompatible implicit declaration of built-in function ‘printf’--^^ //int i = str[0]; //putchar(i); //^^--segmentation fault--^^ int i = -1; while (++i < 255) { if (inStr[i] == 0) break; outStr[i] = inStr[i]; } }
Jul 18 2010
Charles Hixson:extern (C) void dbdefine (char[] inStr, ref char[255] outStr);I think C and D char[] don't go well together. D arrays are 2-word long structs. Try again with something simpler, like a pointer and length. Bye, bearophile
Jul 18 2010
On 07/18/2010 01:56 PM, bearophile wrote:Charles Hixson:Thanks, changing everything to char pointers and passing, e.g., inStr.ptr worked.extern (C) void dbdefine (char[] inStr, ref char[255] outStr);I think C and D char[] don't go well together. D arrays are 2-word long structs. Try again with something simpler, like a pointer and length. Bye, bearophile
Jul 18 2010
On Sun, 18 Jul 2010 13:08:57 -0700, Charles Hixson wrote:I'm trying to link a C routine to a D program, passing string parameters, but I keep getting segmentation errors. As you can see, these are simple test routines, so the names don't reflect current status, but merely where I intend to arrive...but I've hit severe roadblocks. (FWIW, I've tried including -fpic in the gcc command, and it didn't appear to make any difference.) Makefile: biblio: biblio.d sqlitebase.o dmd biblio.d sqlitebase.o -ofbiblio sqlitebase.o: sqlitebase.c sqlitebase.h gcc -c sqlitebase.c biblio.d: import std.stdio; //extern (C) void dbdefine (char[] str); extern (C) void dbdefine (char[] inStr, ref char[255] outStr); void main() { char[255] retVal; char[] msg = cast(char[])"Hello from C\0"; dbdefine (msg, retVal); writeln ("Hello, World"); } sqlitebase.h: //void dbdefine (char str[]); void dbdefine (char inStr[], char outStr[255]); sqlitebase.c: #include "sqlitebase.h" //void dbdefine (char str[]) void dbdefine (char inStr[], char outStr[255]) { //int i = 0; //while (str[i] != 0) i++; //printStr (i, str); //^^--segmentation fault--^^ // printf ("%s/n", str); //^^--warning: incompatible implicit declaration of built-in function ‘printf’--^^ //int i = str[0]; //putchar(i); //^^--segmentation fault--^^ int i = -1; while (++i < 255) { if (inStr[i] == 0) break; outStr[i] = inStr[i]; } }Since bearophile already answered with a solution to your problem, I'll just chime in with a few small tips (of which you may already be aware): 1. D string *literals* are already zero-terminated, so you don't need to add the \0 character explicitly. Also, they cast implicitly to const (char)*, so if your function doesn't change inStr, it's perfectly fine to do extern(C) void dbdefine (const char* inStr); dbdefine("Hello from C"); 2. For D strings in general the \0 must be added, but this is very easy to forget. Therefore, when passing strings to C functions I always use the std.string.toStringz() function. It takes a D string, adds a \0 if necessary, and returns a pointer to the first character. string s = getAStringFromSomewhere(); dbdefine(toStringz(s)); -Lars
Jul 19 2010
Lars T. Kyllingstad:2. For D strings in general the \0 must be added, but this is very easy to forget. Therefore, when passing strings to C functions I always use the std.string.toStringz() function. It takes a D string, adds a \0 if necessary, and returns a pointer to the first character. string s = getAStringFromSomewhere(); dbdefine(toStringz(s));The C code has to use those string pointers with lot of care. The D type system can help you remember to use the toStringz, this is just an idea: import std.string: toStringz; typedef char* Cstring; extern(C) Cstring strcmp(Cstring s1, Cstring s2); Cstring toCString(T)(T[] s) { return cast(Cstring)toStringz(s); } void main() { auto s1 = "abba"; auto s2 = "red"; // auto r = strcmp(toCString(s1), s2); // compile error auto r = strcmp(toCString(s1), toCString(s2)); // OK } Unfortunately Andrei has killed the useful typedef. So you have to use a struct with alias this, that often doesn't work. Bye, bearophile
Jul 19 2010
typedef char* Cstring; extern(C) Cstring strcmp(Cstring s1, Cstring s2); ...You can use just a struct too: import std.string: toStringz; struct Cstring { const(char)* ptr; } extern(C) Cstring strcmp(Cstring s1, Cstring s2); Cstring toCString(T)(T[] s) { return Cstring(toStringz(s)); } void main() { auto s1 = "abba"; auto s2 = "red"; // auto r = strcmp(toCString(s1), s2); // compile error auto r = strcmp(toCString(s1), toCString(s2)); // OK } Bye, bearophile
Jul 19 2010
On Mon, 19 Jul 2010 18:01:25 -0400, bearophile wrote:Good point. Actually, I think there should be a CString type in Phobos, but I think it should wrap a ubyte*, not a char*. The reason for this is that D's char is supposed to be a UTF-8 code unit, whereas C's char can be anything. -Larstypedef char* Cstring; extern(C) Cstring strcmp(Cstring s1, Cstring s2); ...You can use just a struct too: import std.string: toStringz; struct Cstring { const(char)* ptr; } extern(C) Cstring strcmp(Cstring s1, Cstring s2); Cstring toCString(T)(T[] s) { return Cstring(toStringz(s)); } void main() { auto s1 = "abba"; auto s2 = "red"; // auto r = strcmp(toCString(s1), s2); // compile error auto r = strcmp(toCString(s1), toCString(s2)); // OK } Bye, bearophile
Jul 20 2010
In that code, for further safety, I'd like to make it not possible (without a cast) code like this (here toStringz doesn't get called): strcmp(Cstring(s1.ptr), Cstring(s2.ptr)); So I think this code is a bit better: import std.string: toStringz; struct Cstring { const(char)* ptr; // const(ubyte)* ? static Cstring opCall(string s) { Cstring cs; cs.ptr = toStringz(s); return cs; } } extern(C) Cstring strcmp(Cstring s1, Cstring s2); void main() { auto s1 = "abba"; auto s2 = "red"; auto r2 = strcmp(Cstring(s1), Cstring(s2)); } Lars T. Kyllingstad:but I think it should wrap a ubyte*, not a char*. The reason for this is that D's char is supposed to be a UTF-8 code unit, whereas C's char can be anything.Right. But toStringz() returns a const(char)*, so do you want to change toStringz() first? Bye, bearophile
Jul 20 2010
On Tue, 20 Jul 2010 05:10:47 -0400, bearophile wrote:In that code, for further safety, I'd like to make it not possible (without a cast) code like this (here toStringz doesn't get called): strcmp(Cstring(s1.ptr), Cstring(s2.ptr)); So I think this code is a bit better: import std.string: toStringz; struct Cstring { const(char)* ptr; // const(ubyte)* ? static Cstring opCall(string s) { Cstring cs; cs.ptr = toStringz(s); return cs; } } extern(C) Cstring strcmp(Cstring s1, Cstring s2); void main() { auto s1 = "abba"; auto s2 = "red"; auto r2 = strcmp(Cstring(s1), Cstring(s2)); } Lars T. Kyllingstad:Yes. I think we should stop using char* when interfacing with C code altogether. The "right" thing to do, if you can call it that, would be to use char* only if you KNOW the C function expects text input encoded as UTF-8 (or just plain ASCII), and ubyte* for other encodings and non- textual data. But this rule requires knowledge of what each function does with its input and must hence be applied on a case-by-case basis, which makes automated translation of C headers to D difficult. So I say make it simple, don't assume that your C functions handle UTF-8, and use ubyte* everywhere. (Actually, it's not that simple, either. I just remembered that C's char is sometimes signed, sometimes unsigned...) Maybe this should be discussed on the main NG. It's been bothering me for a while. I think I'll start a topic on it later. -Larsbut I think it should wrap a ubyte*, not a char*. The reason for this is that D's char is supposed to be a UTF-8 code unit, whereas C's char can be anything.Right. But toStringz() returns a const(char)*, so do you want to change toStringz() first?
Jul 20 2010