C#调用动态链接库

PInvoke之C#调用动态链接库DLL_v2.0 简单做一下总结叭,通过上述所描述的从第一步如何创建一个DLL到如何通过C#去调用的一个简单实例,也应该能给正在查阅相关资料的你有所收获,也希望能给在这方面有所研究的你有一些相关的启发,同时也希望能给目前对这方面毫无了解的你有一个更进一步的学习。C#实现排序所耗时: 130msC实现排序所耗时:3ms在实现本案例中,可能在编译后,大家所看到的结果不是很出乎意料,但这只是一种案例,希望通过此案例的分析,能给大家带来一些意想不到的收获叭。 阅读详情

之前在百度上找的不全 这个是全的调试通过可用

 

一、      DLL 与应用程序

动态链接库(也称为 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 ,那么多个程序都将从该更新或修复中获益。当您使用定期更新或修复的第三方 DLL 时,此问题可能会更频繁地出现。

二、      DLL 的调用

每种编程语言调用 DLL 的方法都不尽相同,在此只对用 C# 调用 DLL 的方法进行介绍。首先 , 您需要了解什么是托管 , 什么是非托管。一般可以认为:非托管代码主要是基于 win 32 平台开发的 DLL , activeX 的组件,托管代码是基于 .net 平台开发的。如果您想深入了解托管与非托管的关系与区别,及它们的运行机制,请您自行查找资料,本文件在此不作讨论。

(一)     调用 DLL 中的非托管函数一般方法

首先 ,应该在 C# 语言源程序中声明外部方法,其基本形式是:

[DLLImport(“DLL 文件 ”)]

修饰符 extern 返回变量类型 方法名称  (参数列表)

其中 :

DLL 文件:包含定义外部方法的库文件。

修饰符:  访问修饰符,除了 abstract 以外在声明方法时可以使用的修饰符。

返回变量类型:在 DLL 文件中你需调用方法的返回变量类型。

方法名称:在 DLL 文件中你需调用方法的名称。

参数列表:在 DLL 文件中你需调用方法的列表。

注意 :需要在程序声明中使用 System.Runtime.InteropServices 命名空间。

      DllImport 只能放置在方法声明上。

DLL 文件必须位于程序当前目录或系统定义的查询路径中(即:系统环境变量中 Path 所设置的路径)。

返回变量类型、方法名称、参数列表一定要与 DLL 文件中的定义相一致。

若要使用其它函数名,可以使用 EntryPoint 属性设置,如:

[DllImport("user32.dll", EntryPoint="MessageBoxA")]

static extern int MsgBox(int hWnd, string msg, string caption, int type);

其它可选的 DllImportAttribute 属性:

CharSet 指示用在入口点中的字符集,如: CharSet=CharSet.Ansi ;

SetLastError 指示方法是否保留 Win32" 上一错误 " ,如: SetLastError=true ;

ExactSpelling 指示 EntryPoint 是否必须与指示的入口点的拼写完全匹配,如: ExactSpelling=false ;

PreserveSig 指示方法的签名应当被保留还是被转换, 如: PreserveSig=true ;

CallingConvention 指示入口点的调用约定, 如: CallingConvention=CallingConvention.Winapi ;

此外,关于“数据封送处理”及“封送数字和逻辑标量”请参阅其它一些文章 [2] 。

C# 例子:

1.       启动 VS.NET ,新建一个项目,项目名称为“ Tzb ”,模板为“ Windows 应用程序”。

2.       在“工具箱”的“ Windows 窗体”项中双击“ Button ”项,向“ Form1 ”窗体中添加一个按钮。

3.       改变按钮的属性: Name 为 “B1” , Text 为 “ 用 DllImport 调用 DLL 弹出提示框 ” ,并将按钮 B1 调整到适当大小,移到适当位置。

4.       在类视图中双击“ Form1 ”,打开“ Form1 . cs ”代码视图,在“ namespace Tzb ”上面输入“ using System.Runtime.InteropServices; ”,以导入该命名空间。

5.       在“ Form1 . cs [设计]”视图中双击按钮 B1 ,在“ B1_Click ”方法上面使用关键字 static 和 extern 声明方法“ MsgBox ”,将 DllImport 属性附加到该方法,这里我们要使用的是“ user32 . dll ”中的“ MessageBoxA ”函数,具体代码如下:

[DllImport("user32.dll", EntryPoint="MessageBoxA")]

static extern int MsgBox(int hWnd, string msg, string caption, int type);
 

然后在“ B1_Click ”方法体内添加如下代码,以调用方法“ MsgBox ”:

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

 

6.       按“ F5” 运行该程序,并点击按钮 B1 ,便弹出如下提示框:

 
 

(二)     动态装载、调用 DLL 中的非托管函数

在上面已经说明了如何用 DllImport 调用 DLL 中的非托管函数,但是这个是全局的函数,假若 DLL 中的非托管函数有一个静态变量 S ,每次调用这个函数的时候,静态变量 S 就自动加 1 。结果,当需要重新计数时,就不能得出想要的结果。下面将用例子说明:

1.        DLL 的创建

1)        启动 Visual C++ 6.0 ;

