C#与C++函数调用
在 上篇文章重点讲解数据类型的转换,在此基础上再讲解函数调用。
1、函数调用
c++中函数
INF_NET_API INF_RESULT WINAPI INF_NET_GetList(long lLoginHandle,DWORD dwInfoContol,BYTE** pBuf,LONG& lBufSize);
C# 中函数
[DllImport(_strCoreSDK)] public static extern int INF_NET_GetList(Int32 lLoginHandle, UInt32 dwInfoContol,IntPtr _Deviceinfoptr, ref Int32 lBufSize);
说明:常见的数值类型,正常转化;指针类型、引用类型直接转化为ref 数值类型。其中byte ** ,这种类型根据在c++中调用,是存储数组的地址,所以在单纯的用ref,不能解决问题。针对此问题,都可以用intptr,关键就是intptr怎么指向,下面重点讲讲如何结构体怎么访问。
在函数调用时为:
iErr = INF_NET_GetList(g_lHandle, 5, (BYTE**)&m_pMonitorInfo, lMonSize);
其中m_pMonitorInfo是结构体数组,所以此函数是获取一系列结构体,针对此,在c#函数中相对应声明为Intptr。
C#中看看如何利用Intptr,传入地址,还有获取数据。
int iDev_size = Marshal.SizeOf(typeof(INFINOVASDK.INF_NET_DEVICE_INFO)); //定义一个存储指针的数组 IntPtr[] _IntptrDevArray = new IntPtr[1]; //初始化指针数组大小 _IntptrDevArray[0] = Marshal.AllocHGlobal(iDev_size * 100);//分配包含100元素的数组 //查询设备指针 IntPtr _IntptrDevInfo = Marshal.AllocHGlobal(iDev_size); //存储设备指针于数组指针 Marshal.Copy(_IntptrDevArray, 0, _IntptrDevInfo, 1);//拷贝指针数组 int buffeesize = 0; int err = INFINOVASDK.INF_NET_GetList(_loginIds[control], 3, _IntptrDevInfo, ref buffeesize); if (err != 0) { return null; } //从指针数组开始获取数据 for (int i = 0; i < 100; i++) { //设定好指针的偏移量 INFINOVASDK.INF_NET_DEVICE_INFO _deviceInfo = (INFINOVASDK.INF_NET_DEVICE_INFO)Marshal.PtrToStructure((IntPtr)((UInt32)_IntptrDevArray[0] + i * iDev_size), typeof(INFINOVASDK.INF_NET_DEVICE_INFO)); if (_deviceInfo.cDevIP != null) { //获取设备的Ip以及MAC bytes_DevIp = _deviceInfo.cDevIP; bytes_Mac = _deviceInfo.cDevMac; break; } } //释放申请的内存空间 Marshal.FreeHGlobal(_IntptrDevInfo); Marshal.FreeHGlobal(_IntptrDevArray[0]);
下面讲讲另一个函数
c++函数
INF_NET_API INF_RESULT WINAPI INF_NET_FindFile (long & lFindHandle, INF_HANDLE lLoginHandle, LPINDEX_INFO pFindCondition,LPINDEX_INFO pRecvIndex);
上面函数中后两个参数,类型都是结构体指针,但是在函数调用的时,不一样。倒数第二个是传入结构体的指针,指向一个结构体。倒数第一个指向结构体数组的,是 获取的结构体数组。
[DllImport(_strCoreSDK)] public static extern int INF_NET_FindFile(ref Int32 lFindHandle, Int32 lLoginHandle, IntPtr FIleptr, IntPtr RecordFileInfo);
接口函数,按照说明进行声明。
C#函数调用,此方法和上面还不太相同。
//初始化查询录像文件对象
INFINOVASDK.NET_INDEX_INFO _FindFileInfo = new INFINOVASDK.NET_INDEX_INFO(); _FindFileInfo.dwStartTime = (uint)DatetimeToLong(startTime); _FindFileInfo.dwEndTime = (uint)DatetimeToLong(endTime); //查询条件,全部文件 _FindFileInfo.btFileType = (byte)0xFF; //摄像机ID _FindFileInfo.dwIP4 = UInt32.Parse(cameraId); //平台设备的Ip、以及MAc _FindFileInfo.dwIP1 = BitConverter.ToUInt32(bytes_DevIp, 0); _FindFileInfo.wChan = UInt16.Parse(devicechn); _FindFileInfo.btMAC = bytes_Mac; _FindFileInfo.dwReserved = 0; _FindFileInfo.Reserved = new byte[2];//此处应初始化,防止向指针赋值时出错。 try { List<FileItem> files = new List<FileItem>(); //开始获取录像文件,并定义最多取100个文件 //开始设置查询录像文件的指针 int iIndexSize = Marshal.SizeOf(typeof(INFINOVASDK.NET_INDEX_INFO)); IntPtr _IntptrInedx = Marshal.AllocHGlobal(iIndexSize); Marshal.StructureToPtr(_FindFileInfo, _IntptrInedx, false);//在用Marshal类进行托管对象和非托管对象的转换时,会有如下错误提示:“未能封送类型,因为嵌入数组实例的长度与布局中声明的长度不匹配。”所以针对结构体内数组要初始化,再赋值。看看赋值结构体内数组大小和初始化时结构体内数组大小是否相等。 //定义存储录像文件数组,并初始化 INFINOVASDK.NET_INDEX_INFO[] _ArrayFile = new INFINOVASDK.NET_INDEX_INFO[1024 * 10]; for (int i = 0; i < _ArrayFile.Length; i++) {//结构体初始化,防止出错 _ArrayFile[i] = new INFINOVASDK.NET_INDEX_INFO(); _ArrayFile[i].Reserved = new byte[2];//针对数组专门初始化,关键是指明大小。 _ArrayFile[i].btMAC = new byte[6]; } //录像文件数组定义个指针,并在内存空间申请的大小 IntPtr _IntptrRecordFiles = Marshal.AllocHGlobal(iIndexSize * 1024 * 10); //查询录像文件 int iFindhandle = 0; _errorCode = INFINOVASDK.INF_NET_FindFile(ref iFindhandle, _loginIds[control], _IntptrInedx, _IntptrRecordFiles); if (_errorCode != 0) { return null; } for (int i = 0; i < 10240; i++) { _ArrayFile[i] = (INFINOVASDK.NET_INDEX_INFO)Marshal.PtrToStructure((IntPtr)((UInt32)_IntptrRecordFiles + i * iIndexSize), typeof(INFINOVASDK.NET_INDEX_INFO)); if (_ArrayFile[i].dwStartTime != 0) { _NetRecordFiles.Add(_ArrayFile[i]); //获取录像文件的起止时间 DateTime tempstart = LongToDateTime(_ArrayFile[i].dwStartTime);//将datetime格式转化为int类型。 DateTime tempend = LongToDateTime(_ArrayFile[i].dwEndTime); string strstarttime = tempstart.ToString("yyyyMMddHHmmss"); string strendtime = tempend.ToString("yyyyMMddHHmmss"); FileItem _RecordFile = new FileItem(); _RecordFile.FileName = strstarttime + "_" + strendtime; _RecordFile.StartTime = tempstart; _RecordFile.EndTime = tempend; files.Add(_RecordFile); } } Marshal.FreeHGlobal(_IntptrInedx); Marshal.FreeHGlobal(_IntptrRecordFiles); return files; }
这个是之前复杂版本。
byte []_devipBytes = new byte [20]; byte []_macbytes = new byte [6]; //此前结构体内将char[]改为了string,结构看到数据是乱码,所以需要进行数据转化。 byte[] bytes = Encoding.UTF8.GetBytes(_deviceInfo.cDevIP); //_deviceInfo.cDevIP是string类型 //编码转化 byte[] tempdef = Encoding.Convert(Encoding.UTF8, Encoding.Default, bytes); //此处你会发现_devipBytes 原先大小为20,此时由于tempdef;为4,然后此时_devipBytes 也为4,这样是不对的。 _devipBytes = tempdef; byte[] bytes1 = Encoding.UTF8.GetBytes(_deviceInfo.cDevMac); byte[] tempdef1 = Encoding.Convert(Encoding.UTF8, Encoding.Default, bytes1); //所以byte复制到byte,用此方法,这样不会改便byte[]的大小 Array.Copy(tempdef1, _macbytes, 6);
如果想byte转到string,可以一个个获取。
C# 默认的编码方式是Unicode,而调用的DLL规定只处理UTF8编码格式的字符串,DLL中的输入参数类型char*被我Marshal成byte[],输出参数类型char**被我Marshal成了string。用于接收时的string-->string(UTF8-->Unicode),会发现接受的数据string时为乱码,所以还要转化为char[],才能看到正确数据。用于发送时,还需要string-->byte[](Unicode-->UTF8)这样频繁的编码转换,看来编码转换并非想象中那么简单:所以针对这种现场,当char * 作为传入数据时,直接处理为char * 》》string。
(一)、Encoding和CharSet
在C#中包装DLL的时候,DllImportAttribute当中的选项CharSet着:规定封送字符串应使用何种字符集,其中枚举值有Ansi和Unicode,CharSet是字符集,用于字符传送,Encoding代表编码方式,用于数据转化。字符集是字符的集合,规定这个集合里有哪些字符,每个字符都有一个整数编号(只是编号不是编码);而编码是用来规定字符编号如何与二进制交互,每个“字符”分别用一个字节还是多个字节存储。CharSet表明在数据封送的字符集以什么方式封送,然后在获取到数据为乱码时,则通过Encoding里面的函数,将数据转化,则获取到正确数据。
(二)、Ansi、Unicode、UTF8、bala bala
提到字符集,有ASCII、GB2312、GBK、GB18030、BIG5、JIS等等多种,与此相对应的编码方式为ASCII、GB2312、GBK、GB18030、BIG5、JIS,但是Unicode字符集却有多种编码方式:UTF-8、 UTF-7、UTF-16、 UnicodeLittle、UnicodeBig,意味可以讲数据按照上述编码方式进行转化。 Ansi:系统编码的发展经历了三个阶段,ASCIIàAnsi(不同国家语言本地化)àUnicode(标准化),原来Ansi编码也好,Ansi字符集也好,都是指本地化的东西,在简体中文系统下,ANSI
编码代表 GB2312 编码,Windows下自带的记事本程序,默认的就是ANSI编码。输入“anhui合肥”(不含引号),保存编码方式选“ANSI”,查看,哦,9个字节,明白了,原来Ansi编码保留了对ASCII编码的兼容,当遇到ASCII字符时,采用单字节存储,当遇到非ASCII编码时,采用双字节表示(GB2312编码)。
(三)、DllImportAttribute中的CharSet枚举值选择
对字符集和编码的概念清楚了以后,终于可以研究C#中调用非托管的DLL的方法咯。从托管应用程序去调用非托管代码,如果CharSet=Unicode,则DLL中的接口函数将出现的所有字符串(包括参数和返回值)视为Unicode字符集,Ansi一样的道理
第一:我需要调用非托管代码,第二:非托管代码只处理UTF8编码格式的字符,第三,万恶的中文乱码,接口函数签名中,无论参数还是返回值,接收到或是发送出的字符串都含有中文。首先接收字符串,因为接收到的是UTF8编码格式,CharSet属性肯定要设置成Unicode了,接收后要正确显示中文,在当前系统中,需要将编码方式转换成GB2312,即Encoding.Default;另外关于发送字符数组,因为无论是C#中默认的Unicode编码方式,还是DLL处理时规定的UTF8编码方式,都是Unicode字符集的一种编码方式,所以CharSet也要设置成Unicode,只是要在发送前需要将字符数组转换一下编码方式。以下附带两个简单的函数实现。
// 转换接收到的字符串
public string UTF8ToUnicode(string recvStr) { byte[] tempStr = Encoding.UTF8.GetBytes(recvNotify); byte[] tempDef = Encoding.Convert(Encoding.UTF8, Encoding.Default, tempStr); string msgBody = Encoding.Default.GetString(tempDef); return msgBody; } // 转换要发送的字符数组 public byte[] UnicodeToUTF8(string sendStr) { string tempStr = Encoding.UTF8.GetString(sendStr); byte[] msgBody = Encoding.UTF8.GetBytes(tempUTF8); return msgBody; }
下面是常用的几个函数
private DateTime LongToDateTime(uint times) { long _times =(long)times + 28800; DateTime ds = new DateTime(1970, 1, 1, 0, 0, 0,DateTimeKind.Unspecified).AddSeconds(_times); return ds; } private byte [] StructToBytes(Object structobj , int size) { byte []tempBytes = new byte [size]; IntPtr strcutIntptr = Marshal.AllocHGlobal(size); //将结构体拷贝到内存中 Marshal.StructureToPtr(structobj, strcutIntptr, false); //从内存空间拷贝到byte数组中 Marshal.Copy(strcutIntptr, tempBytes, 0, size); Marshal.FreeHGlobal(strcutIntptr); return tempBytes; } private object BytesToStruct(byte[] bytes ,Type _type) { int _size = Marshal.SizeOf(_type); IntPtr structInnptr = Marshal.AllocHGlobal(_size); Marshal.Copy(bytes, 0, structInnptr, _size); object obj = Marshal.PtrToStructure(structInnptr, _type); Marshal.FreeHGlobal(structInnptr); return obj; } private byte [] IntptrToBytes (IntPtr tempIntptr ,int _size) { byte[] tempBytes = new byte[_size]; //从内存空间拷贝到byte数组中 Marshal.Copy(tempIntptr, tempBytes, 0, _size); return tempBytes; } private IntPtr BytesToInptr(byte[] bytes, Type _type) { int _size = Marshal.SizeOf(_type); IntPtr structInnptr = Marshal.AllocHGlobal(_size); Marshal.Copy(bytes, 0, structInnptr, _size); return structInnptr; }