C#调用C++写的dll,如何将C#中的数组作为函数参数进行传递?

时间:2022-08-30 19:56:12
现需要将C#中的结构体数组作为参数进行传递 
对于传递单个结构体的功能我的实现方式如下: 
C++ dll: 


struct Node{
    int cx;
    char* str;
    double dNum;
};

extern "C" __declspec(dllexport) int fnGetStru(Node* pNode);




c#部分代码: 


        /*引用C++的dll*/
        [DllImport("CPlusDll.dll")]
        public static extern int fnGetStru(ref Node node);

        /*定义对应的结构体*/
        [StructLayout(LayoutKind.Sequential)]
        public struct Node
        {
            [MarshalAs(UnmanagedType.I4)]
            public int cx;
            
            [MarshalAs(UnmanagedType.LPStr)]
            public String str;

            [MarshalAs(UnmanagedType.R8)]
            public Double dNum; 
        }



这样就能将结构体对象作为参数进行传递了。 


但是如何将结构体数组作为参数传递呢? 
比如在C#中定义的结构体数组 
Node [] node_array; 
node_array = new Node[3]; 

如何将这个node_array传递给c++,使之能在c++中被访问修改? 

谢谢指教

16 个解决方案

#1


使用类吧,别用结构体了!

因为:
结构体,传值
类,传引用

#2


引用楼主 xuleicsu 的帖子:
...
extern "C" __declspec(dllexport) int fnGetStru(Node* pNode);
...


c++怎么能知道传入的数组的长度呢?

#3


希望高手解答

#4


哦,可以在函数的参数列表中假如int型变量,指明数组的长度

#5


不懂幫頂

#6


将dll文件用Dllimport或Dllimportattribute引用一下,其他的和C#类似的
你这里是不是结构体的问题啊

#7


使用 unsafe 在C#里面先转化为指针,然后再传进去。 

或者, 

改你的C++,C++的函数本来就不是数组的。 

你无法保证C#里面申请的数组的空间是连续的。这才是问题。所以你可以试着把数组的首元素指针传进去,然后让C++去试着用指针操作来获取数组后面的元素,但未必会成功。

建议,还是尽量少尝试让C#传数组给C++的做法吧,反过来还是可以的,因为在C++里面,数组的空间是连续的。

这个你是必须的吗?

折衷的方案是,在C#里面for 循环,每次都向C++的函数里面传递一次元素的指针,这种方法最安全。

#8



using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

    [DllImport("CPlusDll.dll")]
    public static extern int fnGetStru([MarshalAs(UnmanagedType.LPArray, SizeConst = 4)]ref Node node);


加上这句话就可以将Array从manage端传递到native端了。

#9



    [DllImport("CPlusDll.dll")]
    public static extern int fnGetStru([MarshalAs(UnmanagedType.LPArray, SizeConst = 4)]ref Node node);

加上这句话:
[MarshalAs(UnmanagedType.LPArray, SizeConst = 4)]

LPArray是C风格的数组,其他的还有SafeArray数组
SizeConst等于数组的大小,其实从manage端传到native端,clr是可以自动识别数组的大小的;反之,从native端到manage端,必须把数组的大小显示的传递回来。
使用SizeConst指定了大小的数组,在native端是不可以改变大小的。

SizeParamIndex 也是用于指定数组大小的,但在clr4.0之前不支持(VS2008不支持),最近才加入到Interop功能中的。
举个例子:

public static extern int fnGetStru(ref int size,[MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0)]ref Node node);


SizeParamIndex = 0 指的是index为0的是数组的大小,即size是数组大小。

使用SizeParamIndex指定大小的数组,在native端是可以改变大小。

#10


引用 9 楼 wrinky 的回复:
C# code

    [DllImport("CPlusDll.dll")]
    public static extern int fnGetStru([MarshalAs(UnmanagedType.LPArray, SizeConst = 4)]ref Node node);


加上这句话: 
[MarshalAs(UnmanagedType.LPArray, SizeConst = 4)] 

LPArray是C风格的数组,其他的还有SafeArray数组 
SizeConst等于数组的大小,其实从manage端传到native端,clr是可以自动识别数组的大小的;反之,从native端到manage端,必须把数组的大小显示的传递回…


由managed端传到native端有一个问题是,你能否保证你的managed数组的空间是连续的?

managed和native对数组元素读取的方法是不通用的,在native中你可以通过 ((Type*)pt + 1) 的方式来读取下一个元素,因为native数组的元素是连续的,在managed下是不行的。
我觉得用 MarshalAs对于由 native 向managed 传递是安全的,反过去未必吧……

#11


完整代码如下:


struct Node{
    int cx;
    char* str;
    double dNum;
};

extern "C" __declspec(dllexport) int fnGetStru(Node* pNode)
{
return 1;
};



using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;        

class MyClass
{
    /*引用C++的dll*/
    [DllImport("CPlusDll.dll")]
    public static extern int fnGetStru([MarshalAs(UnmanagedType.LPArray, SizeConst = 4)]Node[] node);

    /*定义对应的结构体*/
    [StructLayout(LayoutKind.Sequential)]
    public struct Node
    {
        [MarshalAs(UnmanagedType.I4)]
        public int cx;