2)        新建一 个“ Win32 Dynamic-Link Library ”工程,工程名称为“ Count ”;

3)        在“ Dll kind ”选择界面中选择“ A simple dll project ”;

4)        打开 Count.cpp ,添加如下代码:

// 导出函数,使用“ _stdcall ” 标准调用

extern "C" _declspec(dllexport)int _stdcall count(int init);
 
int _stdcall count(int init)

{//count 函数,使用参数 init 初始化静态的整形变量 S ,并使 S 自加 1 后返回该值

static int S=init;

S++;

return S;

}
 

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 ”上面添加如下代码声明参数传递方式枚举:

///

/// 参数传递方式枚举 ,ByValue 表示值传递 ,ByRef 表示址传递

///

public enum ModePass

{

ByValue = 0x0001,

ByRef = 0x0002

}
 

3.       声明 LoadLibrary 、 GetProcAddress 、 FreeLibrary 及私有变量 hModule 和 farProc :

///

/// 原型是 :HMODULE LoadLibrary(LPCTSTR lpFileName);

///

/// DLL 文件名

/// 函数库模块的句柄

[DllImport("kernel32.dll")]

static extern IntPtr LoadLibrary(string lpFileName);

///

/// 原型是 : FARPROC GetProcAddress(HMODULE hModule, LPCWSTR lpProcName);

///

/// 包含需调用函数的函数库模块的句柄

/// 调用函数的名称

/// 函数指针

[DllImport("kernel32.dll")]

static extern IntPtr GetProcAddress(IntPtr hModule, string lpProcName);

///

/// 原型是 : BOOL FreeLibrary(HMODULE hModule);

///

/// 需释放的函数库模块的句柄

/// 是否已释放指定的 Dll

[DllImport("kernel32",EntryPoint="FreeLibrary",SetLastError=true)]

static extern bool FreeLibrary(IntPtr hModule);

///

/// Loadlibrary 返回的函数库模块的句柄

///

private IntPtr hModule=IntPtr.Zero;

///

/// GetProcAddress 返回的函数指针

///

private IntPtr farProc=IntPtr.Zero;
 

 

4.       添加 LoadDll 方法,并为了调用时方便,重载了这个方法:

///

/// 装载 Dll

///

/// DLL 文件名

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 方法,并为了调用时方便,也重载了这个方法,方法的具体代码及注释如下:

///

/// 获得函数指针

///

/// 调用函数的名称

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+" 这个函数的入口点 "));

}

///

/// 获得函数指针

///

/// 包含需调用函数的 DLL 文件名

/// 调用函数的名称

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 方法也进行了重载:

///

/// 卸载 Dll

///

public void UnLoadDll()

{

FreeLibrary(hModule);

hModule=IntPtr.Zero;

farProc=IntPtr.Zero;

}
 

          Invoke 方法的第一个版本:

///

/// 调用所设定的函数

///

/// 实参

/// 实参类型

/// 实参传送方式

/// 返回类型

/// 返回所调用函数的 object

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 方法的第二个版本,它是调用了第一个版本的:

///

/// 调用所设定的函数

///

/// 函数指针

/// 实参

/// 实参类型

