Java自学者论坛

 找回密码
 立即注册

手机号码,快捷登录

恭喜Java自学者论坛(https://www.javazxz.com)已经为数万Java学习者服务超过8年了!积累会员资料超过10000G+
成为本站VIP会员,下载本站10000G+会员资源,会员资料板块,购买链接:点击进入购买VIP会员

JAVA高级面试进阶训练营视频教程

Java架构师系统进阶VIP课程

分布式高可用全栈开发微服务教程Go语言视频零基础入门到精通Java架构师3期(课件+源码)
Java开发全终端实战租房项目视频教程SpringBoot2.X入门到高级使用教程大数据培训第六期全套视频教程深度学习(CNN RNN GAN)算法原理Java亿级流量电商系统视频教程
互联网架构师视频教程年薪50万Spark2.0从入门到精通年薪50万!人工智能学习路线教程年薪50万大数据入门到精通学习路线年薪50万机器学习入门到精通教程
仿小米商城类app和小程序视频教程深度学习数据分析基础到实战最新黑马javaEE2.1就业课程从 0到JVM实战高手教程MySQL入门到精通教程
查看: 631|回复: 0

c#调用delphi写的dll遇到并解决的问题

[复制链接]
  • TA的每日心情
    奋斗
    2024-11-24 15:47
  • 签到天数: 804 天

    [LV.10]以坛为家III

    2053

    主题

    2111

    帖子

    72万

    积分

    管理员

    Rank: 9Rank: 9Rank: 9

    积分
    726782
    发表于 2021-5-16 16:57:59 | 显示全部楼层 |阅读模式

    背景

          有个项目需要调用别人delphi写的dll,里面有多个方法,有方法的参数需要传结构体的指针,或者结构体的二级指针,用c#调用的过程中费了一番功夫,所以觉得有必要记录一下。 

    参数包含一级指针的:

    Delphi中定义的结构体:
    1 type
    2   PUserInfo = ^UserInfo;
    3   UserInfo = packed record
    4     nCardNo : Cardinal;           
    5     nBalance : Integer;           
    6     aName : array [0..19] of char;
    7   end;

    UserInfo是一个包含3个成员变量的结构体,PUserInfo是指向该结构体的指针。

    Delphi方法入口:

    1 //获取用户信息
    2 //输入: nCardNo 卡号, pUserInfo 用户信息指针
    3 //输出: pUserInfo 指定用户信息
    4 //返回: 用户帐号,0 没找到
    5 function GetUserInfoByCardNo(nCardNo : Cardinal; pUserInfo : PUserInfo) : Integer; stdcall;

    上面分别是Delphi中定义的结构体和方法,下面看c#如何实现调用:

    c#定义结构体:

    1 [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
    2         public struct UserInfo
    3         {
    4             public uint nCardNo;
    5             public Int32 nBalance;
    6             [MarshalAs(UnmanagedType.ByValArray, SizeConst = 20)]
    7             public char[] aName;
    8         }

    这里要加上Pack=1是涉及到一个内存对齐的问题,内存对齐属于编译器的管辖范围,深层的原理我这种小白也不是很明白,只知道delphi和c#不同的编译机制导致如果不指明内存对齐方式,那么当需要取出一个数组数据时,会出问题,我一开始就因为没加这个出了问题。至于charset编码方式,也要视具体情况而定。

    c#方法实现:

     1    //声明方法
     2     [DllImport("yourdelphi.dll")]
     3      public extern int GetUserInfoByCardNo(uint nCardNo, IntPtr inptr);
     4         
     5     //实现过程
     6     public UserInfo GetUser(string id)
     7         {
     8             UserInfo rui = new UserInfo();
     9             int len = Marshal.SizeOf(rui);//计算对象大小
    10             IntPtr ptr = Marshal.AllocHGlobal(len);//从非托管内存中分配内存
    11             Marshal.StructureToPtr(rui, ptr, true);//将数据从托管对象封送到非托管内存块
    12             int res = 0;
    13             try
    14             {
    15                 uint cardid = Convert.ToUInt32(id, 16);
    16                 res = GetUserInfoByCardNo(cardid, ptr);//调用声明的方法
    17              //将数据从非托管内存块封送到新分配的指定类型的托管对象
    18                 rui = (UserInfo)Marshal.PtrToStructure(ptr, typeof(UserInfo));
    19                 Marshal.FreeHGlobal(ptr);//释放从非托管内存中分配的内存
    20                 return rui;
    21             }
    22             catch (Exception ex)
    23             {
    24                 throw ex;
    25             }
    26         }

    其实实现的关键点就在于delphi中需要的指针在c#中如何定义,这里通过使用Inptr这个类型就可以,当然用ref关键字应该也是可以的。

    参数包含二级指针的:

    Delphi中定义的结构体:
     1 type
     2   PPLogInfo = ^PLogInfo;
     3   PLogInfo = ^LogInfo;
     4   LogInfo = packed record
     5     nLogID : Cardinal;
     6     nChange : Integer;
     7     nAmount : Integer;
     8     nBalance : Integer;
     9     aName : array [0..15] of Char;
    10   end;
    LogInfo是一个包含5个成员的结构体,PLogInfo是指向结构体的指针,PPLogInfo是指向PLogInfo的指针。后面的方法就需要传递PPLogInfo这个二级指针。

     Delphi方法入口:

    1 //获取流水
    2 //输入: nDate 最后日期, nDay 向前的天数 ppData 数据指针的指针
    3 //输出: 无
    4 //返回: 流水条数
    5 function GetLog(nDate : Cardinal; nDay : Cardinal; ppData : PPLogInfo) : Cardinal; stdcall;

    c#如何实现调用:

    c#定义结构体:

     1     [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]   
     2         public struct LogInfo
     3         {
     4             public uint nLogID;
     5             public int nChange;
     6             public int nAmount;
     7             public int nBalance;
     8             [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)]
     9             public char[] aName;
    10         }

    c#方法实现:

     1     //声明方法
     2     [DllImport("yourdelphi.dll")]
     3     public extern uint GetLog(int nDate, int nDay, ref IntPtr ryIntPtr);
     4         
     5     //实现过程
     6     public LogInfo[] GetWaterLog(DateTime date,int nday)
     7         {
     8             LogInfo ryLogInfo = new LogInfo();
     9             int len = Marshal.SizeOf(typeof(LogInfo));//计算对象大小
    10             IntPtr ptr = Marshal.AllocHGlobal(len);//从非托管内存中分配内存(此处内存大小应该不重要)
    11             Marshal.StructureToPtr(ryLogInfo, ptr, true);//将数据从托管对象封送到非托管内存块
    12             int nDate = ConvertDateTimeInt(date);//日期转换为需要的格式
    13             try
    14             {
    15                 uint result = GetLog(nDate, nday, ref ptr);//方法调用,得到流水条数
    16                 LogInfo[] ryLogInfos = new LogInfo[result];//声明一个结构体的数组,用来存放流水
    17              //循环取出每条流水数据
    18                 for (int i = 0; i < result; i++)
    19                 {
    20                  //每循环一次,就把指针移到下一条流水的位置
    21                     IntPtr infoPtr = (IntPtr)((uint)ptr + i * len);
    22                     ryLogInfos = (LogInfo)Marshal.PtrToStructure(infoPtr, typeof(LogInfo));
    23                 }
    24                 FreeLog(pro);//释放流水数据,另一个delphi接口实现的
    25                 return ryLogInfos;
    26             }
    27             catch (Exception ex)
    28             {
    29                 throw ex;
    30             }
    31         }    
         和一级指针不同的是,二级指针在Inptr的基础上再加一个ref关键字就行了,也不是想象的那么复杂,可是要探究一下这后面的机制也有点学问。
     
         我简单研究了一下指针的概念,知道二级指针在需要动态分配内存的场景中中运用比较多,也就是说当我把二级指针作为一个参数传到方法里,方法对指针的操作要能够影响我传递指针之前的变量,方法结束后改变依然有效。
     
         这也基本解释了我的一个疑惑,就是这个delphi方法为什么要用二级指针做参数,我觉得应该是因为在调这个方法的时候我是不知道流水的条数的,不知道流水条数就没办法事先分配好内存。所以通过二级指针,让delphi方法去给我动态分配内存,所以方法有个地方写着“此处内存大小应该不重要”就是这个意思,因为反正也是要再重新分配的。
     
         既然内存是delphi方法分配的,那自然也要由delphi方法来释放,事实确实如此,因为我用c#试着去释放的时候报错了。这时候再去看delphi那些接口,果然还有一个专门用来释放流水的接口。
        
        虽然问题解决了,但是对delphi这门小众但强大的语言或者说对指针、内存的一些理解还是很浅显。文中有说的不对的地方,欢迎指正。
     
       The end.
    哎...今天够累的,签到来了1...
    回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    QQ|手机版|小黑屋|Java自学者论坛 ( 声明:本站文章及资料整理自互联网,用于Java自学者交流学习使用,对资料版权不负任何法律责任,若有侵权请及时联系客服屏蔽删除 )

    GMT+8, 2024-12-23 06:22 , Processed in 0.068073 second(s), 29 queries .

    Powered by Discuz! X3.4

    Copyright © 2001-2021, Tencent Cloud.

    快速回复 返回顶部 返回列表