英文:
CGO C# string array to GO slice
问题
我正在使用CGO从GO代码编译一个C库,然后从C#中调用该库的函数。
在GO代码中,我有一个函数,它期望一个**[]string**输入,例如:
func StringArray(strings []string)
我还有另一个函数,它期望一个**[]int**输入,例如:
func IntArray(vals []int)
如果我查看生成的头文件,我可以看到上述函数的如下定义:
extern __declspec(dllexport) void IntArray(GoSlice vals);
extern __declspec(dllexport) void StringArray(GoSlice strings);
我可以成功地从C#中调用IntArray
函数,通过创建以下结构体:
internal struct GoSlice
{
public IntPtr data;
public long len, cap;
public GoSlice(IntPtr data, long len, long cap)
{
this.data = data;
this.len = len;
this.cap = cap;
}
}
然后像这样调用函数:
long[] data = { 1, 2, 3, 4, 5, 6 };
IntPtr data_ptr = Marshal.AllocHGlobal(Buffer.ByteLength(data));
Marshal.Copy(data, 0, data_ptr, data.Length);
var nums = new GoSlice(data_ptr, data.Length, data.Length);
IntArray(nums);
Marshal.Copy(nums.data, data, 0, data.Length);
我还可以成功地调用期望string输入的函数,通过创建以下结构体:
internal struct GoString
{
public string msg;
public long len;
public GoString(string msg, long len)
{
this.msg = msg;
this.len = len;
}
}
然后像这样调用函数:
string inputString = "Test";
GoString goString = new GoString(inputString, inputString.Length);
StringInput(goString);
我在尝试将期望的[]string GoSlice传递给StringArray
函数时遇到了困难。有什么建议吗?我需要GoSlice包含字符串而不是整数。
我已经尝试了各种方式,将字符串传递给GoSlice,而不是整数,但结果不尽相同。我期望得到一个[]string GoSlice,可以在从C#调用"CGO编译"的GO函数时使用。
英文:
I'm compiling a C library from GO code, using CGO. The libraries functions are then called from C#.
In this GO code I have a function that expects a []string input, such as:
func StringArray(strings []string)
I also have another function that expects an []int input, such as:
func IntArray(vals []int)
If I look at the generated header file, I can see the following for the above functions:
extern __declspec(dllexport) void IntArray(GoSlice vals);
extern __declspec(dllexport) void StringArray(GoSlice strings);
I can successfully call the IntArray
function from C#, by creating the following struct:
internal struct GoSlice
{
public IntPtr data;
public long len, cap;
public GoSlice(IntPtr data, long len, long cap)
{
this.data = data;
this.len = len;
this.cap = cap;
}
}
And then call the function like so:
long[] data = { 1, 2, 3, 4, 5, 6 };
IntPtr data_ptr = Marshal.AllocHGlobal(Buffer.ByteLength(data));
Marshal.Copy(data, 0, data_ptr, data.Length);
var nums = new GoSlice(data_ptr, data.Length, data.Length);
IntArray(nums);
Marshal.Copy(nums.data, data, 0, data.Length);
I can also successfully call functions expecting a string input, by creating the following struct:
internal struct GoString
{
public string msg;
public long len;
public GoString(string msg, long len)
{
this.msg = msg;
this.len = len;
}
}
And then just call the function like so:
string inputString = "Test";
GoString goString = new GoString(inputString, inputString.Length);
StringInput(goString);
What I struggle to achieve, is to pass the expected []string GoSlice to the StringArray
function. Any suggestions? I need the GoSlice to include strings and not integers.
I've tried, in various ways, to pass strings to the GoSlice instead of integers which didn't work with mixed results. I expected to end up with a []string GoSlice which could be used when calling the "CGO compiled" GO function from C#.
答案1
得分: 1
我有点困惑,你在问题中提到的C#
和Go
类型是指什么,但我认为我仍然可以为你解答。
首先,cgo
创建了一个C接口,所以你的问题可以简化为:
- 如何从C#调用一个接受数组的C函数
- 如何在Go中接受一个来自cgo接口的C数组
看起来你对前者有很好的理解,所以我将重点关注后者。
对于具有动态结构的C函数,我们需要知道内存的大小和布局。因此,一个接受字符串数组(包含与String Slice相同类型的数据)的函数在C中可能如下所示:
int Parse22Strings(int argc, char** argv){
if(argc!=2){
return -1;
}
printf("string #1 %s string #2\n",argv[0],argv[1]);
}
好的,如果我们想要在Go中使用相同的接口,我们只需要在cgo中匹配它(参考这个答案):
func Parse22Strings(argc C.int, argv **C.char) {
length := int(argc)
tmpslice := (*[1 << 30]*C.char)(unsafe.Pointer(argv))[:length:length]
gostrings := make([]string, length)
for i, s := range tmpslice {
gostrings[i] = C.GoString(s)
}
fmt.Printf("string #1 %s string #2\n",gostrings[0],gostrings [1]);
}
所以在这一点上,你可以将上面的函数视为int Parse22Strings(int argc, char** argv)
,因为它将以这种方式被调用。如果你需要返回一个Slice,你只需要再次将它转换为C类型:
struct GoSliceSimple{
int argc;
char ** argv;
};
func Parse22Strings(argc C.int, argv **C.char) C.struct_GoSliceSimple {
//确保使用malloc,这样Go的垃圾回收器就不会销毁任何返回的字符串
}
你可以使用更复杂的不透明类型,但通常在C结构中序列化数据并来回传递是最好的方法。
英文:
I am a little confused about when you are referring to C#
or Go
types in your questions, but I think i can still clear this up for you.
Firstly, cgo
creates a C interface so your problem is simplified to:
- How can I call a C function that takes an array from C#
- How can I accept a C array from a cgo interface in GO
Seems like you have a good grasp on the former, so i am going to focus on the later.
For C functions with dynamic structures, we need to know how much memory and the layout of it. So a function that accepts an array of strings (containing the same type of data as a String Slice) could look like the following in C
int Parse22Strings(int argc, char** argv){
if(argc!=2){
return -1;
}
printf("string #1 %s string #2\n",argv[0],argv[1]);
}
Ok so if we want the same interface using Go, we just have to match it in cgo (taken form this other answer):
func Parse22Strings(argc C.int, argv **C.char) {
length := int(argc)
tmpslice := (*[1 << 30]*C.char)(unsafe.Pointer(argv))[:length:length]
gostrings := make([]string, length)
for i, s := range tmpslice {
gostrings[i] = C.GoString(s)
}
fmt.Printf("string #1 %s string #2\n",gostrings[0],gostrings [1]);
}
So at this point you can just treat the function above as int Parse22Strings(int argc, char** argv)
because that is how it will be called. If you need to return a Slice you just need to again transform it to a C type:
struct GoSliceSimple{
int argc;
char ** argv;
};
func Parse22Strings(argc C.int, argv **C.char) C.struct_GoSliceSimple {
//be sure to use malloc so the Go garbage collector does not destroy any returned string
}
You can use can more complicated with opaque types, but generally just serializing the data in C structs and passing it back and forth is the best way to go.
答案2
得分: 0
感谢Liam的输入,我成功提出了以下解决方案。
导出的GO函数:
//export Parse22Strings
func Parse22Strings(argv **C.char, argc C.int) {
length := int(argc)
tmpslice := unsafe.Slice(argv, length)
gostrings := make([]string, length)
for i, s := range tmpslice {
gostrings[i] = C.GoString(s)
}
}
由于我使用的是GO的1.20.1版本,我实际上进行了更改:
tmpslice := unsafe.Slice(argv, length)
根据CGO维基上的文档:https://github.com/golang/go/wiki/cgo#turning-c-arrays-into-go-slices
C#平台调用(P/Invoke):
[DllImport("shared.dll", CallingConvention = CallingConvention.Cdecl)]
internal static extern void Parse22Strings(string[] argv, int argc);
C#库调用:
string[] strArray = {"one", "two", "three", "four", "five", "six", "seven", "..."};
Parse22Strings(strArray, strArray.Length);
英文:
Thanks to Liams input, I managed to come up with the following solution.
Exported GO function:
//export Parse22Strings
func Parse22Strings(argv **C.char, argc C.int) {
length := int(argc)
tmpslice := unsafe.Slice(argv, length)
gostrings := make([]string, length)
for i, s := range tmpslice {
gostrings[i] = C.GoString(s)
}
}
Since I'm using version 1.20.1 of GO, I actually changed:
tmpslice := (*[1 << 30]*C.char)(unsafe.Pointer(argv))[:length:length]
To:
tmpslice := unsafe.Slice(argv, length)
As per the documentation from the CGO wiki here: https://github.com/golang/go/wiki/cgo#turning-c-arrays-into-go-slices
C# Platform Invoke (P/Invoke):
[DllImport("shared.dll", CallingConvention = CallingConvention.Cdecl)]
internal static extern void Parse22Strings(string[] argv, int argc);
C# library call:
string[] strArray = {"one", "two", "three", "four", "five", "six", "seven", "..."};
Parse22Strings(strArray, strArray.Length);
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论