/// 实参传送方式

/// 返回类型

/// 返回所调用函数的 object

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 类实例:

///

/// 创建一个 dld 类对象

///

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

c# 如何调用C++ 动态链接库DLL文件 本文主要记录了C#项目以非托管方式进行调用C++ DLL文件。解下来将记录C# 调用C++ DLL 中复杂函数时数据处理过程,如返回值或参数为数组、指针、引用、结构体,结构体指针等,这些在C++已经让人有些头疼的东西,如何在C#中处理,欢迎持续关注并留言讨论。 阅读详情

相关推荐

C#调用C++动态链接库

链接库的调用1. 用vc做一个静态链接库2. 用vc调用静态链接库3. 生成一个动态链接库4. 用vc调用动态链接库5. 将静态链接库做成动态链接库新的改变功能快捷键合理的创建标题,有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右SmartyPants创建一个自定义列表如何创建一个注脚注释也是必不可少的KaTeX数学公...

aboklh的博客 2788

C#程序实现动态调用DLL的研究

<br />正   文: <br />一、      DLL 与应用程序 <br />动态链接库(也称为 DLL ,即为“ Dynamic Link Library ”的缩写)是 Microsoft Windows 最重要的组成要素之一,打开 Windows 系统文件夹,你会发现文件夹中有很多 DLL 文件, Windows 就是将一些主要的系统功能以 DLL 模块的形式实现。 <br />动态链接库是不能直接执行的,也不能接收消息,它只是一个独立的文件,其中包含能被程序或其它 DLL 调用来完成一定操作的

461

Python中动态调用C#的dll动态链接库中方法

【不同语言程序之间调用】Python中动态调用C#的dll动态链接库中方法

警警的博客 3095

【专题】C#调用动态链接库DLL

1.概述 动态链接库(Dynamic Linked Library):将写好的函数存在库中,以供其他程序开发调用,调用方式为“动态的”。  Windows为应用程序提供了丰富的函数调用,这些函数调用都包含在动态链接库中。其中有3个最重要的DLL,Kernel32.dll,它包含用于管理内存、进程和线程的各个函数;  User32.dll,它包含用于执行用户界面任务(如窗口的创建和消息的传送)的各个...

zgscwxd的博客 1万+

C#通过CLR Wrapper 成托管方式调用C++编写的动态链接库

一、目的 C#确实在工控行业用的很多,各种大型自动化开发平台越来越多的基于.net桌面应用的开发,但无论是winform还是WPF,速度真的是个问题,比如西门子的TIA-Portal,安装文件动辄几十G,配置稍微低的电脑运行起来卡顿的要吐血几斗。 大型工控程序,还是用C/C++或其他同效率的语言开发吧! 本文主要记录一下C#最简单的调用C++编写的动态库的一种方式,也是本人常用的一种方式,把核心逻辑用C/C++封装成DLL,然后基于CLR给这些DLL再做一个封装后,直接供C#使用,这样做,还有一个目的,就是

如是道程序人生 2394

【调试记录】GT ip跑aurora 64b66b协议

如果不用,会出现这种情况:自回环测试,数据收发ok.一旦和对方通信,对方接收的就是一堆乱码,因为GT链路发送数据不能是全1或全0,或者长时间的1或0.否则线路容易受到干扰,毕竟线速率10多G,用了加扰和解扰,就可以提高抗干扰能力。64B66B编码,数据输入是64bit,输出是66bit,所以这之间存在着2bit的带宽差,Gearbox 就是用来解决这种差异的,实现方式是:将66bit数据拿出来2bit先存入Gearbox,然后输出64bit.发送同步码或控制数据时,配置成2.2.4 加扰和解扰!

weixin_41246509的博客 1601

C#调用C++的动态链接库

// 8位单通道图像//图像指针//图像宽度//图像高度/// 8位3通道图像//图像指针//图像宽度//图像高度/// 8位4通道图像//图像指针//图像宽度//图像高度/// 16位1通道图像//图像指针//图像宽度//图像高度//图像模板_T* data;//图像的指针int width;//图像宽度int height;//图像高度//8位单通道图像//24位单通道图像//32位单通道图像。

