问题
It's hard for me to describe the problem precisely. Let me show the code below:
I have a function in C:
int foo(char *deviceID){
char a[]="abc";
deviceID=a;
return 1;
}
Obviously I pass the char*deviceID arguments to be changed in this function, just ignore the return integer, what I want is to get the value of deviceID;
In Go:
func getID() string{
var b []byte
C.foo((*C.char)(unsafe.Pointer(&b)))
return string(b)
}
But it seems I got nothing. Can anyone help me find out the problem?
回答1:
Your C code is wrong
As GSerg noted in a comment and shown in Thanh Do's answer, your C code is incorrect (will generally compile and run, but has no useful effect).
Writing correct C code can be difficult enough on its own; connecting it to the Go runtime can be even more difficult. Your existing C code has a single return value, but you would probably like for it to return two values: an integer, and a pointer-to-char that points to the first of a series of char
values ending with a '\0'
byte, i.e., a C string. Strings in C are, however, notoriously tricky.
Your local variable a
holds a suitable string:
char a[]="abc";
Here a
has type array 4 of char
or char[4]
(in C, that is—in Go the closest match would be [4]byte
) but the lifetime of this variable is only until the end of the function itself. Specifically, the array a
has block scope and automatic duration.1 This means that the four bytes holding 'a', 'b', 'c', '\0'
evanesce as soon as the function itself returns.
There are many different ways to cure this problem in your C code. Which one is suitable depends on the real problem: in this toy example in which you've reproduced the problem, the easiest way would be to give the vraiable a
static duration. The result would look like this:
int foo(char **deviceID) {
static char a[] = "abc";
*deviceID = a;
return 1;
}
Note that this function now takes a pointer to a pointer. From more C code, it might be called like this:
char *name;
ret ok;
ret = foo(&name);
The caller's pointer, name
, of type char *
, has been filled in—overwritten—with an appropriate value of type char *
, i.e., pointer-to-char, pointing to the valid string, whose lifetime is that of the entire program ("static duration").
Another method—the one illustrated in Thanh Do's answer, is to use strcpy
. Doing this is dangerous because it is now up to the caller to allocate enough storage to hold the entire string. It's now necessary to pick some maximum length for foo
to fill in (or add a parameter to alleviate the danger, as we'll do in a moment). Our calling C code fragment might now read:
char buf[4];
ret ok;
ret = foo(buf);
but you should ask: How do we know to make buf
have room for four bytes? The answer is either "we don't"—we just got lucky by making it big enough—or "because we can see that function foo
always writes exactly four bytes". But if we use the second answer, well, we can see that function foo
always writes the four bytes 'a', 'b', 'c', '\0'
(and then always returns 1). So why did we bother calling function foo
at all? We could just write:
char buf[4] = "foo";
int ret = 1;
and leave out the function call entirely. So a real world example would, perhaps, take two arguments: a pointer to a buffer to be filled in and the size of that buffer in bytes. If we need more room than is available, in our foo
function, we would return failure—which the caller must now pay attention to—or truncate the name, or perhaps both:
int foo(char *buffer, size_t len) {
char a[] = "abc";
if (strlen(a) + 1 > len) {
return 0; /* failure */
}
strcpy(buffer, a);
return 1; /* success */
}
Function foo
now depends on its caller to send both values correctly, but at least it can be used correctly yet safely in general—unlike the earlier version of foo
.
1These terms are specific to the C language. While Go declarations also have scope, the term winds up being used a bit differently because the underlying storage in Go is garbage-collected. The duration of some data in C can be tied to a variable's scope in a way that simply does not happen in Go.
Your Go code is wrong
No matter what you do with your C code, your existing Go code has a problem as well:
func getID() string {
var b []byte
C.foo((*C.char)(unsafe.Pointer(&b)))
return string(b)
}
The type of b
here is []byte
or "slice of byte". It has an initial value of zero (converted to slice-of-byte type).
A slice value in Go is actually a three-element group, a sort of struct
type if you will. See reflect.SliceHeader for additional details. The construct &b
produces a pointer to this slice header.
Depending on how you fix your foo
function—whether it uses strcpy
, or sets a value of type char *
by taking a value of type char **
, or even uses malloc
as in Allen ZHU's answer—you definitely do not want to pass &b
to C code. Wrapping &b
with unsafe.Pointer
merely disguises this mistake.
Let's say instead that you choose to have C.foo
take char *
and size_t
. Then what we want to do is pass, to the C function:
- a value of type
*C.char
, pointing to the first of n >= 4C.char
s; - the number of
C.char
s that can be overwritten including the terminating `\0'.
We should save the return value and make sure that it is the magic constant 1 (it would be good to get rid of the magic constants, too, but you can do that later).
Here's our modified getID
function:
func getID() string {
b := make([]C.char, 4)
ret := C.foo(&b[0], C.size_t(len(b)))
if ret != 1 {
panic(fmt.Sprintf("C.foo failed (ret=%d)", int(ret)))
}
return C.GoString(&b[0])
}
I put a complete sample program into the Go Playground, but one is not allowed to build the C parts there. (I built and ran it on another system.)
回答2:
char *deviceID => This is the pointer that points to the first character of the array of the calling (client code). We can say the deviceID stores the address of the starting address of the array of the calling (client code). Assume that array is B. So it stores the address of B[0]
deviceID=a => deviceID stores the starting address of [a] array.
When the program returns, deviceID points to the first character. We can say the deviceID stores the address of the starting address of B[0] again.
There is no change.
In C, we can use the strcpy to copy the content to the address.
int foo(char *deviceID){
char a[]="abc";
strcpy(deviceID, a);
return 1;
}
OR
int foo(char *deviceID){
char a[]="abc";
for (int i = 0; i < 4; i++){
a[i] = a[i]; //note: copy the last character such as a[3] = '\0';
}
return 1;
}
来源:https://stackoverflow.com/questions/65446252/how-to-get-string-from-argumentchar-passed-to-c-function