gpt4 book ai didi

c# - 我应该如何使用 P/Invoke 将字符串数组传递给 C 库?

转载 作者:行者123 更新时间:2023-12-04 15:22:06 27 4
gpt4 key购买 nike

我正在尝试从 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# 端的编码(marshal)处理问题。

  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# 到 C)-only 方式中。

如果出于某种原因你想使用一个结构体,那么你可以这样做:

C端(我用的是Windows,你可能要适配):

struct MyArgs {
int32_t someArg;
char** filesToProcess;
int32_t filesToProcessLength;
};

// I pass struct as reference, not value, but this is not relevant
// I also use __stdcall which is quite standard on Windows
extern "C" {
__declspec(dllexport) bool __stdcall myFunction(struct MyArgs* pargs) {
printf("Files to Process: %i\n", pargs->filesToProcessLength);
for (int i = 0; i < pargs->filesToProcessLength; i++) {
char* str = pargs->filesToProcess[i];
printf("\t%i. %s\n", i, str);
}
return true;
}
}

C# 端:

static void Main(string[] args)
{
var files = new List<string>();
files.Add("hello");
files.Add("world!");

var elementSize = IntPtr.Size;
var my = new MyArgs();
my.filesToProcessLength = files.Count;

// allocate the array
my.filesToProcess = Marshal.AllocCoTaskMem(files.Count * elementSize);
try
{
for (var i = 0; i < files.Count; i++)
{
// allocate each file
// I use Ansi as you do although Unicode would be better (at least on Windows)
var filePtr = Marshal.StringToCoTaskMemAnsi(files[i]);

// write the file pointer to the array
Marshal.WriteIntPtr(my.filesToProcess + elementSize * i, filePtr);
}

myFunction(ref my);
}
finally
{
// free each file pointer
for (var i = 0; i < files.Count; i++)
{
var filePtr = Marshal.ReadIntPtr(my.filesToProcess + elementSize * i);
Marshal.FreeCoTaskMem(filePtr);
}
// free the array
Marshal.FreeCoTaskMem(my.filesToProcess);
}
}

[StructLayout(LayoutKind.Sequential)]
struct MyArgs
{
public int someArg;
public IntPtr filesToProcess;
public int filesToProcessLength;
};

// stdcall is the default calling convention
[DllImport("MyProject.dll")]
static extern bool myFunction(ref MyArgs args);

关于c# - 我应该如何使用 P/Invoke 将字符串数组传递给 C 库?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/63077017/

27 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com