Heim  >  Artikel  >  Backend-Entwicklung  >  Detaillierte Einführung in das C#-Programm zum dynamischen Aufrufen von DLL

Detaillierte Einführung in das C#-Programm zum dynamischen Aufrufen von DLL

黄舟
黄舟Original
2017-03-15 10:21:221172Durchsuche

C#Programmimplementierung von Dynamic CallDLForschung zu L

1. DLL und Anwendung

Dynamic Link Library (auch DLL genannt, kurz für „Dynamic Link Library“) ist eine der wichtigsten Komponenten von Microsoft Windows. Öffnen Sie den Windows-Systemordner und Sie werden feststellen, dass sich darin viele DLL-Dateien befinden. Windows implementiert einige wichtige Systemfunktionen in Form von DLL-Modulen.

Die Dynamic Link Library kann weder direkt ausgeführt werden, noch kann sie Nachrichten empfangen. Es handelt sich lediglich um eine unabhängige Datei, die Informationen enthält, die vom Programm oder anderen DLLs zum Abschließen der Aufgabe Funktionen (Methoden. Hinweis: In C# allgemein als „Methoden“ bezeichnet), die bestimmte Vorgänge ausführen. Diese Funktionen sind jedoch nicht Teil des Ausführungsprogramms selbst, sondern werden bei Bedarf geladen Nur dann können sie entsprechend den Anforderungen des Prozesses eingesetzt werden.

Die DLL wird vom System nur dann in den virtuellen Raum des Prozesses geladen, wenn die Anwendung sie benötigt, und wird Teil des aufrufenden Prozesses. Zu diesem Zeitpunkt kann nur der Thread des Prozesses auf die DLL zugreifen Prozess, und sein Handle kann vom Prozess verwendet werden, und das Handle des aufrufenden Prozesses kann auch von der DLL verwendet werden. Im Speicher hat eine DLL nur eine Instanz, und ihre Vorbereitung hat nichts mit der spezifischen

Programmiersprache oder dem Compiler zu tun, sodass eine gemischtsprachige Programmierung über die DLL erreicht werden kann. Jedes Objekt (einschließlich Variablen), das durch Code innerhalb einer DLL-Funktion erstellt wird, gehört dem Thread oder Prozess, der es aufruft.

Im Folgenden sind einige der Vorteile aufgeführt, die sich ergeben, wenn ein Programm eine DLL verwendet: [1]

1) Verbraucht weniger Ressourcen

Bei mehreren Wann Da mehrere Programme dieselbe Funktionsbibliothek verwenden, kann eine DLL die Menge an Duplikaten des auf der Festplatte und im physischen Speicher geladenen Codes reduzieren. Dies kann sich nicht nur stark auf Programme auswirken, die im Vordergrund ausgeführt werden, sondern auch auf andere Programme, die auf dem Windows-Betriebssystem ausgeführt werden.

2) Förderung der modularen Architektur

DLL hilft, die Entwicklung modularer Programme zu fördern. Dies kann Ihnen bei der Entwicklung großer Programme helfen, die mehrere Sprachversionen erfordern, oder Programme, die eine modulare Architektur erfordern. Ein Beispiel für ein modulares Programm ist ein Buchhaltungsprogramm, das über mehrere Module verfügt, die zur Laufzeit dynamisch geladen werden können.

3) Vereinfachte Bereitstellung und Installation Die DLL erfordert nicht, dass die Verbindung des Programms mit der DLL wiederhergestellt werden muss. Wenn außerdem mehrere Programme dieselbe DLL verwenden, profitieren mehrere Programme von dem Update oder Fix. Dieses Problem kann häufiger auftreten, wenn Sie DLLs von Drittanbietern verwenden, die regelmäßig aktualisiert oder repariert werden. 2. DLL aufrufen

