1. DLL 및 애플리케이션
Dynamic Link Library(DLL이라고도 함, "Dynamic Link Library"의 약자)는 Microsoft Windows의 가장 중요한 구성 요소 중 하나입니다. Windows 시스템 폴더를 열면 폴더에 많은 DLL 파일이 있습니다. , Windows는 DLL 모듈 형태로 일부 주요 시스템 기능을 구현합니다.
동적 링크 라이브러리는 직접 실행할 수도 없고 메시지를 받을 수도 없으며, 프로그램이나 기타 작업을 완료하기 위한 DLL 함수 (메서드. 참고: 일반적으로 C#에서는 "메서드"라고 함). 그러나 이러한 함수는 실행 프로그램 자체의 일부가 아니지만 요구에 따라 로드됩니다. 그래야만 프로세스의 필요에 따라 효과를 사용할 수 있습니다.
DLL은 애플리케이션이 필요할 때만 시스템에 의해 프로세스의 가상 공간에 로드되고 호출 프로세스의 일부가 됩니다. 이때 DLL은 스레드를 통해서만 액세스할 수 있습니다. 프로세스 및 해당 핸들은 프로세스에서 호출될 수 있으며 호출 프로세스의 핸들도 DLL에서 사용될 수 있습니다. 메모리에서 DLL은 인스턴스가 하나만 있고 그 준비는 특정프로그래밍 언어나 컴파일러와 관련이 없으므로 DLL을 통해 혼합 언어 프로그래밍이 가능합니다. DLL 함수 내의 코드로 생성된 모든 객체(변수 포함)는 이를 호출하는 스레드나 프로세스의 소유입니다.
다음은 프로그램이 DLL을 사용할 때 제공되는 몇 가지 장점입니다. [1]1) 더 적은 리소스를 사용합니다.
여러 개일 때 여러 프로그램이 동일한 함수 라이브러리를 사용하므로 DLL은 디스크와 실제 메모리에 로드된 코드의 중복 양을 줄일 수 있습니다. 이는 포그라운드에서 실행되는 프로그램뿐만 아니라 Windows 운영 체제에서 실행되는 다른 프로그램에도 큰 영향을 미칠 수 있습니다.2) 모듈식 아키텍처 장려
DLL은 모듈식 프로그램 개발을 촉진하는 데 도움이 됩니다. 이는 여러 언어 버전이 필요한 대규모 프로그램이나 모듈식 아키텍처가 필요한 프로그램을 개발하는 데 도움이 될 수 있습니다. 모듈식 프로그램의 예로는 런타임에 동적으로 로드할 수 있는 여러 모듈이 있는 회계 프로그램이 있습니다.3) 단순화된 배포 및 설치 DLL에서는 프로그램과 DLL의 링크를 다시 설정할 필요가 없습니다. 또한 여러 프로그램이 동일한 DLL을 사용하는 경우 여러 프로그램이 업데이트 또는 수정의 이점을 누릴 수 있습니다. 이 문제는 정기적으로 업데이트되거나 수정되는 타사 DLL을 사용할 때 더 자주 발생할 수 있습니다. 2.DLL 호출
각 프로그래밍 언어마다 DLL을 호출하는 방법이 다릅니다. 여기서는 C#을 사용하여 DLL을 호출하는 방법만 소개합니다. 먼저, 관리되는 것과 관리되지 않는 것을 이해해야 합니다. 일반적으로 비관리 코드는 주로 win 32 플랫폼을 기반으로 개발된 DLL과 ActiveX 구성 요소인 반면, 관리 코드는 .net 플랫폼을 기반으로 개발되었다고 생각하면 됩니다. 관리되는 것과 관리되지 않는 것의 관계와 차이점, 그리고 작동 메커니즘에 대해 자세히 알아보려면 이 문서에서는 여기서 다루지 않는 정보를 직접 찾아보세요.
(1) DLL에서 관리되지 않는 함수를 호출하는 일반적인 방법우선
C# 언어 소스 프로그램에서 외부 메서드를 선언해야 합니다. 기본 형식은 다음과 같습니다.
[DLLImport("DLL file")] modifier extern은 변수 유형 메서드 이름(매개변수 목록)을 반환합니다.
여기서 :
DLL 파일: 외부 메서드를 정의하는 라이브러리 파일이 포함되어 있습니다.
수정자: 액세스 수정자, 메소드 선언 시 사용할 수 있는 추상 이외의 수정자.
반환 변수 유형: DLL 파일에서 호출해야 하는 메소드의 반환 변수 유형입니다. 메서드 이름: DLL 파일에서 호출해야 하는 메서드 이름입니다.
매개변수 목록: DLL 파일에서 호출해야 하는 메서드 목록입니다.
참고: System.Run
time.InteropServices
namespace를 프로그램 선언에 사용해야 합니다.
DllImport는 메서드 선언에만 배치할 수 있습니다. DLL 파일은 프로그램의 현재 디렉터리나 시스템 정의 query 경로(예: 시스템 환경 변수에서 Path로 설정된 경로)에 있어야 합니다. 반환 변수 유형, 메서드 이름, 매개변수 목록은 DLL 파일의 정의와 일치해야 합니다. 다른 함수 이름을 사용하려면 다음과 같은 EntryPoint 속성 설정을 사용할 수 있습니다. 기타 선택적 DllImportAttribute 속성: CharSet은 다음과 같이 진입점에 사용된 문자 집합을 나타냅니다. CharSet=CharSet.Ansi SetLastError는 메서드가 다음과 같은지 여부를 나타냅니다. 다음과 같은 Win32 "이전 오류"를 유지합니다. SetLastError=true; ExactSpelling은 EntryPoint가 다음과 같이 표시된 진입점의 철자와 정확히 일치해야 하는지 여부를 나타냅니다. PreserveSig는 메서드의 서명이 보존되거나 변환되어야 함을 나타냅니다. PreserveSig=true; CallingConvention은 다음과 같이 진입점의 호출 규칙을 나타냅니다. CallingConvention=CallingConvention.Winapi ; 또한 "데이터 마샬링" 및 "숫자 마샬링 및 논리 스칼라"[2]에 대한 다른 기사를 참조하세요. 1. VS.NET을 시작하고 프로젝트 이름이 "Tzb"이고 템플릿이 "Windows Application"인 새 프로젝트를 만듭니다. 2. "도구 상자"의 "Windows Forms" 항목에서 "버튼" 항목을 두 번 클릭하여 " m1"에 을 추가합니다. 형태 . 3. 버튼 속성을 변경합니다. 이름은 "B1", 텍스트는 "DllImport를 사용하여 DLL을 호출하여 프롬프트 상자 팝업"으로 설정하고 버튼 B1을 적절한 크기로 조정한 다음 적절한 위치. 4. 에서 "Form1"을 두 번 클릭하여 "Form1.cs" 코드 뷰를 열고 "namespace Tzb"에 "using System.Runtime.InteropServices;"를 입력합니다. 이 네임스페이스를 가져오려면 5. "Form1.cs [Design]" 뷰에서 B1 버튼을 두 번 클릭하고 static 및 extern 키워드를 사용하여 "B1_Click" 메서드에 "MsgBox" 메서드를 선언한 다음 DllImport 속성을 첨부합니다. 여기서 우리가 사용하려는 것은 "user32.dll"의 "MessageBoxA" 함수입니다. 구체적인 코드는 다음과 같습니다. [DllImport("user32.dll", EntryPoint="MessageBoxA")]
static extern int MsgBox(int hWnd, string msg, string caption, int type);
MsgBox( 0," DllImport 프롬프트 상자를 사용하여 DLL을 호출할 때 나타나는 팝업입니다!"," Challenge Cup",0x30);
|
MsgBox(0," 这就是用 DllImport 调用 DLL 弹出的提示框哦! "," 挑战杯 ",0x30); |
(2) DLL에서 관리되지 않는 함수를 동적으로 로드하고 호출
위에서 DllImport를 사용하여 DLL에서 관리되지 않는 함수를 호출하는 방법을 설명했지만 이는 관리되지 않는 함수인 경우 전역 함수입니다. DLL에는 static
변수 S가 있으며, 이 함수가 호출될 때마다 정적 변수 S는 자동으로 1씩 증가합니다. 결과적으로 재검표가 필요한 경우 원하는 결과를 얻을 수 없습니다. 다음은 예시와 함께 설명됩니다.1)
Visual C++ 6.0 시작2)
프로젝트 이름이 "Count"인 새 "Win32 동적 링크 라이브러리" 프로젝트를 생성합니다. 3)
"에서 선택합니다. Dll 종류" 인터페이스에서 "간단한 dll 프로젝트"를 선택합니다;4)
Count.cpp를 열고 다음 코드를 추가합니다:rreee |
[DllImport("Count.dll")] static extern int count(int init); |
4) 在“Form1.cs[设计]”视图中双击按钮B2,在“B2_Click”方法体内添加如下代码:
MessageBox.Show(" 用 DllImport 调用 DLL 中的 count 函数, /n 传入的实参为 0 ,得到的结果是: "+count(0).ToString()," 挑战杯 "); MessageBox.Show(" 用 DllImport 调用 DLL 中的 count 函数, /n 传入的实参为 10 ,得到的结果是: "+count(10).ToString()+"/n 结果可不是想要的 11 哦!!! "," 挑战杯 "); MessageBox.Show(" 所得结果表明: /n 用 DllImport 调用 DLL 中的非托管 /n 函数是全局的、静态的函数!!! "," 挑战杯 "); |
5) 把Count.dll复制到项目“Tzb”的bin/Debug文件夹中,按“F5”运行该程序,并点击按钮B2,便弹出如下三个提示框:
第1个提示框显示的是调用“count(0)”的结果,第2个提示框显示的是调用“count(10)”的结果,由所得结果可以证明“用DllImport调用DLL中的非托管函数是全局的、静态的函数”。所以,有时候并不能达到我们目的,因此我们需要使用下面所介绍的方法:C#动态调用DLL中的函数。
3. 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. 打开项目“Tzb”,打开类视图,右击“Tzb”,选择“添加”-->“类”,类名设置为“dld”,即dynamic loading dll 的每个单词的开头字母。
2. 添加所需的命名空间及声明参数传递方式枚举:
using System.Runtime.InteropServices; // 用 DllImport 需用此 命名空间 using System.Reflection; // 使用 Assembly 类需用此 命名空间 using System.Reflection.Emit; // 使用 ILGenerator 需用此 命名空间
|
在“public class dld”上面添加如下代码声明参数传递方式枚举:
/// <summary> /// 参数传递方式枚举 ,ByValue 表示值传递 ,ByRef 表示址传递 /// </summary> public enum ModePass { ByValue = 0x0001, ByRef = 0x0002 } |
3. 声明LoadLibrary、GetProcAddress、FreeLibrary及私有变量hModule和farProc:
/// <summary> /// 原型是 :HMODULE LoadLibrary(LPCTSTR lpFileName); /// </summary> /// <param name="lpFileName">DLL 文件名 </param> /// <returns> 函数库模块的句柄 </returns> [DllImport("kernel32.dll")] static extern IntPtr LoadLibrary(string lpFileName); /// <summary> /// 原型是 : FARPROC GetProcAddress(HMODULE hModule, LPCWSTR lpProcName); /// </summary> /// <param name="hModule"> 包含需调用函数的函数库模块的句柄 </param> /// <param name="lpProcName"> 调用函数的名称 </param> /// <returns> 函数指针 </returns> [DllImport("kernel32.dll")] static extern IntPtr GetProcAddress(IntPtr hModule, string lpProcName); /// <summary> /// 原型是 : BOOL FreeLibrary(HMODULE hModule); /// </summary> /// <param name="hModule"> 需释放的函数库模块的句柄 </param> /// <returns> 是否已释放指定的 Dll</returns> [DllImport("kernel32",EntryPoint="FreeLibrary",SetLastError=true)] static extern bool FreeLibrary(IntPtr hModule); /// <summary> /// Loadlibrary 返回的函数库模块的句柄 /// </summary> private IntPtr hModule=IntPtr.Zero; /// <summary> /// GetProcAddress 返回的函数指针 /// </summary> private IntPtr farProc=IntPtr.Zero; |
4. 添加LoadDll方法,并为了调用时方便,重载了这个方法:
/// <summary> /// 装载 Dll /// </summary> /// <param name="lpFileName">DLL 文件名 </param> public void LoadDll(string lpFileName) { hModule=LoadLibrary(lpFileName); if(hModule==IntPtr.Zero) throw(new Exception(" 没有找到 :"+lpFileName+"." )); } |
若已有已装载Dll的句柄,可以使用LoadDll方法的第二个版本:
public void LoadDll(IntPtr HMODULE) { if(HMODULE==IntPtr.Zero) throw(new Exception(" 所传入的函数库模块的句柄 HMODULE 为空 ." )); hModule=HMODULE; } |
5. 添加LoadFun方法,并为了调用时方便,也重载了这个方法,方法的具体代码及注释如下:
/// <summary> /// 获得函数指针 /// </summary> /// <param name="lpProcName"> 调用函数的名称 </param> public void LoadFun(string lpProcName) { // 若函数库模块的句柄为空,则抛出异常 if(hModule==IntPtr.Zero) throw(new Exception(" 函数库模块的句柄为空 , 请确保已进行 LoadDll 操作 !")); // 取得函数指针 farProc = GetProcAddress(hModule,lpProcName); // 若函数指针,则抛出异常 if(farProc==IntPtr.Zero) throw(new Exception(" 没有找到 :"+lpProcName+" 这个函数的入口点 ")); } /// <summary> /// 获得函数指针 /// </summary> /// <param name="lpFileName"> 包含需调用函数的 DLL 文件名 </param> /// <param name="lpProcName"> 调用函数的名称 </param> public void LoadFun(string lpFileName,string lpProcName) { // 取得函数库模块的句柄 hModule=LoadLibrary(lpFileName); // 若函数库模块的句柄为空,则抛出异常 if(hModule==IntPtr.Zero) throw(new Exception(" 没有找到 :"+lpFileName+"." )); // 取得函数指针 farProc = GetProcAddress(hModule,lpProcName); // 若函数指针,则抛出异常 if(farProc==IntPtr.Zero) throw(new Exception(" 没有找到 :"+lpProcName+" 这个函数的入口点 ")); } |
6. 添加UnLoadDll及Invoke方法,Invoke方法也进行了重载:
/// <summary> /// 卸载 Dll /// </summary> public void UnLoadDll() { FreeLibrary(hModule); hModule=IntPtr.Zero; farProc=IntPtr.Zero; } |
Invoke方法的第一个版本:
/// <summary> /// 调用所设定的函数 /// </summary> /// <param name="ObjArray_Parameter"> 实参 </param> /// <param name="TypeArray_ParameterType"> 实参类型 </param> /// <param name="ModePassArray_Parameter"> 实参传送方式 </param> /// <param name="Type_Return"> 返回类型 </param> /// <returns> 返回所调用函数的 object</returns> public object Invoke(object[] ObjArray_Parameter,Type[] TypeArray_ParameterType,ModePass[] ModePassArray_Parameter,Type Type_Return) { // 下面 3 个 if 是进行安全检查 , 若不能通过 , 则抛出异常 if(hModule==IntPtr.Zero) throw(new Exception(" 函数库模块的句柄为空 , 请确保已进行 LoadDll 操作 !")); if(farProc==IntPtr.Zero) throw(new Exception(" 函数指针为空 , 请确保已进行 LoadFun 操作 !" ) ); if(ObjArray_Parameter.Length!=ModePassArray_Parameter.Length) throw(new Exception(" 参数个数及其传递方式的个数不匹配 ." ) ); // 下面是创建 MyAssemblyName 对象并设置其 Name 属性 AssemblyName MyAssemblyName = new AssemblyName(); MyAssemblyName.Name = "InvokeFun"; // 生成单模块配件 AssemblyBuilder MyAssemblyBuilder =AppDomain.CurrentDomain.DefineDynamicAssembly(MyAssemblyName,AssemblyBuilderAccess.Run); ModuleBuilder MyModuleBuilder =MyAssemblyBuilder.DefineDynamicModule("InvokeDll"); // 定义要调用的方法 , 方法名为“ MyFun ”,返回类型是“ Type_Return ”参数类型是“ TypeArray_ParameterType ” MethodBuilder MyMethodBuilder =MyModuleBuilder.DefineGlobalMethod("MyFun",MethodAttributes.Public| MethodAttributes.Static,Type_Return,TypeArray_ParameterType); // 获取一个 ILGenerator ,用于发送所需的 IL ILGenerator IL = MyMethodBuilder.GetILGenerator(); int i; for (i = 0; i < ObjArray_Parameter.Length; i++) {// 用循环将参数依次压入堆栈 switch (ModePassArray_Parameter[i]) { case ModePass.ByValue: IL.Emit(OpCodes.Ldarg, i); break; case ModePass.ByRef: IL.Emit(OpCodes.Ldarga, i); break; default: throw(new Exception(" 第 " +(i+1).ToString() + " 个参数没有给定正确的传递方式 ." ) ); } } if (IntPtr.Size == 4) {// 判断处理器类型 IL.Emit(OpCodes.Ldc_I4, farProc.ToInt32()); } else if (IntPtr.Size == 8) { IL.Emit(OpCodes.Ldc_I8, farProc.ToInt64()); } else { throw new PlatformNotSupportedException(); } IL.EmitCalli(OpCodes.Calli,CallingConvention.StdCall,Type_Return,TypeArray_ParameterType); IL.Emit(OpCodes.Ret); // 返回值 MyModuleBuilder.CreateGlobalFunctions(); // 取得方法信息 MethodInfo MyMethodInfo = MyModuleBuilder.GetMethod("MyFun"); return MyMethodInfo.Invoke(null, ObjArray_Parameter);// 调用方法,并返回其值 } |
Invoke方法的第二个版本,它是调用了第一个版本的:
/// <summary> /// 调用所设定的函数 /// </summary> /// <param name="IntPtr_Function"> 函数指针 </param> /// <param name="ObjArray_Parameter"> 实参 </param> /// <param name="TypeArray_ParameterType"> 实参类型 </param> /// <param name="ModePassArray_Parameter"> 实参传送方式 </param> /// <param name="Type_Return"> 返回类型 </param> /// <returns> 返回所调用函数的 object</returns> public object Invoke(IntPtr IntPtr_Function,object[] ObjArray_Parameter,Type[] TypeArray_ParameterType,ModePass[] ModePassArray_Parameter,Type Type_Return) { // 下面 2 个 if 是进行安全检查 , 若不能通过 , 则抛出异常 if(hModule==IntPtr.Zero) throw(new Exception(" 函数库模块的句柄为空 , 请确保已进行 LoadDll 操作 !")); if(IntPtr_Function==IntPtr.Zero) throw(new Exception(" 函数指针 IntPtr_Function 为空 !" ) ); farProc=IntPtr_Function; return Invoke(ObjArray_Parameter,TypeArray_ParameterType,ModePassArray_Parameter,Type_Return); } |
2) dld类的使用:
1. 打开项目“Tzb”,向“Form1”窗体中添加三个按钮。Name 和Text属性分别为 “B3”、“用LoadLibrary方法装载Count.dll”,“B4”、“调用count方法”,“B5”、“卸载Count.dll”,并调整到适当的大小及位置。
2. 在“Form1.cs[设计]”视图中双击按钮B3,在“B3_Click”方法体上面添加代码,创建一个dld类实例:
/// <summary> /// 创建一个 dld 类对象 /// </summary> private dld myfun=new dld(); |
3. 在“B3_Click”方法体内添加如下代码:
myfun.LoadDll("Count.dll"); // 加载 "Count.dll" myfun.LoadFun("_count@4"); // 调入函数 count, "_count@4" 是它的入口,可通过 Depends 查看 |
4. “Form1.cs[设计]”视图中双击按钮B4,在“B4_Click”方法体内添加如下代码:
object[] Parameters = new object[]{(int)0}; // 实参为 0 Type[] ParameterTypes = new Type[]{typeof(int)}; // 实参类型为 int ModePass[] themode=new ModePass[]{ModePass.ByValue}; // 传送方式为值传 Type Type_Return = typeof(int); // 返回类型为 int // 弹出提示框,显示调用 myfun.Invoke 方法的结果,即调用 count 函数 MessageBox.Show(" 这是您装载该 Dll 后第 "+myfun.Invoke(Parameters,ParameterTypes,themode,Type_Return).ToString() +" 次点击此按钮。 "," 挑战杯 "); |
5. “Form1.cs[设计]”视图中双击按钮B5,在“B5_Click”方法体内添加如下代码:
myfun.UnLoadDll(); |
6. 按“F5”运行该程序,并先点击按钮B3以加载“Count.dll”,接着点击按钮B4三次以调用3次“count(0)”,先后弹出的提示框如下:
这三个提示框所得出的结果说明了静态变量S 经初始化后,再传入实参“0”也不会改变其值为“0”。
7. 点击按钮B5以卸载“Count.dll”,再点击按钮B3进行装载“Count.dll”,再点击按钮B4查看调用了“count(0)”的结果:
从弹出的提示框所显示的结果可以看到又开始重新计数了,也就是实现了DLL的动态装载与卸载了。
(三) 调用托管DLL一般方法
C# 调用托管DLL是很简单的,只要在“解决方案资源管理器”中的需要调用DLL的项目下用鼠标右击“引用”,并选择“添加引用”,然后选择已列出的DLL或通过浏览来选择DLL文件,最后需要用using 导入相关的命名空间。
(四) 动态调用托管DLL
C# 动态调用托管DLL也需要借助System.Reflection.Assembly里的类和方法,主要使用了Assembly.LoadFrom。现在,用例子说明:
首先,启动VS.NET,新建一个Visual C# 项目,使用的模板为“类库”,名称为“CsCount”,并在类“Class1”中添加静态整型变量S及方法count:
// 由于 static 不能修饰方法体内的变量,所以需放在这里,且初始化值为 int.MinValue static int S=int.MinValue; public int count(int init) {// 判断 S 是否等于 int.MinValue ,是的话把 init 赋值给 S if(S==int.MinValue) S=init; S++; //S 自增 1 return S; // 返回 S } |
然后,打开项目“Tzb”,向“Form1”窗体中添加一个按钮,Name属性为“B6”,Text属性为“用Assembly类来动态调用托管DLL”,调整到适当大小和位置,双击按钮B6,转入代码视图,先导入命名空间:using System.Reflection; 接着添加Invoke方法和B6_Click方法代码:
private object Invoke(string lpFileName,string Namespace,string ClassName,string lpProcName,object[] ObjArray_Parameter) { Try { // 载入程序集 Assembly MyAssembly=Assembly.LoadFrom(lpFileName); Type[] type=MyAssembly.GetTypes(); foreach(Type t in type) {// 查找要调用的命名空间及类 if(t.Namespace==Namespace&&t.Name==ClassName) {// 查找要调用的方法并进行调用 MethodInfo m=t.GetMethod(lpProcName); if(m!=null) { object o=Activator.CreateInstance(t); return m.Invoke(o,ObjArray_Parameter); } else MessageBox.Show(" 装载出错 !"); } } }//try catch(System.NullReferenceException e) { MessageBox.Show(e.Message); }//catch return (object)0; }// Invoke |
“B6_Click”方法体内代码如下:
// 显示 count(0) 返回的值 MessageBox.Show(" 这是您第 "+Invoke("CsCount.dll","CsCount","Class1","count",new object[]{(int)0}).ToString()+" 次点击此按钮。 "," 挑战杯 "); |
最后,把项目“CsCount”的bin/Debug文件夹中的CsCount.dll复制到项目“Tzb”的bin/Debug文件夹中,按“F5”运行该程序,并点击按钮B6三次,将会弹出3个提示框,内容分别是“这是您第 1次点击此按钮。”、“这是您第 2次点击此按钮。”、“这是您第 3次点击此按钮。”,由此知道了静态变量S在这里的作用。
(五) C#程序嵌入DLL的调用
DLL文件作为资源嵌入在C#程序中,我们只要读取该资源文件并以“byte[]”返回,然后就用“Assembly Load(byte[]);”得到DLL中的程序集,最后就可以像上面的Invoke方法那样对DLL中的方法进行调用。当然不用上面方法也可以,如用接口实现动态调用,但DLL中必须有该接口的定义并且程序中也要有该接口的定义;也可用反射发送实现动态调用[4]。现在我只对像上面的Invoke方法那样对DLL中的方法进行调用进行讨论,为了以后使用方便及实现代码的复用,我们可以结合上一个编写一个类。
1) ldfs类的编写:
在项目“Tzb”中新建一个名为ldfs的类,意为“load dll from resource”,请注意,在这个类中“resource”不只是嵌入在EXE程序中的资源,它也可以是硬盘上任意一个DLL文件,这是因为ldfs的类中的方法LoadDll有些特别,就是先从程序的内嵌的资源中查找需加载的DLL,如果找不到,就查找硬盘上的。
首先导入所需的命名空间:
using System.IO; // 对文件的读写需要用到此命名空间 using System.Reflection; // 使用 Assembly 类需用此命名空间 using System.Reflection.Emit; // 使用 ILGenerator 需用此命名空间 |
声明一静态变量MyAssembly:
// 记录要导入的程序集 static Assembly MyAssembly; |
添加LoadDll方法:
private byte[] LoadDll(string lpFileName) { Assembly NowAssembly = Assembly.GetEntryAssembly(); Stream fs=null; try {// 尝试读取资源中的 DLL fs = NowAssembly.GetManifestResourceStream(NowAssembly.GetName().Name+"."+lpFileName); } finally {// 如果资源没有所需的 DLL ,就查看硬盘上有没有,有的话就读取 if (fs==null&&!File.Exists(lpFileName)) throw(new Exception(" 找不到文件 :"+lpFileName)); else if(fs==null&&File.Exists(lpFileName)) { FileStream Fs = new FileStream(lpFileName, FileMode.Open); fs=(Stream)Fs; } } byte[] buffer = new byte[(int) fs.Length]; fs.Read(buffer, 0, buffer.Length); fs.Close(); return buffer; // 以 byte[] 返回读到的 DLL } |
添加UnLoadDll方法来卸载DLL:
public void UnLoadDll() {// 使 MyAssembly 指空 MyAssembly=null; } |
添加Invoke方法来进行对DLL中方法的调用,其原理大体上和“Form1.cs”中的方法Invoke相同,不过这里用的是“Assembly.Load”,而且用了静态变量MyAssembly来保存已加载的DLL,如果已加载的话就不再加载,如果还没加载或者已加载的不同现在要加载的DLL就进行加载,其代码如下所示:
public object Invoke(string lpFileName,string Namespace,string ClassName,string lpProcName,object[] ObjArray_Parameter) { try {// 判断 MyAssembly 是否为空或 MyAssembly 的命名空间不等于要调用方法的命名空间,如果条件为真,就用 Assembly.Load 加载所需 DLL 作为程序集 if(MyAssembly==null||MyAssembly.GetName().Name!=Namespace) MyAssembly=Assembly.Load(LoadDll(lpFileName)); Type[] type=MyAssembly.GetTypes(); foreach(Type t in type) { if(t.Namespace==Namespace&&t.Name==ClassName) { MethodInfo m=t.GetMethod(lpProcName); if(m!=null) {// 调用并返回 object o=Activator.CreateInstance(t); return m.Invoke(o,ObjArray_Parameter); } else System.Windows.Forms.MessageBox.Show(" 装载出错 !"); } } } catch(System.NullReferenceException e) { System.Windows.Forms.MessageBox.Show(e.Message); } return (object)0; } |
2) ldfs类的使用:
1. 把CsCount.dll作为“嵌入的资源”添加到项目“Tzb”中。
2. 向“Form1”窗体中添加两个按钮,Name和Text属性分别为“B7”、“ldfs.Invoke调用count”;“B8”、“UnLoadDll”,并将它们调整到适当大小和位置。
3. 打开“Form1.cs”代码视图,添加一个ldfs实例:
// 添加一个 ldfs 实例 tmp private ldfs tmp=new ldfs(); |
4. 在“Form1.cs[设计]”视图中双击按钮B7,在“B1_Click”方法体内添加如下代码:
// 调用 count(0), 并使用期提示框显示其返回值 MessageBox.Show(" 这是您第 "+tmp.Invoke("CsCount.dll","CsCount","Class1","count",new object[]{(int)0}).ToString()+" 次点击此按钮。 "," 挑战杯 "); |
5. 在“Form1.cs[设计]”视图中双击按钮B7,在“B1_Click”方法体内添加如下代码:
// 卸载 DLL tmp.UnLoadDll(); |
6. “F5”运行该程序,并先点击按钮B7三次,接着点击按钮B8,最后再点击按钮B7,此时发现又开始重新计数了,情况和“dld类的使用”类似,也就是也实现了DLL的动态装载与卸载了。
说明:以上所用到的所有源代码详见附件1:Form1.cs、附件2:dld.cs、附件3:ldfs.cs、附件4:Count.cpp、附件5:Class1.cs。
三、 结 论
使用DLL有很多优点,如:节省内存和减少交换操作;开发大型程序时可以把某些模块分配给程序员,程序员可以用任何一门他所熟悉的语言把该模块编译成DLL文件,这样可以提高代码的复用,大大减轻程序员的工作量。当然DLL也有一些不足,如在提要中提及的问题。所以,如何灵活地调用DLL应该是每位程序员所熟知的。
C# 语言有很多优点,越来越多的人开始使用它来编程。但是,C#还有一些不足,如对不少的底层操作是无能为力的,只能通过调用Win32 DLL 或C++等编写的DLL;另外,一般认为C#程序的保密性不够强,因为它容易被Reflector 反编译而得到部分源码,所以需要使用混合编程加强C#程序的保密性,而把DLL嵌入C#程序并实现动态调用的方法是比较理想的方法,因为可以把DLL文件先用某一算法进行加密甚至压缩后再作为资源文件添加到C#程序中,在程序运行时才用某一算法进行解压解密后才进行加载,所以即使用反编译软件,也只能得到一个资源文件,且这个资源文件是用一个复杂算法进行加密过的,不可能再次对资源文件中的内容进行反编译,从而大大加强了代码的保密性。
위 내용은 DLL을 동적으로 호출하는 C# 프로그램에 대한 자세한 소개의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!