本文的内容接着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函数方法就介绍完了,希望对大家有所帮助。