| 网站首页 | 业界新闻 | 小组 | 威客 | 人才 | 下载频道 | 博客 | 代码贴 | 在线编程 | 编程论坛
欢迎加入我们,一同切磋技术
用户名:   
 
密 码:  
共有 3972 人关注过本帖, 2 人收藏
标题:C#程序实现动态调用DLL的研究
只看楼主 加入收藏
laraft
Rank: 1
等 级:新手上路
帖 子:2
专家分:0
注 册:2008-9-24
收藏(2)
 问题点数:0 回复次数:1 
C#程序实现动态调用DLL的研究
转自断点社区 http://bbs.

C#程序实现动态调用DLL的研究
  要:开发高手》2004期中的《化功大法——将DLL》一文,介绍了如何把一个动态链接库作为一个资源嵌入到可执行文件,在可执行文件运行时,自动从资源中释放出来,通过静态加载延迟实现DLL函数的动态加载,程序退出后实现临时文件的自动删除,从而为解决“DLL Hell实现了,在Internet程序,但在某一技术论坛上提起这种设计方法时,有网友提出:“这种方法好是好,但就是启动速度太慢”。这是因为程序启动时实现DLL,这个过程会耗费一定的时间。鉴于此问题,经过思索,提出另一个设计方案:DLL释放及其重新加载。本文就是对该设计方案的原理分析及使用C#关键词:,嵌入DLL正  文:

一、      DLL与应用程序

,即为“Dynamic Link Library最重要的组成要素之一,打开Windows文件,Windows模块的形式实现。

调用来完成一定操作的函数(中一般称为“方法”)DLL也只能被该进程的线程访问,它的句柄可以被调用进程所使用,而调用进程的句柄也可以被该DLL只有一个实例,且它的编制与具体的编程语言和编译器都没有关系,所以可以通过DLL函数中的代码所创建的任何对象(包括变量)都归调用它的线程或进程所有。

时提供的一些优点:[1]

1)        可以减少在磁盘和物理内存中加载的代码的重复量。这不仅可以大大影响在前台运行的程序,而且可以大大影响其他在 Windows 2)        DLL 3)        中的函数需要更新或修复时,部署和安装 DLL 的链接。此外,如果多个程序使用同一个 DLL时,此问题可能会更频繁地出现。

二、      DLL的调用

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

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

,应该在C#[DLLImport(“DLL返回变量类型 (参数列表)



DLL访问修饰符,除了abstract文件中你需调用方法的返回变量类型。

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

文件中你需调用方法的列表。

:需要在程序声明中使用System.Runtime.InteropServices      DllImportDLL所设置的路径)。

文件中的定义相一致。

  

属性设置,如:

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

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

属性:

CharSet ;

SetLastError ;

ExactSpelling 是否必须与指示的入口点的拼写完全匹配,如:ExactSpelling=falsePreserveSig如:PreserveSig=trueCallingConvention如:CallingConvention=CallingConvention.Winapi  



C#1.       ,新建一个项目,项目名称为“Tzb应用程序”。

2.       窗体”项中双击“Button”窗体中添加一个按钮。

3.       为 “B1”为 “调用DLL,并将按钮B14.       ”,打开“Form1”代码视图,在“namespace Tzb”,以导入该命名空间。

5.       .cs,在“B1_Click和 extern ”,将 DllImport .dll”函数,具体代码如下:

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

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



”方法体内添加如下代码,以调用方法“MsgBox

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


  
6.       ”,便弹出如下提示框:


  

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

调用DLL中的非托管函数有一个静态变量S就自动加11.        DLL的创建

1)        

2)        个“Win32 Dynamic-Link Library”;

3)        ”选择界面中选择“A simple dll project4)        ,添加如下代码:


// 导出函数,使用“ _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)        ”(在工程目录下的Debug  

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

1)        ”,向“Form1窗体中添加一个按钮。

2)        为 ”,Text“用DllImport中count调整到适当大小,移到适当位置。

3)        .cs和 extern ”,并使其具有来自 Count.dll 的实现,代码如下:

  

[DllImport("Count.dll")]

static extern int count(int init);



4)        .cs,在“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)        复制到项目“Tzb文件夹中,按“F5运行该程序,并点击按钮B2  

   
个提示框显示的是调用“count(0)个提示框显示的是调用“count(10)调用DLL动态调用DLL  

   

3.        C#动态调用DLL中的函数

中使用DllImport那样,所以只能借助API中,与动态库调用有关的函数包括[3]①LoadLibrary的AfxLoadLibrary②GetProcAddress内部地址。

(或MFC),释放动态链接库。

现在,我们可以用IntPtr hModule=LoadLibrary(“Count.dll”);的句柄,”);中是没有函数指针的,没有像C++及System.Reflection.Assembly1)        dld       ”,打开类视图,右击“Tzb“类”,类名设置为“dld的每个单词的开头字母。

2.      

using System.Runtime.InteropServices; // 用 DllImport 需用此 命名空间

using System.Reflection; // 使用 Assembly 类需用此 命名空间

using System.Reflection.Emit; // 使用 ILGenerator 需用此 命名空间



          ”上面添加如下代码声明参数传递方式枚举:

/// <summary>

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

/// </summary>

public enum ModePass

{

ByValue = 0x0001,

ByRef = 0x0002

}



  

3.       、GetProcAddress及私有变量hModule:

/// <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.       方法,并为了调用时方便,重载了这个方法:

  

/// <summary>

/// 装载 Dll

/// </summary>

/// <param name="lpFileName">DLL 文件名 </param>

