如何编组指向包含unsigned char数组的结构数组的指针?

时间:2017-03-07 13:43:22

标签: c# arrays pointers pinvoke marshalling

我很抱歉,如果这听起来太具体了,但我需要以这种方式完成这项工作,而且我已经坚持了几天。在我的实际场景中,我有一个MyStruct **ppObject,必须将该地址传递给第三方dll,以便它指向一个结构数组。之后,我必须p / Invoke这个指向数组的指针,但是我遇到了struct的内容问题。这是一个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类中的字节数组(我试图通过前面提到的unsigned char数组编组)在每次运行程序时打印不同的随机数据但是当我用调试器检查C ++端时(在编组之前),一切都很好。就像我在上面的代码中的评论中提到的那样,我认为问题在于ManagedClass类中的这一行:

list.Add((PlcVarValue)Marshal.PtrToStructure(new IntPtr(ptr.ToInt32() + memSize * i), typeof(PlcVarValue)));

我知道一个事实,这对MyStruct *pObject很好用,其中*pObject是一个相同结构的数组,但在这种情况下我需要一个指向指针的指针,因为第三方dll是什么要求实际上是MyStruct ***pppObject(打败我,但这是我必须处理的)。我试图将数据从ppObject复制到单个指针,但即使它有效,我对结果也不满意,因为它在实际应用程序中有一些不希望的副作用。另外,如果我对GCHandle的使用是错误的,我很乐意接受如何解决它的建议,但这不是这个问题的主要焦点。

1 个答案:

答案 0 :(得分:2)

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);
}

但你不应该混用mallocnew

如果你需要解释我正在做什么,请记住你正在返回一个指针数组的指针!