c#調用delphi寫的dll遇到並解決的問題


背景

      有個項目需要調用別人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[i] = (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.


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM