【发布时间】:2020-11-14 12:33:15
【问题描述】:
我正在尝试从 C# 应用程序 P/Invoke 到 C 库中,通过包含字符串数组的结构发送。我确实可以控制 C 库,并且可以根据需要进行更改。
这是一条单行道:从 C# 到 C,我不需要观察对 C 端的结构所做的修改(我也通过值而不是通过引用传递它,尽管我可能会改变稍后 - 首先尝试解决眼前的问题)。
我的 C 结构现在看起来像这样:
// C
struct MyArgs {
int32_t someArg;
char** filesToProcess;
int32_t filesToProcessLength;
};
在 C# 中,我复制了这样的结构:
// C#
public struct MyArgs
{
public int someArg;
[MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.LPStr)]
public string[] filesToProcess;
public int filesToProcessLength;
}
然后将其传递给库:
// C#
[DllImport("myLib.so", EntryPoint = "myFunction", CallingConvention = CallingConvention.Cdecl)]
internal static extern bool myFunction(MyArgs args);
var myArgs = new MyArgs {
someArg = 10,
filesToProcess = new string[] { "one", "two", "three" }
};
myArgs.filesToProcessLength = myArgs.filesToProcess.Length;
Console.WriteLine(myFunction(myArgs));
我想在哪里消费它:
// C
bool myFunction(struct MyArgs args) {
printf("Files to Process: %i\n", args.filesToProcessLength);
for (int i = 0; i < args.filesToProcessLength; i++) {
char* str = args.filesToProcess[i];
printf("\t%i. %s\n", i, str);
}
return true;
}
这基本上会使应用程序崩溃。我得到一个输出,上面写着Files to Process: 3,但随后应用程序就停止了。如果我将 for 循环更改为不尝试访问字符串,它会通过循环计数 - 所以我似乎遇到了某种访问冲突。
如果我更改我的代码以接受一个数组作为函数参数的一部分,它会起作用:
// C
bool myFunction(struct MyArgs args, char** filesToProcess, int32_t filesToProcesLength) {
printf("Files to Process: %i\n", filesToProcessLength);
for (int i = 0; i < filesToProcessLength; i++) {
char* str = filesToProcess[i];
printf("\t%i. %s\n", i, str);
}
return true;
}
// C#
[DllImport("myLib.so", EntryPoint = "myFunction", CallingConvention = CallingConvention.Cdecl)]
internal static extern bool myFunction(MyArgs args, [MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.LPStr)] filesToProcess, int filesToProcessLength);
我最初的想法是,因为在一个结构中,我使用 ByValArray,它可能是一个指向字符串数组的指针(所以本质上是一个 char***?),但即使我将类型更改为 char*** 在结构并执行char** strArray = *args.filesToProcess,我得到相同的结果/非工作崩溃。
由于我主要是一名具有一定 C 知识的 C# 开发人员,所以在这里我有点不知所措。将字符串集合 P/Invoke 到结构内的 C 库中的最佳方法是什么?如前所述,我可以随心所欲地更改 C 库,只是更喜欢将其保留在结构中而不是添加函数参数。
如果重要的话,这是在 Linux 上,使用 gcc 9.3.0,这只是普通的 C,而不是 C++。
更新:
- sizeof(args) 为 24
- 获取地址:
- &args = ...560
- &args.someArg = ...560
- &args.filesToProcess = ...568
- &args.filesToProcessLength = ...576
- 所以 args.filesToProcess 是指向某物的单个指针 - 将尝试挖掘以查看它指向的内容
更新 2: 查看使用 this code 进行的内存转储,似乎 C# 端没有以我想要的方式发送数组,我假设 ByValArray 是这里有问题。
0000 6f 6e 65 00 00 00 00 00 00 00 00 00 00 00 00 00 one.............
0010 50 44 fc 00 00 00 00 00 61 00 00 00 00 00 00 00 PD......a.......
0020 53 00 79 00 73 00 74 00 65 00 6d 00 2e 00 53 00 S.y.s.t.e.m...S.
0030 65 00 63 00 75 00 72 00 69 00 74 00 79 00 2e 00 e.c.u.r.i.t.y...
0040 43 00 72 00 79 00 70 00 74 00 6f 00 67 00 72 00 C.r.y.p.t.o.g.r.
0050 61 00 70 00 68 00 79 00 2e 00 4f 00 70 00 65 00 a.p.h.y...O.p.e.
0060 6e 00 53 00 73 00 6c 00 00 00 98 1f dc 7f 00 00 n.S.s.l.........
所以我得到了第一个数组元素,但之后它只是随机垃圾(每次运行都会改变) - 所以 C 端暂时可以,但 C# 端不是。
更新 3:我进行了更多试验,并将 C# 端从字符串数组更改为 IntPtr 和 Marshal.UnsafeAddrOfPinnedArrayElement(filesToProcess, 0)。在 C 端,我现在得到了 C# 数组,当然,其中包含 C# 的内容和错误的编码,但至少它表明它确实是 C# 端的编组问题。
0000 90 0f 53 f7 27 7f 00 00 03 00 00 00 6f 00 6e 00 ..S.'.......o.n.
0010 65 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 e...............
0020 90 0f 53 f7 27 7f 00 00 03 00 00 00 74 00 77 00 ..S.'.......t.w.
0030 6f 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 o...............
0040 90 0f 53 f7 27 7f 00 00 05 00 00 00 74 00 68 00 ..S.'.......t.h.
0050 72 00 65 00 65 00 00 00 00 00 00 00 00 00 00 00 r.e.e...........
0060 90 0f 53 f7 27 7f 00 00 27 00 00 00 20 00 4d 00 ..S.'...'... .M.
我得到了关键问题:如果我想按值传递数组,每次调用的结构大小都是动态的,这可能是个问题。但是传递 ByValArray 似乎也不正确。可能需要使用固定大小的数组,将 IntPtr 用于数组,或者放弃结构并将其作为函数参数传递。
但一如既往,如果有人有更好的计划,我会全神贯注:)
【问题讨论】:
-
可能是填充问题 - 您能否在 C 程序中
printfsizeof(struct Myargs)看看是否在char **成员之前添加了 32 位填充?在 64 位平台上可能会出现这种情况。 -
@AdrianMole 也这么认为,但是
filesToProcessLength应该是不同的值。 -
@KonradRudolph 说得好。
-
您必须将 filesToProcess 声明为 IntPtr 并自己进行编组工作。或者不要使用结构并将所有作为参数传递,如下所示:
[DllImport("myLib.so", CharSet = CharSet.Ansi)] static extern bool myFunction(string[] files, int count); -
是的,您无法使用具有可变大小数组的结构。 IntPtr 是唯一的方法。你需要一个例子吗?
标签: c# arrays c struct pinvoke