本文的内容接着C#调用DLL函数方法(上)。

因为C#中使用DllImport是不能像动态load/unload assembly那样,所以只能借助API函数了。在kernel32.dll中,与动态库调用有关的函数包括[3]:

①LoadLibrary(或MFC 的AfxLoadLibrary),装载动态库。

②GetProcAddress,获取要引入的函数,将符号名或标识号转换为DLL内部地址。

③FreeLibrary(或MFC的AfxFreeLibrary),释放动态链接库。

它们的原型分别是:

HMODULE LoadLibrary(LPCTSTR lpFileName);
FARPROC GetProcAddress(HMODULE hModule, LPCWSTR lpProcName);
BOOL FreeLibrary(HMODULE hModule);

现在,我们可以用IntPtr hModule=LoadLibrary(“Count.dll”);来获得Dll的句柄,用IntPtr farProc=GetProcAddress(hModule,”_count@4”);来获得函数的入口地址。

但是,知道函数的入口地址后,怎样调用这个函数呢?因为在C#中是没有函数指针的,没有像C++那样的函数指针调用方式来调用函数,所以我们得借助其它方法。经过研究,发现我们可以通过结合使用System.Reflection.Emit及System.Reflection.Assembly里的类和函数达到我们的目的。为了以后使用方便及实现代码的复用,我们可以编写一个类。

1) dld类的编写:

1.打开项目“Test”,打开类视图,右击“Tzb”,选择“添加”-->“类”,类名设置为“dld”,即dynamic loading dll 的每个单词的开头字母。

2.添加所需的命名空间及声明参数传递方式枚举:

1. using System.Runtime.InteropServices; // 用DllImport 需用此命名空间  
2.  
3. using System.Reflection; // 使用Assembly 类需用此命名空间  
4.  
5. using System.Reflection.Emit; // 使用ILGenerator 需用此命名空间

3. 在namespace test中,“public class dld”的上面,添加如下代码声明参数传递方式枚举:

 

1. /// < summary>   
2.  
3.     /// 参数传递方式枚举,ByValue 表示值传递,ByRef 表示址传递  
4.  
5.     /// < /summary>   
6.  
7.     public enum ModePass  
8.  
9.     {  
10.  
11.         ByValue = 0x0001,  
12.  
13.         ByRef = 0x0002  
14.  
15. }  
16.

4、在public class DLD中,添加如下代码:

