c#调用delphi写的dll遇到并解决的问题
2017-01-06 16:53
555 查看
写下这篇文字对我来说意义非凡,因为一直想养成写博客的习惯却因为懒一直未做,能把工作中做过的东西拿出来讲给需要的人,也是一件好事。
UserInfo是一个包含3个成员变量的结构体,PUserInfo是指向该结构体的指针。
Delphi方法入口:
上面分别是Delphi中定义的结构体和方法,下面看c#如何实现调用:
c#定义结构体:
c#方法实现:
Delphi方法入口:
c#如何实现调用:
c#定义结构体:
c#方法实现:
和一级指针不同的是,二级指针在Inptr的基础上再加一个ref关键字就行了,也不是想象的那么复杂,可是要探究一下这后面的机制也有点学问。
我简单研究了一下指针的概念,知道二级指针在需要动态分配内存的场景中中运用比较多,也就是说当我把二级指针作为一个参数传到方法里,方法对指针的操作要能够影响我传递指针之前的变量,方法结束后改变依然有效。
这也基本解释了我的一个疑惑,就是这个delphi方法为什么要用二级指针做参数,我觉得应该是因为在调这个方法的时候我是不知道流水的条数的,不知道流水条数就没办法事先分配好内存。所以通过二级指针,让delphi方法去给我动态分配内存,所以方法有个地方写着“此处内存大小应该不重要”就是这个意思,因为反正也是要再重新分配的。
既然内存是delphi方法分配的,那自然也要由delphi方法来释放,事实确实如此,因为我用c#试着去释放的时候报错了。这时候再去看delphi那些接口,果然还有一个专门用来释放流水的接口。
虽然问题解决了,但是对delphi这门小众但强大的语言或者说对指针、内存的一些理解还是很浅显。文中有说的不对的地方,欢迎指正。
The end.
背景
有个项目需要调用别人delphi写的dll,里面有多个方法,有方法的参数需要传结构体的指针,或者结构体的二级指针,用c#调用的过程中费了一番功夫,所以觉得有必要记录一下。参数包含一级指针的:
Delphi中定义的结构体:type PUserInfo = ^UserInfo; UserInfo = packed record nCardNo : Cardinal; nBalance : Integer; aName : array [0..19] of char; end;
UserInfo是一个包含3个成员变量的结构体,PUserInfo是指向该结构体的指针。
Delphi方法入口:
//获取用户信息 //输入: nCardNo 卡号, pUserInfo 用户信息指针 //输出: pUserInfo 指定用户信息 //返回: 用户帐号,0 没找到 function GetUserInfoByCardNo(nCardNo : Cardinal; pUserInfo : PUserInfo) : Integer; stdcall;
上面分别是Delphi中定义的结构体和方法,下面看c#如何实现调用:
c#定义结构体:
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)] public struct UserInfo { public uint nCardNo; public Int32 nBalance; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 20)] public char[] aName; }这里要加上Pack=1是涉及到一个内存对齐的问题,内存对齐属于编译器的管辖范围,深层的原理我这种小白也不是很明白,只知道delphi和c#不同的编译机制导致如果不指明内存对齐方式,那么当需要取出一个数组数据时,会出问题,我一开始就因为没加这个出了问题。至于charset编码方式,也要视具体情况而定。
c#方法实现:
//声明方法 [DllImport("yourdelphi.dll")] public extern int GetUserInfoByCardNo(uint nCardNo, IntPtr inptr); //实现过程 public UserInfo GetUser(string id) { UserInfo rui = new UserInfo(); int len = Marshal.SizeOf(rui);//计算对象大小 IntPtr ptr = Marshal.AllocHGlobal(len);//从非托管内存中分配内存 Marshal.StructureToPtr(rui, ptr, true);//将数据从托管对象封送到非托管内存块 int res = 0; try { uint cardid = Convert.ToUInt32(id, 16); res = GetUserInfoByCardNo(cardid, ptr);//调用声明的方法 //将数据从非托管内存块封送到新分配的指定类型的托管对象 rui = (UserInfo)Marshal.PtrToStructure(ptr, typeof(UserInfo)); Marshal.FreeHGlobal(ptr);//释放从非托管内存中分配的内存 return rui; } catch (Exception ex) { throw ex; } }其实实现的关键点就在于delphi中需要的指针在c#中如何定义,这里通过使用Inptr这个类型就可以,当然用ref关键字应该也是可以的。
参数包含二级指针的:
Delphi中定义的结构体:type PPLogInfo = ^PLogInfo; PLogInfo = ^LogInfo; LogInfo = packed record nLogID : Cardinal; nChange : Integer; nAmount : Integer; nBalance : Integer; aName : array [0..15] of Char; end;LogInfo是一个包含5个成员的结构体,PLogInfo是指向结构体的指针,PPLogInfo是指向PLogInfo的指针。后面的方法就需要传递PPLogInfo这个二级指针。
Delphi方法入口:
//获取流水 //输入: nDate 最后日期, nDay 向前的天数 ppData 数据指针的指针 //输出: 无 //返回: 流水条数 function GetLog(nDate : Cardinal; nDay : Cardinal; ppData : PPLogInfo) : Cardinal; stdcall;
c#如何实现调用:
c#定义结构体:
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)] public struct LogInfo { public uint nLogID; public int nChange; public int nAmount; public int nBalance; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] public char[] aName; }
c#方法实现:
//声明方法 [DllImport("yourdelphi.dll")] public extern uint GetLog(int nDate, int nDay, ref IntPtr ryIntPtr); //实现过程 public LogInfo[] GetWaterLog(DateTime date,int nday) { LogInfo ryLogInfo = new LogInfo(); int len = Marshal.SizeOf(typeof(LogInfo));//计算对象大小 IntPtr ptr = Marshal.AllocHGlobal(len);//从非托管内存中分配内存(此处内存大小应该不重要) Marshal.StructureToPtr(ryLogInfo, ptr, true);//将数据从托管对象封送到非托管内存块 int nDate = ConvertDateTimeInt(date);//日期转换为需要的格式 try { uint result = GetLog(nDate, nday, ref ptr);//方法调用,得到流水条数 LogInfo[] ryLogInfos = new LogInfo[result];//声明一个结构体的数组,用来存放流水 //循环取出每条流水数据 for (int i = 0; i < result; i++) { //每循环一次,就把指针移到下一条流水的位置 IntPtr infoPtr = (IntPtr)((uint)ptr + i * len); ryLogInfos[i] = (LogInfo)Marshal.PtrToStructure(infoPtr, typeof(LogInfo)); } FreeLog(pro);//释放流水数据,另一个delphi接口实现的 return ryLogInfos; } catch (Exception ex) { throw ex; } }
和一级指针不同的是,二级指针在Inptr的基础上再加一个ref关键字就行了,也不是想象的那么复杂,可是要探究一下这后面的机制也有点学问。
我简单研究了一下指针的概念,知道二级指针在需要动态分配内存的场景中中运用比较多,也就是说当我把二级指针作为一个参数传到方法里,方法对指针的操作要能够影响我传递指针之前的变量,方法结束后改变依然有效。
这也基本解释了我的一个疑惑,就是这个delphi方法为什么要用二级指针做参数,我觉得应该是因为在调这个方法的时候我是不知道流水的条数的,不知道流水条数就没办法事先分配好内存。所以通过二级指针,让delphi方法去给我动态分配内存,所以方法有个地方写着“此处内存大小应该不重要”就是这个意思,因为反正也是要再重新分配的。
既然内存是delphi方法分配的,那自然也要由delphi方法来释放,事实确实如此,因为我用c#试着去释放的时候报错了。这时候再去看delphi那些接口,果然还有一个专门用来释放流水的接口。
虽然问题解决了,但是对delphi这门小众但强大的语言或者说对指针、内存的一些理解还是很浅显。文中有说的不对的地方,欢迎指正。
The end.
相关文章推荐
- c#调用Delphi的dll函数遇到的问题及解决方法
- VC调用Delphi的DLL时遇到的问题及解决
- c#调用Delphi的dll函数遇到的问题及解决方法
- VC调用Delphi的DLL时遇到的问题及解决
- Delphi6 调用 C# Dll 时一个编译问题
- Delphi 调用 C# Dll 时一个编译问题 mscorlib_TLB.h Ambiguity between 'String' and 'System::String'
- C#调用delphi dll接口问题
- delphi 开发中遇到的DLL问题思考及解决方法
- 解决windows2008 下iis c#调用dll问题
- C#调用C++Dll封装时遇到的一系列问题
- C#调用C++Dll封装时遇到的一系列问题
- 续:Java Tomcat 中调用.net DLL的方法 - 实际部署中遇到的一些问题解决
- C#调用C++Dll封装时遇到的一系列问题
- C# 调用delphi生成Dll的相关问题
- C# 调用 C++ DLL无法调试的问题解决方法
- C#调用C++Dll封装时遇到的一系列问题
- C#调用C++Dll封装时遇到的一系列问题
- C#调用C++Dll封装时遇到的一系列问题
- C#调用C++Dll封装时遇到的一系列问题
- Delphi调用C#dll的问题