Major_S的博客 689

libfranka---joint_impendence_control例程分析

joint_impedance_control.cpp例程分析 部分内容来自链接,我个人以初学者的角度对代码进行了更加细致的解释。 核心部分: // Set and initialize trajectory parameters. const double radius = 0.05; const double vel_max = 0.25; const double acceleration_time = 2.0; const double run_time = 20.0; d

weixin_41567783的博客 1121

C#调用C/C++动态链接库

C#调用C/C++动态链接库示例

Brady的专栏 9104

基于javaEE的图书管理系统

极简的图书管理系统,无任何样式修饰,适合新手练手(图文并释) 1、实现了用户注册、登录,图书的添加、修改、删除和修改操作 2、工具需要:eclipse、mysql、Tomcat 3、做系统之前在eclipse需要配置Tomcat服务器和导入mysql连接数据库的jar包 4、数据库中建一个user表存用户信息,一个book表存图书信息 一、各种页面 index.html文件,系统运行的首页,有登录...

qq_44357792的博客 2万+

比例导引算法详解:从理论到Matlab实现(含3D轨迹可视化)

本文深入解析比例导引算法(PNG)的数学原理与Matlab实现,涵盖3D轨迹可视化技术。从基础运动学模型到导航比优化,详细讲解导弹拦截系统的核心算法,并提供Matlab代码示例,帮助读者掌握从理论到实战的全流程。

weixin_33725722的博客 548

c#中如何调用vc++写的动态链接库

      当VC等调用C#写的COM时,用regasm   /tlb生成TLB文件,也可用tlbexp.exe,在VC等中加载TLB文件,当用C#调用VC等写的COM时,用tlbimp.exe,你可以写一个程序调试一下       添加System.Runtime.InteropServices命名空间        如是COM就直接用静态函数调用:              

xuerene2的专栏 2824

[软考]系统架构设计师 备考经验分享(一) - 备考计划篇

前言 上周末刚刚结束了软考架构师的考试,自我感觉考的还可以,大概率是过了。 两次参加软考,一次中级嵌入式,一次高级架构师,都是备考不到两个月时间,尤其是架构师这次考试内容和本职工作有不小的差别,并且需要同时备考三个科目,在高强度地备考中感觉是摸索出到了一点软考的技巧与经验。 打算趁还记得,赶紧写几篇文章做下梳理总结,算是给将来自己考系分、高项或者其他考试做个记录,也希望能够帮到正在备考的大家。 先把第一步,个人的备考计划分享给大家。 自身情况分析 首先,请大家一定先明确三个问题,这三个问题会直接影响到你的

_ 6404

C#动态链接库(dll)调用全攻略

动态链接库调用

m0_58750788的博客 6854

【数据集】MODIS 的 MOD07 大气廓线产品概述及下载

MODIS 的 MOD07 大气廓线产品概述及下载

WW、forever的博客 550

复现论文《孤岛微电网环境下基于物理模型引导的深度强化学习VSG频率控制方法》

本文提出了一种结合物理模型与深度强化学习的孤岛微电网VSG频率控制方法。通过建立VSG小信号模型并设计可变系数控制器,将物理知识融入深度强化学习框架,解决了高维参数和机组耦合带来的控制难题。复现代码实现了三个核心组件:VSGSmallSignalModel建模VSG动态特性,VariableCoefficientController实现参数自适应调节,PhysicsGuidedDRL整合DQN算法与物理模型引导的奖励机制。该方法通过模型驱动与数据驱动的优势互补,提升了频率控制性能,其中物理模型既作为先验知识

huanghm88的专栏 127

c# 调用c++ 动态链接库

对于DLL导出函数声明的四种写法,在动态调用时, 声明成第一种方式是最好的。但是,C/C++缺省的调用约定为__cdecl约定,如果想别的语言能用DLL的话,最好是将调用约定写成__stdcall方式(不能动态调用),然后静态(隐式)调用。1)强制用C语言方式进行修饰,且用C的默认调用约定,即__cdecl方式。这种方式编译产生的DLL中有一个导出函数:_add@0,即前面有“_”,后面加了参数长。在隐式调用时,四种声明方式都是可以的,只要调用者的声明方式和DLL声明时的方式一致即可。