public void LoadDll(string lpFileName)

{

hModule=LoadLibrary(lpFileName);

if(hModule==IntPtr.Zero)

throw(new Exception(" 没有找到 :"+lpFileName+"." ));

}



  

          的句柄,可以使用LoadDll

public void LoadDll(IntPtr HMODULE)

{

if(HMODULE==IntPtr.Zero)

throw(new Exception(" 所传入的函数库模块的句柄 HMODULE 为空 ." ));

hModule=HMODULE;

}


  

5.       方法,并为了调用时方便,也重载了这个方法,方法的具体代码及注释如下:

/// <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)

{

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  ”,向“Form1窗体中添加和Text“B3、“用LoadLibrary”,“B4、“调用count””,并调整到适当的大小及位置。

2.  .cs,在“B3_Click类实例:

/// <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.  .cs,在“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.  .cs,在“B5_Click

myfun.UnLoadDll();


6.  ”运行该程序,并先点击按钮B3加载“Count.dll”三次以调用3”,先后弹出的提示框如下:


          经初始化后,再传入实参“0也不会改变其值为“0。

7.  以卸载“Count.dll进行装载“Count.dll查看调用了“count(0)

  

(三)     调用托管DLL一般方法

C# 是很简单的,只要在“解决方案资源管理器”中的需要调用DLL或通过浏览来选择DLL导入相关的命名空间。

(四)     动态调用托管DLL

C# 也需要借助System.Reflection.Assembly。现在,用例子说明:

     ,新建一个Visual C# ”,并在类“Class1中及方法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

}


  

”,向“Form1窗体中添加属性为“B6,Text类来动态调用托管DLL,转入代码视图,先导入命名空间:using System.Reflection; 方法和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


  

”方法体内代码如下:

// 显示 count(0) 返回的值

MessageBox.Show(" 这是您第 "+Invoke("CsCount.dll","CsCount","Class1","count",new object[]{(int)0}).ToString()+" 次点击此按钮。 "," 挑战杯 ");



  

”的bin\Debug复制到项目“Tzb文件夹中,按“F5运行该程序,并点击按钮B6个提示框,内容分别是“这是您第 1次点击此按钮。”、“这是您第 3在这里的作用。

  

(五) C#程序嵌入DLL的调用

     DLL程序byte[]”返回,然后就用“Assembly Load(byte[]);”得到DLL程序集,最后就可以像上面的Invoke中的方法进行调用。当然不用上面方法也可以,如用接口实现动态调用,但DLL。现在我只对像上面的Invoke中的方法进行调用进行讨论,为了以后使用方便及实现代码的复用,我们可以结合上一个编写一个类。

1)        ldfs”中新建一个名为ldfs”,请注意,在这个类中“resource程序中的资源,它也可以是硬盘上任意一个DLL的类中的方法LoadDll,如果找不到,就查找硬盘上的。


using // 对文件的读写需要用到此命名空间

using System.Reflection; // 使用 Assembly 类需用此命名空间

using System.Reflection.Emit; // 使用 ILGenerator 需用此命名空间




// 记录要导入的程序集

static Assembly MyAssembly;



方法:

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

}



方法来卸载DLL

public void UnLoadDll()

{// 使 MyAssembly 指空

MyAssembly=null;

}


方法来进行对DLL.cs相同,不过这里用的是Assembly.Load”,而且用了来保存已加载的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)        ldfs1.  作为“嵌入的资源”添加到项目“Tzb2.  ”窗体中添加两个按钮,Name属性分别为“B7调用count”、“UnLoadDll3.  .cs实例:

// 添加一个 ldfs 实例 tmp

private ldfs tmp=new ldfs();

4.  .cs,在“B1_Click

// 调用 count(0), 并使用期提示框显示其返回值

MessageBox.Show(" 这是您第 "+tmp.Invoke("CsCount.dll","CsCount","Class1","count",new object[]{(int)0}).ToString()+" 次点击此按钮。 "," 挑战杯 ");

5.  .cs,在“B1_Click

// 卸载 DLL

tmp.UnLoadDll();

6.  ”运行该程序,并先点击按钮B7,最后再点击按钮B7类的使用”类似,。


    、附件2:dld.cs、附件4:Count.cpp。  

三、      结 论

有很多优点,如:节省内存和减少交换操作;开发大型程序时可以把某些模块分配给程序员,程序员可以用任何一门他所熟悉的语言把该模块编译成DLL也有一些不足,如在提要中提及的问题。所以,如何灵活地调用DLL语言有很多优点,越来越多的人开始使用它来编程。但是,C#或C++;另外,一般认为C#反编译而得到部分源码,所以需要使用混合编程加强C#嵌入C#文件先用某一算法进行加密甚至压缩后再作为资源文件添加到C#



转自断点社区 http://bbs.
搜索更多相关主题的帖子: DLL 动态 研究 
2008-09-24 10:36
jason13168
Rank: 1
等 级:新手上路
帖 子:15
专家分:0
注 册:2008-10-1
收藏
得分:0 
好文章值得學習!
以加入最愛日後隨時可以在學習!
謝謝分享
2008-10-02 03:29
快速回复:C#程序实现动态调用DLL的研究
数据加载中...
 
   



关于我们 | 广告合作 | 编程中国 | 清除Cookies | TOP | 手机版

编程中国 版权所有,并保留所有权利。
Powered by Discuz, Processed in 0.024744 second(s), 8 queries.
Copyright©2004-2024, BCCN.NET, All Rights Reserved