        [MarshalAs(UnmanagedType.LPStr)]
        public String str;

        [MarshalAs(UnmanagedType.R8)]
        public Double dNum;
    }

    public static void Main()
    {
        Node[] node_array;
        node_array = new Node[4];

        fnGetStru(node_array);

    }

}


修复了楼上代码的一些错误。
注意manage端是node[],我用的VS2005不支持ref Array,貌似也要到VS2010才确实支持ref Array。

希望这些对你有些帮助。

#12


引用 10 楼 hikaliv 的回复:
由managed端传到native端有一个问题是,你能否保证你的managed数组的空间是连续的? 

managed和native对数组元素读取的方法是不通用的,在native中你可以通过 ((Type*)pt + 1) 的方式来读取下一个元素,因为native数组的元素是连续的,在managed下是不行的。 
我觉得用 MarshalAs对于由 native 向managed 传递是安全的,反过去未必吧……


第一,managed中数组是类,是引用类型,是分配在托管堆上的。所以Array是连续的空间,不管包含多少个元素都是一个对象,一个对象是不会被GC调整到内存空间不同的地方的。这一点,你可以调试一下,观察一下Array对应的内存空间。

第二,managed和native中都可以用[]来读取数组元素,native多了可以用指针读取的方式。

第三,个人觉得用MarshalAs对COMtoNet(managed to native), 还是NETtoCOM(native to managed)都是安全的。

PS. 楼主问的其实是一个PInvoke的问题,还是比较简单的COM与.NET交互问题。

#13


引用 12 楼 wrinky 的回复:
引用 10 楼 hikaliv 的回复:

由managed端传到native端有一个问题是,你能否保证你的managed数组的空间是连续的? 

managed和native对数组元素读取的方法是不通用的,在native中你可以通过 ((Type*)pt + 1) 的方式来读取下一个元素,因为native数组的元素是连续的,在managed下是不行的。 
我觉得用 MarshalAs对于由 native 向managed 传递是安全的,反过去未必吧…… 


第一,managed中数组是类,是引用类型,是分配在托管堆上…


我知道,数组实际上在.net中是一个实现IEnumerable接口的类。
但是我记得好像是同一个类的同一个对像的各部分也可能会被分散到内存的各个地方,所以才会有类对像的序列化的问题。
或者说,.net对数组对像有着特别的处理?

#14


不懂....

#15


你的问题主要就是平台调用过程中的数据封送问题。楼上有人已经给出了正确的答案。但是如果只获得答案,不知道原理,以后遇到了此类问题还是不知道如何下手。

如果你想系统学习如何进行数据封送,我推荐你阅读刚刚出版的新书:《精通.NET互操作P/Invoke,C++Interop和COM Interop》,这本书的第2章“数据封送”详细介绍了平台调用中的数据封送过程,非常详细,我就是读完后才搞清楚平台调用中的封送处理。 


该书的官方网站: 
www.interop123.com 

豆瓣网信息: 
http://www.douban.com/subject/3671497/ 

#16


按楼上说的,去图书馆借了本~先顶了,大概看了下,感觉好像还不错~

#1


使用类吧,别用结构体了!

因为:
结构体,传值
类,传引用

#2


引用楼主 xuleicsu 的帖子:
...
extern "C" __declspec(dllexport) int fnGetStru(Node* pNode);
...


c++怎么能知道传入的数组的长度呢?

#3


希望高手解答

#4


哦,可以在函数的参数列表中假如int型变量,指明数组的长度

#5


不懂幫頂

#6


将dll文件用Dllimport或Dllimportattribute引用一下,其他的和C#类似的
你这里是不是结构体的问题啊

#7


使用 unsafe 在C#里面先转化为指针,然后再传进去。 

或者, 

改你的C++,C++的函数本来就不是数组的。 

你无法保证C#里面申请的数组的空间是连续的。这才是问题。所以你可以试着把数组的首元素指针传进去,然后让C++去试着用指针操作来获取数组后面的元素,但未必会成功。

建议,还是尽量少尝试让C#传数组给C++的做法吧,反过来还是可以的,因为在C++里面,数组的空间是连续的。

这个你是必须的吗?

折衷的方案是,在C#里面for 循环,每次都向C++的函数里面传递一次元素的指针,这种方法最安全。

#8



using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

    [DllImport("CPlusDll.dll")]
    public static extern int fnGetStru([MarshalAs(UnmanagedType.LPArray, SizeConst = 4)]ref Node node);


加上这句话就可以将Array从manage端传递到native端了。

#9



    [DllImport("CPlusDll.dll")]
    public static extern int fnGetStru([MarshalAs(UnmanagedType.LPArray, SizeConst = 4)]ref Node node);

加上这句话:
[MarshalAs(UnmanagedType.LPArray, SizeConst = 4)]

LPArray是C风格的数组,其他的还有SafeArray数组
SizeConst等于数组的大小,其实从manage端传到native端,clr是可以自动识别数组的大小的;反之,从native端到manage端,必须把数组的大小显示的传递回来。
使用SizeConst指定了大小的数组,在native端是不可以改变大小的。