1. public class DLD  
2.  
3.     {  
4.  
5.         [DllImport("kernel32.dll")]  
6.  
7.         public static extern IntPtr LoadLibrary(string lpFileName);  
8.  
9.    
10.  
11.         [DllImport("kernel32.dll")]  
12.  
13.         public static extern IntPtr GetProcAddress(IntPtr hModule, string lpProceName);  
14.  
15.    
16.  
17.         [DllImport("kernel32", EntryPoint = "FreeLibrary", SetLastError = true)]  
18.  
19.         public static extern bool FreeLibrary(IntPtr hModule);  
20.  
21.    
22.  
23.         /// < summary>   
24.  
25.    
26.  
27.         /// Loadlibrary 返回的函数库模块的句柄   
28.  
29.    
30.  
31.         /// < /summary>   
32.  
33.    
34.  
35.         private IntPtr hModule = IntPtr.Zero;  
36.  
37.    
38.  
39.         /// < summary>   
40.  
41.    
42.  
43.         /// GetProcAddress 返回的函数指针   
44.  
45.    
46.  
47.         /// < /summary>   
48.  
49.    
50.  
51.         private IntPtr farProc = IntPtr.Zero;  
52.  
53.    
54.  
55.         /// < summary>   
56.  
57.    
58.  
59.         /// 装载 Dll   
60.  
61.    
62.  
63.         /// < /summary>   
64.  
65.    
66.  
67.         /// < param name="lpFileName">DLL 文件名 < /param>   
68.  
69.    
70.  
71.         public void LoadDll(string lpFileName)  
72.  
73.         {  
74.  
75.    
76.  
77.             hModule = LoadLibrary(lpFileName);  
78.  
79.    
80.  
81.             if (hModule == IntPtr.Zero)  
82.  
83.    
84.  
85.                 throw (new Exception(" 没有找到 :" + lpFileName + "."));  
86.  
87.         }  
88.  
89.    
90.  
91.    
92.  
93.         /// < summary>   
94.  
95.    
96.  
97.         /// 获得函数指针   
98.  
99.    
100.  
101.         /// < /summary>   
102.  
103.    
104.  
105.         /// < param name="lpProcName"> 调用函数的名称 < /param>   
106.  
107.    
108.  
109.         public void LoadFun(string lpProcName)  
110.  
111.         { // 若函数库模块的句柄为空,则抛出异常   
112.  
113.    
114.  
115.             if (hModule == IntPtr.Zero)  
116.  
117.    
118.  
119.                 throw (new Exception(" 函数库模块的句柄为空 , 请确保已进行 LoadDll 操作 !"));  
120.  
121.    
122.  
123.             // 取得函数指针   
124.  
125.    
126.  
127.             farProc = GetProcAddress(hModule, lpProcName);  
128.  
129.    
130.  
131.             // 若函数指针,则抛出异常   
132.  
133.    
134.  
135.             if (farProc == IntPtr.Zero)  
136.  
137.    
138.  
139.                 throw (new Exception(" 没有找到 :" + lpProcName + " 这个函数的入口点 "));  
140.  
141.    
142.  
143.         }  
144.  
145.    
146.  
147.         /// < summary>   
148.  
149.    
150.  
151.         /// 卸载 Dll   
152.  
153.    
154.  
155.         /// < /summary>   
156.  
157.    
158.  
159.         public void UnLoadDll()  
160.  
161.         {  
162.  
163.    
164.  
165.             FreeLibrary(hModule);  
166.  
167.    
168.  
169.             hModule = IntPtr.Zero;  
170.  
171.    
172.  
173.             farProc = IntPtr.Zero;  
174.  
175.    
176.  
177.         }  
178.  
179.    
180.  
181.    
182.  
183.         /// < summary>   
184.  
185.    
186.  
187.         /// 调用所设定的函数   
188.  
189.    
190.  
191.         /// < /summary>   
192.  
193.    
194.  
195.         /// < param name="ObjArray_Parameter"> 实参 < /param>   
196.  
197.    
198.  
199.         /// < param name="TypeArray_ParameterType"> 实参类型 < /param>   
200.  
201.    
202.  
203.         /// < param name="ModePassArray_Parameter"> 实参传送方式 < /param>   
204.  
205.    
206.  
207.         /// < param name="Type_Return"> 返回类型 < /param>   
208.  
209.    
210.  
211.         /// < returns> 返回所调用函数的 object< /returns>   
212.  
213.    
214.  
215.         public object Invoke(object[] ObjArray_Parameter, Type[] TypeArray_ParameterType, ModePass[] ModePassArray_Parameter, Type Type_Return)  
216.  
217.         {  
218.  
219.    
220.  
221.             // 下面 3 个 if 是进行安全检查 , 若不能通过 , 则抛出异常   
222.  
223.    
224.  
225.             if (hModule == IntPtr.Zero)  
226.  
227.    
228.  
229.                 throw (new Exception(" 函数库模块的句柄为空 , 请确保已进行 LoadDll 操作 !"));  
230.  
231.    
232.  
233.             if (farProc == IntPtr.Zero)  
234.  
235.    
236.  
237.                 throw (new Exception(" 函数指针为空 , 请确保已进行 LoadFun 操作 !"));  
238.  
239.    
240.  
241.             if (ObjArray_Parameter.Length != ModePassArray_Parameter.Length)  
242.  
243.    
244.  
245.                 throw (new Exception(" 参数个数及其传递方式的个数不匹配 ."));  
246.  
247.    
248.  
249.             // 下面是创建 MyAssemblyName 对象并设置其 Name 属性   
250.  
251.    
252.  
253.             AssemblyName MyAssemblyName = new AssemblyName();  
254.  
255.    
256.  
257.             MyAssemblyName.Name = "InvokeFun";  
258.  
259.    
260.  
261.             // 生成单模块配件   
262.  
263.    
264.  
265.             AssemblyBuilder MyAssemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(MyAssemblyName, AssemblyBuilderAccess.Run);  
266.  
267.    
268.  
269.             ModuleBuilder MyModuleBuilder = MyAssemblyBuilder.DefineDynamicModule("InvokeDll");  
270.  
271.    
272.  
273.             // 定义要调用的方法 , 方法名为“ MyFun ”,返回类型是“ Type_Return ”参数类型是“ TypeArray_ParameterType ”   
274.  
275.    
276.  
277.             MethodBuilder MyMethodBuilder = MyModuleBuilder.DefineGlobalMethod("MyFun", MethodAttributes.Public | MethodAttributes.Static, Type_Return, TypeArray_ParameterType);  
278.  
279.    
280.  
281.             // 获取一个 ILGenerator ,用于发送所需的 IL   
282.  
283.    
284.  
285.             ILGenerator IL = MyMethodBuilder.GetILGenerator();  
286.  
287.    
288.  
289.             int i;  
290.  
291.    
292.  
293.             for (i = 0; i <  ObjArray_Parameter.Length; i++)  
294.  
295.             {// 用循环将参数依次压入堆栈   
296.  
297.    
298.  
299.                 switch (ModePassArray_Parameter[i])  
300.  
301.                 {  
302.  
303.    
304.  
305.                     case ModePass.ByValue:  
306.  
307.    
308.  
309.                         IL.Emit(OpCodes.Ldarg, i);  
310.  
311.    
312.  
313.                         break;  
314.  
315.    
316.  
317.                     case ModePass.ByRef:  
318.  
319.    
320.  
321.                         IL.Emit(OpCodes.Ldarga, i);  
322.  
323.    
324.  
325.                         break;  
326.  
327.    
328.  
329.                     default:  
330.  
331.    
332.  
333.                         throw (new Exception(" 第 " + (i + 1).ToString() + " 个参数没有给定正确的传递方式 ."));  
334.  
335.    
336.  
337.                 }  
338.  
339.    
340.  
341.             }  
342.  
343.    
344.  
345.             if (IntPtr.Size == 4)  
346.  
347.             {// 判断处理器类型   
348.  
349.    
350.  
351.                 IL.Emit(OpCodes.Ldc_I4, farProc.ToInt32());  
352.  
353.    
354.  
355.             }  
356.  
357.    
358.  
359.             else if (IntPtr.Size == 8)  
360.  
361.             {  
362.  
363.    
364.  
365.                 IL.Emit(OpCodes.Ldc_I8, farProc.ToInt64());  
366.  
367.    
368.  
369.             }  
370.  
371.    
372.  
373.             else 
374.  
375.             {  
376.  
377.    
378.  
379.                 throw new PlatformNotSupportedException();  
380.  
381.    
382.  
383.             }  
384.  
385.    
386.  
387.             IL.EmitCalli(OpCodes.Calli, CallingConvention.StdCall, Type_Return, TypeArray_ParameterType);  
388.  
389.    
390.  
391.             IL.Emit(OpCodes.Ret); // 返回值   
392.  
393.    
394.  
395.             MyModuleBuilder.CreateGlobalFunctions();  
396.  
397.    
398.  
399.             // 取得方法信息   
400.  
401.    
402.  
403.             MethodInfo MyMethodInfo = MyModuleBuilder.GetMethod("MyFun");  
404.  
405.    
406.  
407.             return MyMethodInfo.Invoke(null, ObjArray_Parameter);// 调用方法,并返回其值   
408.  
409.    
410.  
411.         }  
412.  
413.     }  
414.  
2) dld类的使用:
1.打开项目“Test”,向“Form1”窗体中添加一个按钮,和一个TestBox,Name改为txRet。视图中双击按钮,在“button1_Click”方法体上面添加代码,创建一个dld类实例,并进行测试。具体如下:
1. private void button1_Click(object sender, EventArgs e)  
2.  
3.         {  
4.  
5.    
6.  
7.             int ret = 0;  
8.  
9.             dld myDLD = new dld();  
10.  
11.    
12.  
13.             myDLD.LoadDll("xxx.dll");  
14.  
15.    
16.  
17.             myDLD.LoadFun("InitSDK");  
18.  
19.    
20.  
21.             object[] Parameters = new object[] { }; // 实参为0   
22.  
23.    
24.  
25.             Type[] ParameterTypes = new Type[] { }; // 实参类型为int   
26.  
27.    
28.  
29.             ModePass[] themode = new ModePass[] { }; // 传送方式为值传  
30.  
31.    
32.  
33.             Type Type_Return = typeof(int); // 返回类型为int  
34.  
35.    
36.  
37.             ret = (int)myDLD.Invoke(Parameters, ParameterTypes, themode, Type_Return);  
38.  
39.             txRet.Text = ret.ToString();  
40.  
41.             if (ret != 1)  
42.  
43.             {  
44.  
45.                 MessageBox.Show("InitSDK failed !");  
46.  
47.             }  
48.  
49.             if (ret == 1)  
50.  
51.             {  
52.  
53.                 MessageBox.Show("InitSDK Sucessed !");  
54.  
55.             }  
56.  
57.         }  
58.

其中,xxx为要测试的dll名称,InitSDK为dll中的要测试的函数。

至此,C#调用DLL函数方法就介绍完了,希望对大家有所帮助。