将带有字符串数组的C#结构传递给C++函数,该函数接受C#结构的void*和C#字符串数组的char**

本文关键字:函数 字符串 结构 数组 void char C++ | 更新日期: 2023-09-27 18:22:17

我想将带有字符串数组的C#结构发送到C++函数,该函数接受用于C#结构的void*和用于C#结构字符串数组成员的char**。

我能够将结构发送到c++函数,但问题是,无法从c++函数访问c#结构的字符串数组数据成员。当单独发送字符串数组时,我能够访问数组元素。

示例代码为-

C# Code:
[StructLayout(LayoutKind.Sequential)]
public struct TestInfo
{
    public int TestId;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
    public String[] Parameters;
}
[DllImport("TestAPI.dll", CallingConvention = CallingConvention.StdCall, EntryPoint    "TestAPI")]
private static extern void TestAPI(ref TestInfo data);
static unsafe void Main(string[] args)
{
TestInfo  testinfoObj = new TestInfo();
testinfoObj.TestId = 1;
List<string> names = new List<string>();
names.Add("first");
names.Add("second");
names.Add("third");
testinfoObj.Parameters=names.ToArray();
TestAPI(ref testinfoObj);
}

VC++ Code:
/*Structure with details for TestInfo*/
typedef struct TestInfo
{
int  TestId;
char **Parameters;
}TestInfo_t;
//c++ function
__declspec(dllexport) int TestAPI(void *data)
{
TestInfo *cmd_data_ptr= NULL;
cmd_data_ptr = (TestInfo) data;
printf("ID is %d 'r'n",cmd_data_ptr->TestId);//Working fine
for(i = 0; i < 3; i++)
printf("value: %s 'r'n",((char *)cmd_data_ptr->Parameters)[i]);/*Error-Additional     information: Attempted to read or write protected memory. This is often an indication that other memory is corrupt*/
}

在分析内存堆栈时,可以观察到,当我打印((char*)cmd_data_ptr->Parameters),打印第一个数组元素("first"),但使用((char*)cmd_data_ptr->Parameters)[i],无法访问元素,上述异常即将出现。

结构内存地址包含所有结构元素的地址,但在从c++访问数据时,它只访问字符串数组的第一个元素。

将带有字符串数组的C#结构传递给C++函数,该函数接受C#结构的void*和C#字符串数组的char**

[MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
public String[] Parameters;

是一个内联数组。匹配的C++声明是:

char* Parameters[2];

但你正试图将其与相匹配

char** Parameters;

这完全不同。

您需要手动整理。在C#结构中,将Parameters声明为IntPtr。然后用Marshal.AllocHGlobal分配本机内存以保存指针数组。然后用指向字符串的指针填充这些指针。

[StructLayout(LayoutKind.Sequential)]
public struct TestInfo
{
    public int TestId;
    public IntPtr Parameters;
}
static void Main(string[] args) // no need for unsafe
{
    TestInfo testInfo;
    testInfo.TestId = 1;
    testInfo.Parameters = Marshal.AllocHGlobal(2*Marshal.SizeOf(typeof(IntPtr)));
    IntPtr ptr = testInfo.Parameters;
    Marshal.WriteIntPtr(ptr, Marshal.StringToHGlobalAnsi("foo"));
    ptr += Marshal.SizeOf(typeof(IntPtr));
    Marshal.WriteIntPtr(ptr, Marshal.StringToHGlobalAnsi("bar"));
    TestAPI(ref testinfoObj);
    // now you want to call FreeHGlobal, I'll leave that code to you
}

另一种选择是使用固定的IntPtr[]并将其放入testInfo.Parameters.

这实际上更像是对David答案的扩展/扩展,但这里有一种结束自定义编组的方法:

public struct LocalTestInfo
{
    public int TestId;
    public IEnumerable<string> Parameters;
    public static explicit operator TestInfo(LocalTestInfo info)
    {
        var marshalled = new TestInfo
            {
                TestId = info.TestId, 
            };
        var paramsArray = info.Parameters
            .Select(Marshal.StringToHGlobalAnsi)
            .ToArray();
        marshalled.pinnedHandle = GCHandle.Alloc(
            paramsArray, 
            GCHandleType.Pinned);
        marshalled.Parameters = 
            marshalled.pinnedHandle.AddrOfPinnedObject();
        return marshalled;
    }
}
[StructLayout(LayoutKind.Sequential)]
public struct TestInfo : IDisposable
{
    public int TestId;
    public IntPtr Parameters;
    [NonSerialized]
    public GCHandle pinnedHandle;
    public void Dispose()
    {
        if (pinnedHandle.IsAllocated)
        {
            Console.WriteLine("Freeing pinned handle");
            var paramsArray = (IntPtr[])this.pinnedHandle.Target;
            foreach (IntPtr ptr in paramsArray)
            {
                Console.WriteLine("Freeing @ " + ptr);
                Marshal.FreeHGlobal(ptr);
            }
            pinnedHandle.Free();
        }
    }
}

关于我的测试,我换成了CDecl:

[DllImport(@"Test.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int TestAPI(ref TestInfo info);

此外,我认为你在C++方面有一个拼写错误:

extern "C" 
__declspec(dllexport) int TestAPI(void *data)
{
    TestInfo *cmd_data_ptr= NULL;
    cmd_data_ptr = (TestInfo*) data;
    printf("ID is %d 'r'n",cmd_data_ptr->TestId);
    // char**, not char*
    char** paramsArray = ((char **)cmd_data_ptr->Parameters);
    for(int i = 0; i < 3; i++)
    {
        printf("value: %s 'r'n",paramsArray[i]);
    }
    return 0;
}

还有一个试验台:

static void Main(string[] args)
{
    var localInfo = new LocalTestInfo()
    {
        TestId = 1,
        Parameters = new[]
        {
            "Foo", 
            "Bar",
            "Baz"
        }
    };
    TestInfo forMarshalling;
    using (forMarshalling = (TestInfo)localInfo)
    {
        TestAPI(ref forMarshalling);                
    }
}

反向编组运算符留给读者练习,但基本上看起来应该与显式TestInfo运算符相反。