SizeParamIndex 也是用于指定数组大小的,但在clr4.0之前不支持(VS2008不支持),最近才加入到Interop功能中的。
举个例子:

public static extern int fnGetStru(ref int size,[MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0)]ref Node node);


SizeParamIndex = 0 指的是index为0的是数组的大小,即size是数组大小。

使用SizeParamIndex指定大小的数组,在native端是可以改变大小。

#10


引用 9 楼 wrinky 的回复:
C# code

    [DllImport("CPlusDll.dll")]
    public static extern int fnGetStru([MarshalAs(UnmanagedType.LPArray, SizeConst = 4)]ref Node node);


加上这句话: 
[MarshalAs(UnmanagedType.LPArray, SizeConst = 4)] 

LPArray是C风格的数组,其他的还有SafeArray数组 
SizeConst等于数组的大小,其实从manage端传到native端,clr是可以自动识别数组的大小的;反之,从native端到manage端,必须把数组的大小显示的传递回…


由managed端传到native端有一个问题是,你能否保证你的managed数组的空间是连续的?

managed和native对数组元素读取的方法是不通用的,在native中你可以通过 ((Type*)pt + 1) 的方式来读取下一个元素,因为native数组的元素是连续的,在managed下是不行的。
我觉得用 MarshalAs对于由 native 向managed 传递是安全的,反过去未必吧……

#11


完整代码如下:


struct Node{
    int cx;
    char* str;
    double dNum;
};

extern "C" __declspec(dllexport) int fnGetStru(Node* pNode)
{
return 1;
};



using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;        

class MyClass
{
    /*引用C++的dll*/
    [DllImport("CPlusDll.dll")]
    public static extern int fnGetStru([MarshalAs(UnmanagedType.LPArray, SizeConst = 4)]Node[] node);

    /*定义对应的结构体*/
    [StructLayout(LayoutKind.Sequential)]
    public struct Node
    {
        [MarshalAs(UnmanagedType.I4)]
        public int cx;

        [MarshalAs(UnmanagedType.LPStr)]
        public String str;

        [MarshalAs(UnmanagedType.R8)]
        public Double dNum;
    }

    public static void Main()
    {
        Node[] node_array;
        node_array = new Node[4];

        fnGetStru(node_array);

    }

}


修复了楼上代码的一些错误。
注意manage端是node[],我用的VS2005不支持ref Array,貌似也要到VS2010才确实支持ref Array。

希望这些对你有些帮助。

#12


引用 10 楼 hikaliv 的回复:
由managed端传到native端有一个问题是,你能否保证你的managed数组的空间是连续的? 

managed和native对数组元素读取的方法是不通用的,在native中你可以通过 ((Type*)pt + 1) 的方式来读取下一个元素,因为native数组的元素是连续的,在managed下是不行的。 
我觉得用 MarshalAs对于由 native 向managed 传递是安全的,反过去未必吧……


第一,managed中数组是类,是引用类型,是分配在托管堆上的。所以Array是连续的空间,不管包含多少个元素都是一个对象,一个对象是不会被GC调整到内存空间不同的地方的。这一点,你可以调试一下,观察一下Array对应的内存空间。

第二,managed和native中都可以用[]来读取数组元素,native多了可以用指针读取的方式。

第三,个人觉得用MarshalAs对COMtoNet(managed to native), 还是NETtoCOM(native to managed)都是安全的。

PS. 楼主问的其实是一个PInvoke的问题,还是比较简单的COM与.NET交互问题。

#13


引用 12 楼 wrinky 的回复:
引用 10 楼 hikaliv 的回复:

由managed端传到native端有一个问题是,你能否保证你的managed数组的空间是连续的? 

managed和native对数组元素读取的方法是不通用的,在native中你可以通过 ((Type*)pt + 1) 的方式来读取下一个元素,因为native数组的元素是连续的,在managed下是不行的。 
我觉得用 MarshalAs对于由 native 向managed 传递是安全的,反过去未必吧…… 


第一,managed中数组是类,是引用类型,是分配在托管堆上…


我知道,数组实际上在.net中是一个实现IEnumerable接口的类。
但是我记得好像是同一个类的同一个对像的各部分也可能会被分散到内存的各个地方,所以才会有类对像的序列化的问题。
或者说,.net对数组对像有着特别的处理?

#14


不懂....

#15


你的问题主要就是平台调用过程中的数据封送问题。楼上有人已经给出了正确的答案。但是如果只获得答案,不知道原理,以后遇到了此类问题还是不知道如何下手。

如果你想系统学习如何进行数据封送,我推荐你阅读刚刚出版的新书:《精通.NET互操作P/Invoke,C++Interop和COM Interop》,这本书的第2章“数据封送”详细介绍了平台调用中的数据封送过程,非常详细,我就是读完后才搞清楚平台调用中的封送处理。 


该书的官方网站: 
www.interop123.com 

豆瓣网信息: 
http://www.douban.com/subject/3671497/ 

#16


按楼上说的,去图书馆借了本~先顶了,大概看了下,感觉好像还不错~