Die Methode zum Aufrufen von DLL ist in jeder Programmiersprache unterschiedlich. Hier stellen wir nur die Methode zum Aufrufen von DLL mit C# vor. Zunächst müssen Sie verstehen, was verwaltet wird und was nicht. Im Allgemeinen kann davon ausgegangen werden, dass es sich bei nicht verwaltetem Code hauptsächlich um DLL- und ActiveX-Komponenten handelt, die auf der Grundlage der Win 32-Plattform entwickelt wurden, während verwalteter Code auf der Grundlage der .net-Plattform entwickelt wird. Wenn Sie mehr über die Beziehung und den Unterschied zwischen verwaltet und nicht verwaltet sowie deren Funktionsweise erfahren möchten, finden Sie die Informationen selbst. In diesem Dokument wird darauf nicht eingegangen.

(1) Allgemeine Methoden zum Aufrufen nicht verwalteter Funktionen in DLL

Zunächst einmal

sollten externe Methoden im C#-Sprachquellprogramm deklariert werden , und Die Grundform ist:

[DLLImport("DLL-Datei")] Modifikator extern gibt den Variablentyp-Methodennamen (Parameterliste) zurück

wobei :

DLL-Datei: enthält Bibliotheksdateien, die externe Methoden definieren.

Modifikatoren: Zugriffsmodifikatoren, andere Modifikatoren als abstrakte Modifikatoren, die beim Deklarieren von Methoden verwendet werden können.

Rückgabevariablentyp: Der Rückgabevariablentyp der Methode, die Sie in der DLL-Datei aufrufen müssen. Methodenname: Der Name der Methode, die Sie in der DLL-Datei aufrufen müssen.

Parameterliste: Eine Liste von Methoden, die Sie in der DLL-Datei aufrufen müssen.

Hinweis

: System.Run

time

.InteropServices

Namespace

muss in der Programmdeklaration verwendet werden.

DllImport kann nur auf Methodendeklarationen platziert werden. Die DLL-Datei muss sich im aktuellen Verzeichnis des Programms oder im systemdefinierten Abfrage-Pfad befinden (d. h. der durch Path in der Systemumgebungsvariablen festgelegte Pfad).

Der Typ der Rückgabevariable, der Methodenname und die Parameterliste müssen mit den Definitionen in der DLL-Datei übereinstimmen.

Wenn Sie andere Funktionsnamen verwenden möchten, können Sie die Einstellungen des EntryPoint-Attributs verwenden, wie zum Beispiel:

[DllImport("user32.dll", EntryPoint="MessageBoxA")]
static extern int MsgBox(int hWnd, string msg, string caption, int type);

Andere optionale DllImportAttribute Attribute:

CharSet gibt den im Einstiegspunkt verwendeten Zeichensatz an, z. B.: CharSet=CharSet.Ansi;

SetLastError gibt an, ob Die Methode behält den „Vorherigen Fehler“ von Win32 bei, z. B.: SetLastError=true; ExactSpelling gibt an, ob der EntryPoint genau mit der Schreibweise des angegebenen Einstiegspunkts übereinstimmen muss, z >PreserveSig gibt an, ob die Signatur der Methode beibehalten oder konvertiert werden soll, wie zum Beispiel: PreserveSig=true;

CallingConvention gibt die Aufrufkonvention des Einstiegspunkts an, wie zum Beispiel: CallingConvention=CallingConvention.Win

api

;

Weitere Informationen finden Sie in einigen anderen Artikeln [2] zum Thema „Datenmarshalling“ und „Marshalling numerischer und logischer Skalare“.

C#-Beispiel:

1. Starten Sie VS.NET und erstellen Sie ein neues Projekt mit dem Projektnamen „Tzb“ und der Vorlage „Windows Application“. 2. Doppelklicken Sie auf das Element „Schaltfläche“ im Element „Windows Forms“ der „Toolbox“, um eine

Schaltfläche

zu „

Für

