C#调用C++的dll中的函数,数组指针的问题
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C#调用C++的dll中的函数,数组指针的问题相关的知识,希望对你有一定的参考价值。
我也说不清是数组指针还是指针数组……
我用c#写了一个程序,需要调用c++写的dll文件,文件中的其他函数都已经可以正常使用了,就剩下一个函数一直用不成功。
C++中的函数:
Read(int h, PUSHORT pBuf, int n);
使用(可以正常使用,h和n在前面定义了)
PUSHORT Buffer = NULL;
Buffer = new USHORT[n];(不知道这里是什么意思,为什么定义了2个相同的)
Read(h, Buffer, n);
C# 中(dllimport我就不贴上了)
unsafe public static extern bool Read(int h, int* []Buffer, int n);
函数的buffer里不管我是定义了int* []还是int []还是用指针IntPtr都失败
int[] Buffer = new int[n];(失败)
int* []Buffer = new int*[200];(失败)
最后指针(失败)
int size = Marshal.SizeOf(Buffer);(会报错,我知道会这样,可是这里应该怎么做?)
IntPtr ptrbu = Marshal.AllocHGlobal(size);
Marshal.Copy(Buffer, 0, ptrbu, n);
Console.WriteLine(Read(h, Buffer, n));
除了使用指针在size的时候会报错,其他方法都能正常编译通过,但是最后consolo的时候都是false!
我知道是buffer的问题,这个指针数组(还是数组指针)到底该怎么弄啊?
在定义Buffer的时候,后面括号里的失败不是指定义的时候失败,而是最后consolo里是false。
函数的用途是读取数据,存到buffer里面。
对于dll调用问题,如果是pe格式的dll考虑使用PInvoke(平台调用)。而平台调用跟具体的unsafe开发是没有任何关联的,并不是说平台调用一定会用到非安全开发!由于.net本身隐藏了指针的使用,你不必要再去开辟任何指针了!所以这里显然是平台调用。
解答一个疑问:数组指针or指针数组?
数组指针指的一个指针,该指针指向一个数组。
指针数组是一个数组,一个数组里全是指针。
显然这使用是有区别,但这里并不涉及,在safe开发.net都不会涉及!
IntPtr是一种特殊的指针,这种针指的特殊性在于它向了设备!所以嘛,他也有一个名词——名柄,IntPtr多种在PInvoke时,传递设备句柄时使用(设备并不是硬件,有时一个文件也算是一个句柄的)。当然,这里也用不到!
现在说一下平台调用(PInvoke)中最重要的一个部分!数据封送!
也就是你写dll导入时应用使用的类型!
在C语言中声明与赋值分开写的时候多了去了!他生成文件时写一起与分开写并没有任何区别!所以分开写无所谓的!但是,多数时间你会发现,其实他分开写是为了你阅读的方便!为什么?因为声明时使用的类型是PUSHORT,而赋值时使用了USHORT的类型,这两句写在一块的话,很多人不会注意到赋值时使用的基础类型!所以分开写只是相当于想写一个注释而已!
平台对应的数据封送就在这里!USHORT在C/CPP中对就的一个byte!一定要注意,所以你知道的,他们使用的new USHORT[n]只是开辟了n个无符号数的字节!在.net平台封送时如何传递数据?当然用byte去接值,他是数组,你就用byte[]去接!
这里有一个问题你可能不知道,在.net中,最小的数是byte,而不是short和ushort!在.net中可以直接使用byte与其他数值进行运算!而short/ushort在.net中却是一个16bit数!占用两个字节!而C/Cpp为了确定类型,所以byte是byte,short/ushort是数值,但是short/ushort却是一个8bit数字!当然了,C/Cpp中的long才与.net中的int对应!
如果你明白这个,那么该用int[]还是该用short[]还是该用byte[]你自己就知道了!
第二个问题——你一直在想指针,其实你要传入的数组是地址而不是副本!换句话来说,在安全模式中我们是有ref/out修饰的!在dllImport时你传入byte[]是没有任何错误的!但是你能不能在从dll中将byte[]修改后的值带回就是两个字了!
一般情况下byte[]等数组本身就是引址的方式,所以不使用ref/out声明也是没有问题的!但是如果封装层在有一定的疑问存在时,最好还是声明成ref的方式较为合适。经常性的规律是,如果我们看到Cpp的header中说明是指针时,我们会使用ref声明,不是指针形式时可以不用ref/out声明,如果是**形式时,平台封送最好使用数组指针。这种情况并不多见!
但是,由于string类型也是引用形的,由于其特点,返回值一定要用ref/out(不管你看到Cpp的header中是什么类型),或者我们可以使用一个说明长度的byte[]也是可行的!
换句话来说,虽然我们可以说C#中的byte可以接short, short可以接int等等的对应关系!但其实在不改变内存字节长度的情况下,PInvoke对类型的要求并没有那么严格!比如你可以用string接收dll中传入的字符串,用byte[]也行的!用stringbuilder还行的!并没有严格上的限制!但是这里边一定要注意的是长度!换句话来说,对方使用的是USHORT[n],你用byte[n]能接回来。
是不是一定要有这个对应关系?不一定!
这种情况往往出来在自定类中。比如Cpp header中说明某个参数一个结构!这个结构是由一个short,一个字符串组成的!用C#时可以使用byte实现结构中对应的short,也可以用short实现对应的short,但在声明必须使用特定的长度说明,说明其只有一位!当然用,无论你用int还是long都可以,在结构中必须要说明长度只有一位!那个字符串也是一样!所以你完全可以看到,数据类型并不重要,平台封送中不用管具体的类型,要的只是长度!长度必须对应!
换句话来说,你用char[]来处理Cpp中的ushort也是可以的!
平台数据封送中重要一部分就是共长度(类型?开什么玩笑,Cpp不会认识.net中的类型的,当然.net也不会认识Cpp中的类型)。只有长度一致才能接到值!
cpp中肯定是哪种类型方便用哪种了,当然.net中也是哪种方便用哪种了!那么平台数据是如何进行传递的呢?答案就在于基本类型和结构!
基本类型不说了,当然是ushort只是一个byte,你用int无用谓!反正在.net中byte类型会自然转换成int。如果是ushort[],那么你必须是byte[]或char[]!你用int[]绝对会出现错误的!为了帮助理解,我这里说的是重点是结构:
假定一个结构有n个项(你可以理解为字段)组成。那么在数据封送时就是这n个项每一段的组合。经如有字段是USHORT类型,一个字段是Byte[10]类型,还有一个是Int类型,在平台封送时,我们要封装一段内存,长度为13,第一个长度为1表示第一字段,接下来10个表示第二字段,接下来2个表示 第三字段。
当然我们在.net方面也要构造一个对应的结构!第一个字段你可以声明成byte/short/int/long都可以(必须说明这个在内存中只有一个字节),如果你用了short却不说明,很显然你.net会接到前两字节,破坏了要接收的结构!你自己肯定拿不到数据的!
原理就这么多!慢慢看懂了就知道平台封送是怎么回事了!至于你的问题,抄个近路告诉你结果:dllImport声明时使用
static bool Read(short h, byte[] buffer, short n);
或者
static bool Read(short h, ref byte[] buffer, short n);
当然,也可以使用int,我并不反对
static bool Read([MarshalAs(UnManagerType.USHORT)] int h, byte[] buffer, short n);
换句话来说,你可以使用MarshalAsAttribute来指针其非托类型,结构时还可以直接使用长度说明。主要是你学会MarshalAsAttribute的使用,就知道参数如何传递了! 参考技术A 这个不需要用到unsafe和指针
[DllImport(xxxxxx)]
public static bool Read(int h, UInt16[] Buffer, int n);//c#定义
调用,h和n先定义
var buffer = new UInt16[n];
Read(h, buffer, n);追问
谢谢,不过依旧是失败。我也没有原函数,只能一味的去试。
以上是关于C#调用C++的dll中的函数,数组指针的问题的主要内容,如果未能解决你的问题,请参考以下文章
C#调用C++的dll库怎么传递结构体中不定长度的char数组
C# 调用C++ DLL,而c++函数的有一个参数需要是null,该怎么传递?