qq165285727的专栏 996

54、生物神经网络建模全解析

本文系统解析了生物神经网络的建模方法,涵盖从单个神经元到神经网络的多个时空尺度。首先介绍神经元层面的生物物理模型(如霍奇金-赫胥黎模型和多隔室模型)与多种简化模型(如菲茨休-纳古莫、莫里斯-勒卡尔、积分发放模型等),并分析其适用场景。随后探讨突触层面的信号传递机制,对比电突触与化学突触在结构、传输速度和方向上的差异,阐述其在神经网络建模中的理论意义。文章旨在为理解神经系统的信息处理机制、构建受生物启发的人工智能模型提供理论基础。

app77的博客 76

C# 生成Dll,Visual Stdio和Labview调用此动态链接库

/ 注意此处的转换!// 注意此处的转换!3、生成Dll文件,在bin--debug里面去找。并复制到需要调用的地方去。1、将生成的DLL文件放进 需要调用的文件夹bin--debug里面。二、C#的动态链接库 DLL调用。注意:在程序中调用 命名空间。一、生成动态连接库DLL。2、引用添加DLL文件。三、labview调用。

m0_65344060的博客 1252

win10 跳过U8环境检测的IE WebControls.rar

win10 安装u8的时候 提示IE WebControls 不通过,这个工具可以欺骗u8的检查。 跳过U8环境检测的IE WebControls。提示导入失败后,重新检测通过。

IMSL CNL 7.0.0 (x86-64)Crack

IMSL CNL 7.0.0 (x86-64)Crack intel的算法库破解文件,这是C++版本的破解文件

C#调用C++动态链接库dll

C#调用C++动态链接库dll 由于公司很多底层的SDK,都是C++开发,上层的应用软件却是C# Winform程序。在实际工作的过程中,就经常碰到了C# 程序调用C++ 动态库的问题。最近一直在和C++ 打交道,C# 怎么调用C++ 类库函数。也遇到了一些问题,所以就来总结总结C#程序调用C++动态库时的各种坑。 1. 可能遇到的问题: C#在调用动态库的过程中我也遇到了以下一些问题:...

爱喝奶茶的博客 3633

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

C# 调用C++动态链接库Dll以及联调

C# 调用C++动态链接库Dll以及联调 平:很好用,已经使用过了。可以联调。 1: C++对外公开的接口需声明为: extern “C” __declspec Fun(); 这是由于c++与c# 调用方式不一样,所以函数前需要加__stdcall修饰; 2:c#中通过DllImport引用c++动态链接库 [DllImport("xxx.dll")] public static extern fun() C++ 与c#的数据类型有的不一样,这个需要注意; 3:把C++

jean7155的专栏 868

文档:亲测可用解决vnc灰屏,黑屏,鼠标大黑叉情况

主要解决vnc-viewer灰屏,黑屏,鼠标大黑叉等现象,详细见文档。

C#调用C的动态链接库

C#调用C的动态链接库 1.C#调用C的动态链接库时的数据格式转换 参照文章:https://yq.aliyun.com/articles/678155 1.1 函数传参的数据类型转换 1.1.1 函数参数是普通的类型 1.1.2 函数参数是普通变量的指针 1.1.3 函数参数是结构体 1.1.4 定义的结构体中含有数组 1.1.5 函数参数是结构指针 1.1.5.1 适用于结构体指针不是另外一个函数的返回值 【DllImport("DLL文件名.DLL",Entr

qq_41917296的博客 1222

C#调用C++ DLL动态链接库如何回传数组

C#调用C++ DLL动态链接库如何回传数组 参考:https://www.cnblogs.com/zhaox583132460/p/3402243.html 使用C#中的GCHandle类,解决数组数据传回。 C++ 内部的函数 extern "C" __declspec(dllexport) void __stdcall function(int* retriveData, int idataCount) { for(int i = 0; i < idataCount, i++) { re

schmiloo的博客 3537
上一篇: Windows和Linux dll区别
下一篇: Windows静态库和动态库的调用方法汇总
nodeathphoenix
博客等级 码龄16年 97粉丝 99原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值