C#调用DLL

来源:互联网 发布:java压缩图片 编辑:程序博客网 时间:2024/04/28 23:25

最近在写一个关于C#调用VC的非MFC动态库的一个小程序,本来原来想用VC写的。后来想用C#画个图
  估计很方便,于是就是操起了C#. 虽然以前没有用过C#开发过东西,但是大道都是通的,无非就是工具
  而已,虽然前方很模糊,但是我更想看看模糊的背后是什么(呵呵,废话过多,开始了)
   运行环境: XP + SP2
  1.非托管类(不需要CLR和.NetFramework支持)
  注意:需要在程序声明中使用System.Runtime.InteropServices命名空间。
   public class Utility
  {
   [DllImport("User32"]
  EntryPoint="MessageBoxA",
  CallingConvention=CallingConvention.StdCall]
   public static extern int MsgBox (System.IntPtr handle, System.String msg,
   System.String caption, System.UInt32 type);
  }
  
  class MyClass
  {
   public static int Main()
   {
   System.IntPtr handle = new System.IntPtr();
   string myString;
   Console.Write("Enter your message: ");
   myString = Console.ReadLine();
   return Utility.MsgBox(handle, "", "", 0);
   }
  }
  
  值得注意的是,缺省的调用规则(CallingConvention)是Stdcall,同Winapi,在
  C++里是__stdcall的形式,还有其他形式, 比如__cdecl, __fastcall
  
  在调用WIN32 API时注意那些类型的转换,如结构(struct)、指针(pointer),
  有关各种语言之间类型转换和DllImport属性的详细信息可以参考SDK文档(后面给出)
  
  2.托管类
  用Assmebly调用
  例: Assembly clientassembly =
   Assembly.LoadFrom(@"/dll/CommInfoQuery.dll");
   Form clientform =
   (Form)clientassembly.CreateInstance("JHManage.CommInfoQueryForm");
   clientform.Show();
  这种可以防止DLL_HELL,等一下说明DLL_HELL
  
  3 调用标准的DLL
  在c#里只需要调用pinvoke (platform invocation) 服务。c# 支持一种sysimport属性
  支持这种调用。
  下面是完整的语法形式(在例子里没有用到所有的参数):
  [sysimport(
  dll=dllname,
  name=functionname,
  charset=charactersettobeused)
  ]
  给出一个调用win32 messagebox函数的例子:
  using system;
  class pinvokeclient
  {
  [sysimport(dll="user32.dll")]
  public static extern int messageboxa(int hwnd, string message,
  string caption, int type);
  public static void main()
  {
  int result = messageboxa(0, "hello world", "pinvoke test", 0);
  }
  }
  注明: 托管与非托管的说法:
   简单说明: 托管就是要借助个环境来执行就象.net要装framework
   非托管就是代码已经是可以执行的程序就象你平时用的应用程序
   复杂说明: 查看MSDN中对于公共语言运行库的介绍(CLR)
  
  4 C#中动态调用DLL中的函数(转)
  因为C#中使用DllImport是不能像动态load/unload assembly那样,所以只能借助API函数了。在
  kernel32.dll中,与动态库调用有关的函数包括
  ①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 类需用此命名空间(C#的反射)
  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";
  // 生成单模块配件
  
  
  DLL_HELL的说明:
  
  
  状况1: 动态库没有尽到回溯相容的责任
   如果程序A调用的是1.0版本的动态库(X.dll), 而程序B调用的是2.0版本的动态库(X.dll), 因为程
  序B开发时间比较晚, 结果程序B在安装到系统的时候, 2.0版本的X.dll也安装到了系统中, 而原来1.0的
  版本被2.0所代替.
   在大部分情况下2.0向下兼容1.0的版本, 但是有时候也会出现不相容的情况. 此时程序A便无法正
  常执行了,此种DLL Hell的起因是设计者, 原因在于动态库没有回溯的原则. (比如A原来在1.0在X.dll中
  有的函数, 在更新到2.0的时候取消了, 此时DLL Hell就产生了)
  
  
  状况2: 动态库尽到了回溯的责任,但是动态库本身出现了bug
   另外一种情况就是,2.0的版本向下兼容了,但是出现了程序A和X.dll一起运行的时候出现了问题.
  程式 A 用 X 1.0
   (X 1.0) (原始版本)
   Public SetValue(ByVal Value As Integer)
   If Value < 0 Then
   Value = 0
   End If
   m_Value = Value
  End Property
  
  程式 B 用 X 2.0
   (X 2.0) (更新版本)
   Public SetValue(ByVal Value As Integer)
   'Fix the bug
   If Value < 0 Then
   Err.Raise Number:=APP_ERROR, Description:="Negative Value " (错误检测功能)
   End If
   m_Value = Value
  End Property
  
   比如以上出现的问题, 开发人员编写的二进制的dll兼容了以下的版本,内部的GUID的方法名称和参
  数都完全相同. 由于X1.0中有一个名字为value的属性名称, 但是次数为负数的时候,就会变成0. 但是确
  不会出现错误信息. 这个做法后来发现是错误的,于是X2.0版本解决了value,一旦出现负数就报错.
   但是当X2.0用到A上面的时候,编译可以通过,但是运行就DOWN掉了. 所以要完善二进制的dll, 并且
  向下兼容,不发生DLL_HELL的情况是非常困难和复杂的。
  
  
  C#调用DLL文件时参数对应表
  Wtypes.h中的非托管类型 非托管 C语言类型 托管类名 说明
  HANDLE void* System.IntPtr 32 位
  BYTE unsigned char System.Byte 8 位
  SHORT short System.Int16 16 位
  WORD unsigned short System.UInt16 16 位
  INT int System.Int32 32 位
  UINT unsigned int System.UInt32 32 位
  LONG long System.Int32 32 位
  BOOL long System.Int32 32 位
  DWORD unsigned long System.UInt32 32 位
  ULONG unsigned long System.UInt32 32 位
  CHAR char System.Char 用 ANSI 修饰。
  LPSTR char* System.String 或
   System.StringBuilder 用 ANSI 修饰。
  LPCSTR Const char* System.String 或
   System.StringBuilder 用 ANSI 修饰。
  LPWSTR wchar_t* System.String 或
   System.StringBuilder 用 Unicode 修饰。
  LPCWSTR Const wchar_t* System.String 或
   System.StringBuilder 用 Unicode 修饰。
  FLOAT Float System.Single 32 位
  DOUBLE Double System.Double 64 位

原创粉丝点击