如何编组指向包含无符号字符数组的结构数组的指针?
How to marshal a pointer to array of structs which contains an unsigned char array?
如果这听起来太具体,我很抱歉,但我需要完全按照这种方式完成,而且我坚持了好几天。在我的真实场景中,我有一个 MyStruct **ppObject
必须将哪个地址传递给第三方 dll,以便它指向一个结构数组。之后,我必须 p/Invoke 同一个指向数组的指针,但我对结构的内容有疑问。这是一个 MCVE:
Unmanaged.h:
#ifndef _NATIVELIB_H_
#define _NATIVELIB_H_
#ifndef MCVE
#define MCVE
#endif
struct PlcVarValue
{
unsigned char byData[8];
};
#ifdef __cplusplus
extern "C" {
#endif
MCVE __declspec(dllexport) void FillArray(void);
MCVE __declspec(dllexport) void Clear(void);
MCVE __declspec(dllexport) PlcVarValue** GetValues(void);
#ifdef __cplusplus
}
#endif
#endif // _NATIVELIB_H_
Unmanaged.cpp
#include "stdafx.h"
#include "Unmanaged.h"
#include <iostream>
PlcVarValue** values;
MCVE __declspec(dllexport) void FillArray(void) {
values = (PlcVarValue**)malloc(sizeof(PlcVarValue*) * 5);
for (int i = 0; i < 5; i++)
{
values[i] = new PlcVarValue();
*values[i]->byData = i;
}
}
MCVE __declspec(dllexport) void Clear(void) {
delete *values;
free(values);
}
MCVE __declspec(dllexport) PlcVarValue** GetValues(void) {
return values;
}
PlcVarValue.cs
using System.Runtime.InteropServices;
namespace Managed
{
[StructLayout(LayoutKind.Sequential)]
public class PlcVarValue
{
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
public byte[] Data;
}
}
ManagedClass.cs
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
namespace Managed
{
public class ManagedClass
{
public ManagedClass()
{
FillArray();
}
[DllImport("Unmanaged.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern void FillArray();
[DllImport("Unmanaged.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern IntPtr GetValues();
[DllImport("Unmanaged.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern void Clear();
public List<PlcVarValue> GetList()
{
int size = 5;
var list = new List<PlcVarValue>();
IntPtr ptr = GetValues();
var gch = GCHandle.Alloc(ptr, GCHandleType.Pinned);
try
{
int memSize = Marshal.SizeOf(typeof(PlcVarValue));
for (int i = 0; i < size; i++)
{
//I know this is wrong, it would work for a PlcVarValue* but I need it to work with a PlcVarValue**
list.Add((PlcVarValue)Marshal.PtrToStructure(new IntPtr(ptr.ToInt32() + memSize * i), typeof(PlcVarValue)));
}
}
finally
{
gch.Free();
}
return list;
}
public void FreeMemory()
{
Clear();
}
}
}
Program.cs
using Managed;
using System;
namespace Test
{
class Program
{
static void Main(string[] args)
{
var managed = new ManagedClass();
var list = managed.GetList();
foreach(var value in list)
Console.WriteLine(BitConverter.ToInt32(value.Data, 0));
managed.FreeMemory();
Console.ReadKey();
}
}
}
我希望代码是不言自明的,如果需要我会提供更多信息。我的问题是 Program
class 中的字节数组(我试图从前面提到的 unsigned char 数组中编组)每次我 运行 程序时打印不同的随机数据但是当我用调试器检查了 C++ 端(在编组发生之前),一切都很好。就像我在上面代码的评论中提到的那样,我认为问题出在 ManagedClass
class 中的这一行:
list.Add((PlcVarValue)Marshal.PtrToStructure(new IntPtr(ptr.ToInt32() + memSize * i), typeof(PlcVarValue)));
我知道这适用于 MyStruct *pObject
,其中 *pObject
是相同结构的数组,但在这种情况下,我需要一个指向指针的指针,因为第三个 - party dll 需要的实际上是一个 MyStruct ***pppObject
(打败了我,但这就是我必须处理的)。我曾尝试将数据从 ppObject
复制到单个指针,但即使它有效,我对结果也不满意,因为它在实际应用程序中有一些不良副作用。另外,如果我对 GCHandle 的使用有误,我很乐意接受有关如何修复它的建议,但这不是这个问题的主要焦点。
C# 端:
public List<PlcVarValue> GetList()
{
int size = 5;
var list = new List<PlcVarValue>(size);
IntPtr ptr = GetValues();
for (int i = 0; i < size; i++)
{
IntPtr ptrPlc = Marshal.ReadIntPtr(ptr, i * IntPtr.Size);
var plc = (PlcVarValue)Marshal.PtrToStructure(ptrPlc, typeof(PlcVarValue));
list.Add(plc);
}
return list;
}
和 C++ 端(唯一的错误在 Clear()
):
__declspec(dllexport) void Clear(void)
{
for (int i = 0; i < 5; i++)
{
delete values[i];
}
free(values);
}
但是你不应该混合使用 malloc
和 new
!
如果您需要解释我在做什么,请记住您返回的是指向指针数组的指针!
如果这听起来太具体,我很抱歉,但我需要完全按照这种方式完成,而且我坚持了好几天。在我的真实场景中,我有一个 MyStruct **ppObject
必须将哪个地址传递给第三方 dll,以便它指向一个结构数组。之后,我必须 p/Invoke 同一个指向数组的指针,但我对结构的内容有疑问。这是一个 MCVE:
Unmanaged.h:
#ifndef _NATIVELIB_H_
#define _NATIVELIB_H_
#ifndef MCVE
#define MCVE
#endif
struct PlcVarValue
{
unsigned char byData[8];
};
#ifdef __cplusplus
extern "C" {
#endif
MCVE __declspec(dllexport) void FillArray(void);
MCVE __declspec(dllexport) void Clear(void);
MCVE __declspec(dllexport) PlcVarValue** GetValues(void);
#ifdef __cplusplus
}
#endif
#endif // _NATIVELIB_H_
Unmanaged.cpp
#include "stdafx.h"
#include "Unmanaged.h"
#include <iostream>
PlcVarValue** values;
MCVE __declspec(dllexport) void FillArray(void) {
values = (PlcVarValue**)malloc(sizeof(PlcVarValue*) * 5);
for (int i = 0; i < 5; i++)
{
values[i] = new PlcVarValue();
*values[i]->byData = i;
}
}
MCVE __declspec(dllexport) void Clear(void) {
delete *values;
free(values);
}
MCVE __declspec(dllexport) PlcVarValue** GetValues(void) {
return values;
}
PlcVarValue.cs
using System.Runtime.InteropServices;
namespace Managed
{
[StructLayout(LayoutKind.Sequential)]
public class PlcVarValue
{
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
public byte[] Data;
}
}
ManagedClass.cs
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
namespace Managed
{
public class ManagedClass
{
public ManagedClass()
{
FillArray();
}
[DllImport("Unmanaged.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern void FillArray();
[DllImport("Unmanaged.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern IntPtr GetValues();
[DllImport("Unmanaged.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern void Clear();
public List<PlcVarValue> GetList()
{
int size = 5;
var list = new List<PlcVarValue>();
IntPtr ptr = GetValues();
var gch = GCHandle.Alloc(ptr, GCHandleType.Pinned);
try
{
int memSize = Marshal.SizeOf(typeof(PlcVarValue));
for (int i = 0; i < size; i++)
{
//I know this is wrong, it would work for a PlcVarValue* but I need it to work with a PlcVarValue**
list.Add((PlcVarValue)Marshal.PtrToStructure(new IntPtr(ptr.ToInt32() + memSize * i), typeof(PlcVarValue)));
}
}
finally
{
gch.Free();
}
return list;
}
public void FreeMemory()
{
Clear();
}
}
}
Program.cs
using Managed;
using System;
namespace Test
{
class Program
{
static void Main(string[] args)
{
var managed = new ManagedClass();
var list = managed.GetList();
foreach(var value in list)
Console.WriteLine(BitConverter.ToInt32(value.Data, 0));
managed.FreeMemory();
Console.ReadKey();
}
}
}
我希望代码是不言自明的,如果需要我会提供更多信息。我的问题是 Program
class 中的字节数组(我试图从前面提到的 unsigned char 数组中编组)每次我 运行 程序时打印不同的随机数据但是当我用调试器检查了 C++ 端(在编组发生之前),一切都很好。就像我在上面代码的评论中提到的那样,我认为问题出在 ManagedClass
class 中的这一行:
list.Add((PlcVarValue)Marshal.PtrToStructure(new IntPtr(ptr.ToInt32() + memSize * i), typeof(PlcVarValue)));
我知道这适用于 MyStruct *pObject
,其中 *pObject
是相同结构的数组,但在这种情况下,我需要一个指向指针的指针,因为第三个 - party dll 需要的实际上是一个 MyStruct ***pppObject
(打败了我,但这就是我必须处理的)。我曾尝试将数据从 ppObject
复制到单个指针,但即使它有效,我对结果也不满意,因为它在实际应用程序中有一些不良副作用。另外,如果我对 GCHandle 的使用有误,我很乐意接受有关如何修复它的建议,但这不是这个问题的主要焦点。
C# 端:
public List<PlcVarValue> GetList()
{
int size = 5;
var list = new List<PlcVarValue>(size);
IntPtr ptr = GetValues();
for (int i = 0; i < size; i++)
{
IntPtr ptrPlc = Marshal.ReadIntPtr(ptr, i * IntPtr.Size);
var plc = (PlcVarValue)Marshal.PtrToStructure(ptrPlc, typeof(PlcVarValue));
list.Add(plc);
}
return list;
}
和 C++ 端(唯一的错误在 Clear()
):
__declspec(dllexport) void Clear(void)
{
for (int i = 0; i < 5; i++)
{
delete values[i];
}
free(values);
}
但是你不应该混合使用 malloc
和 new
!
如果您需要解释我在做什么,请记住您返回的是指向指针数组的指针!