将结构从原生C库传递到C#

xdyibdwo  于 2023-04-19  发布在  C#
关注(0)|答案(1)|浏览(146)

我试图填补结构包含数组从本机库到C#。我的结构包含两个int变量和数组的元素是两个整数的结构。
问题是数组内部的变量被正确接收,但外部结构中的变量被接收为0。如果我在C#端用不同的值初始化它们,它们不会改变。

C接口及实现:

extern "C" {
struct Element
{
    int id;
    int uid;
};

struct ElementsGrid
{
    int width;
    int height;
    Element* elements;
};

__declspec(dllexport) int gridSize()
{
    return 20;
}
__declspec(dllexport) void getGrid(ElementsGrid* grid)
{
    grid->width = 5;
    grid->height = 4;
    for (int w = 0; w < grid->width; ++w) {
        for (int h = 0; h < grid->height; ++h) {
            int index = w * grid->height + h;
            grid->elements[index].uid = index;
            grid->elements[index].id = 42;
        }
    }
}
} // extern "C"

C#部分

[StructLayout(LayoutKind.Sequential)]
public struct Element
{
    public int id;
    public int uid;
}

[StructLayout(LayoutKind.Sequential)]
public struct ElementsGrid
{
    public int width;
    public int height;
    public Element[] elements;
}
//...
[DllImport("my_plugin")]
static extern void getGrid([In,Out] ElementsGrid ptr);
//...
void getGrid()
{
    int field_size = gridSize();
    ElementsGrid grid = new ElementsGrid
    {
        elements = new Element[field_size]
    };
    getGrid(grid);
    print(grid.width);      // 0 here. Unchanged
    print(grid.height);     // 0 here. Unchanged
    for (int row = 0; row < 4; row++) {
        for (int column = 0; column < 5; column++) {
            int index = row * 5 + column;
            Element element = grid.elements[index];
            print(element.id);  // 42. Correct value
            print(element.uid); // ==index. Correct value
        }
    }
}
mepcadol

mepcadol1#

这是完全可能的,但您的代码有一些缺陷。C中的getGrid方法需要一个指向ElementsGrid示例的指针,而您试图传递的是示例,而不是指向它的指针。另一个问题是C中ElementsGrid的定义与C#中的等效定义不同;C需要一个指向Element的指针,而C#有一个元素数组。
我根本没有改变你的C
代码,但设法让它工作如下。
结构:

[StructLayout(LayoutKind.Sequential)]
    public struct Element
    {
        public int id;
        public int uid;
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct ElementsGrid
    {
        public int width;
        public int height;
        public IntPtr elements; // Note that this is now a pointer, not an array of Element
    }

当我这样尝试时,getGrid方法可以工作:

public static void getGrid()
    {
        int field_size = gridSize();
        ElementsGrid grid = new ElementsGrid()
        {
            width = field_size,
            height = field_size,
            elements = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(Element)) * field_size * field_size)  // Allocate enough memory to the pointer for the C++ method to populate it
        };
        IntPtr gridPtr = Marshal.AllocHGlobal(Marshal.SizeOf(grid));  // Allocate enough memory to a pointer to hold the contents of the ElementsGrid instance

        try
        {
            Marshal.StructureToPtr<ElementsGrid>(grid, gridPtr, false);  // Fill the pointer
            getGrid(gridPtr);  // Pass the pointer to C++
            grid = Marshal.PtrToStructure<ElementsGrid>(gridPtr);  // Re-assign grid to the contents of the pointer 
            
            Element[] elements = GetElements(grid.elements, grid.width * grid.height);  // Read the array from the grid.elements pointer using the new method below

            Console.WriteLine("Grid Width: " + grid.width.ToString());  // Works
            Console.WriteLine("Grid Height: " + grid.height.ToString());  // Works
            for (int row = 0; row < grid.height; row++)
            {
                for (int column = 0; column < grid.width; column++)
                {
                    int index = row * 5 + column;
                    Element element = elements[index];
                    Console.WriteLine("Element id: " + element.id.ToString());  // Works
                    Console.WriteLine("Element uid: " + element.uid.ToString());  // Works
                }
            }
        }
        finally
        {
            // Always clear up the pointers in a finally block
            Marshal.FreeHGlobal(grid.elements);  
            Marshal.FreeHGlobal(gridPtr);
        }
    }

读取grid.elements指针到Element数组的新方法。这是必要的,因为你不能在不直接阅读数组的情况下马歇尔指向结构数组的指针。

private static Element[] GetElements (IntPtr elementsPtr, int length)
    {
        int sizeInBytes = Marshal.SizeOf(typeof(Element));  // Get the size of one Element
        Element[] output = new Element[length];  // Create an array of Element with the right size

        for (int elCount = 0; elCount < length; elCount++)
        {
            IntPtr elPtr = new IntPtr((elementsPtr.ToInt64() + (elCount * sizeInBytes)));  // Read the pointer + offset from the element count
            output[elCount] = (Element)Marshal.PtrToStructure(elPtr, typeof(Element)); Assign the element to the contents of the pointer 
        }

        return output;
    }

同样,对于一个空白画布,你可以用一种完全不同的方式来实现这一点,通过改变你在C中的代码来接受行和列的计数,并返回一个指向ElementsGrid结构的指针。C#中的代码可以传入相关的行和列的计数,并从指针中读取数据。这将保存在C#中创建ElementsGrid,获取一个指向它的指针,然后将其传递给你的C代码。
希望这能帮上忙。

相关问题