m1“ hinzuzufügen. bilden . 3. Ändern Sie die Eigenschaften der Schaltfläche: Name ist „B1“, Text ist „Verwenden Sie DllImport, um die DLL aufzurufen, um das Eingabeaufforderungsfeld aufzurufen“, und passen Sie Schaltfläche B1 auf die entsprechende Größe an und verschieben Sie sie nach die entsprechende Position. 4. Doppelklicken Sie auf „Form1“ in der Klassenansicht

, um die Codeansicht „Form1.cs“ zu öffnen, geben Sie „using System.Runtime.InteropServices;“ ein. um diesen Namensraum zu importieren.

5. Doppelklicken Sie auf die Schaltfläche B1 in der Ansicht „Form1.cs [Design]“, deklarieren Sie mit den Schlüsselwörtern static und extern die Methode „MsgBox“ für die Methode „B1_Click“ und hängen Sie das Attribut DllImport an Zur Methode hier Was wir verwenden möchten, ist die Funktion „MessageBoxA“ in „user32.dll“. Der spezifische Code lautet wie folgt:

Fügen Sie dann den folgenden Code in den Methodenkörper „B1_Click“ ein um die Methode „MsgBox“ aufzurufen:

MsgBox(0 , „Dies ist die Eingabeaufforderung, die angezeigt wird, wenn DLL mit DllImport Box aufgerufen wird! “, „Challenge Cup“,0x30);

[DllImport("user32.dll", EntryPoint="MessageBoxA")]
static extern int MsgBox(int hWnd, string msg, string caption, int type);

6. Drücken Sie „F5“, um das Programm auszuführen, und klicken Sie auf die Schaltfläche B1. Das folgende Eingabeaufforderungsfeld wird angezeigt:

MsgBox(0," 这就是用 DllImport 调用 DLL 弹出的提示框哦! "," 挑战杯 ",0x30);

(2) Dynamisches Laden und Aufrufen nicht verwalteter Funktionen in der DLL

Oben wurde erklärt, wie man DllImport verwendet, um die nicht verwaltete Funktion in der DLL aufzurufen, aber dies ist eine globale Funktion, wenn die nicht verwaltet wird Die Funktion in der DLL verfügt über eine statische Variable S. Bei jedem Aufruf dieser Funktion wird die statische Variable S automatisch um 1 erhöht. Wenn eine Nachzählung erforderlich ist, werden daher nicht die gewünschten Ergebnisse erzielt. Folgendes wird anhand von Beispielen erläutert:

1. Erstellung der DLL

1) Starten Sie Visual C++ 6.0;

2)   

Erstellen Sie ein neues „Win32 Dynamic-Link Library“-Projekt mit dem Projektnamen „Count“;

3)   Wählen Sie in „ Dll-Art“ Wählen Sie in der Benutzeroberfläche „Ein einfaches DLL-Projekt“ aus;

4)    

Öffnen Sie Count.cpp und fügen Sie den folgenden Code hinzu:


5)        按“F7”进行编译,得到Count.dll(在工程目录下的Debug文件夹中)。

 

2.         用DllImport调用DLL中的count函数

1)        打开项目“Tzb”,向“Form1”窗体中添加一个按钮。

2)        改变按钮的属性:Name为 “B2”,Text为 “用DllImport调用DLL中count函数”,并将按钮B1调整到适当大小,移到适当位置。

3)        打开“Form1.cs”代码视图,使用关键字 static 和 extern 声明方法“count”,并使其具有来自 Count.dll 的导出函数count的实现,代码如下:

 

[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#程序中,在程序运行时才用某一算法进行解压解密后才进行加载,所以即使用反编译软件,也只能得到一个资源文件,且这个资源文件是用一个复杂算法进行加密过的,不可能再次对资源文件中的内容进行反编译,从而大大加强了代码的保密性。

Das obige ist der detaillierte Inhalt vonDetaillierte Einführung in das C#-Programm zum dynamischen Aufrufen von DLL. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn