zoukankan      html  css  js  c++  java
  • C#.Net调用非托管的DLL

    一、DLL介绍:

    动态链接库(DLL,即“Dynamic Link Library”)是Microsoft Windows最重要的组成元素之一,打开windows系统文件夹,会发现很多DLL文件,windows就是将一些主要的系统功能以DLL模块的形式实现。动态链接库是不能直接执行的,也不能接收消息,它是一个独立的文件,其中包含被程序或其他DLL调用来完成一定操作的函数(方法)。但这些函数不是执行程序本身的一部分,而是根据进程的需要按需载入,此时才能发挥作用。

    二、C#.Net调用基本格式:

    [DLLImport(“DLL文件路径”)]

    修饰符 extern 返回值类型 方法名称(参数列表) 如:

    [DllImport("kernel32.dll", SetLastError = true, EntryPoint = "SetLocalTime")]
          public static extern int SetSystemTime(ref SystemTime lpSystemTime);

    PS:

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

    2、DLLImport会按照顺序去查找DLL文件(程序当前目录>System32目录>环境变量Path所设置路径)。

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

    4、Asp.net DLLImport路径----使用第三方非托管的DLL(Charles.dll)组件的时候,当把Charles.dll拷贝到Bin目录下,提示仍然提示仍然找不到该dll.(而这样[DLLImport(@“C:\ProgramDir\Charles.dll”)]可以正常加载)。Asp.Net Team的官方解决方案如下:

    首先需要确认引用了哪些组件?哪些是托管的?那些是非托管的?

    托管的很方便,直接被使用的需要引用,间接使用的需要拷贝到Bin目录下。非托管的就特殊处理(实际上你拷贝到bin是没有任何作用的,因为CLR会把文件拷贝到一个临时目录下,然后在那运行Web,而CLR只会拷贝托管文件,这就是为什么把非托管的DLL放到bin目录下仍然提示找不到该模块)。

    解决方案:首先在服务器上建立一个新建的目录,假设是(C:\ProgramDir\WinDLL\).然后在环境变量中,给Path变量添加这个目录,最后把非托管的DLL文件都拷贝到该目录下。或者更干脆把DLL放到System32目录中。对于自己部署的应用程序,这样的确能很好的解决问题。然而如果我们用的是虚拟空间,我们有没有办法吧注册Path变量或者把我们自己的DLL拷贝System32目录下。同时我们也不一定知道我们DLL的物理路径.

    DLLImport里面只能用字符常量,而不能使用Server.MapPath来确认物理绝对路径。

    这样的话我们需要动态的取得我们DLL的物理路径(Server.MapPath),并通过API来取得DLL里面的函数(先加载LoadLibrary后获得函数地址GetProcAddress)。相关的API如下:

    Public Class CustomDLLInvoke

    {

    [DLLImport(“kernel32.dll”)]

    private extern static IntPtr LoadLibrary(string path);

    [DLLImport(kernel32.dll)]

    private extern static IntPtr GetProcAddress(IntPtr lib,String funcName);

    [DLLImport(Kernel32.dll)]

    private extern static bool FreeLibrary(IntPtr lib);

    private IntPtr MLib;

    public CustomDLLInvoke(string dllPath)

    {MLib=LoadLibrary(DLLPath)}

    ~CustomDLLInvoke(){FreeLibrary(MLib);}

    public Delegate Invoke(string APIName,Type t)

    {IntPtr api=GetProAddress(MLib,APIName);return (Delegate)Marshal.GetDelegateForFunctionPointer(api,t);}

    }

    三、消息回调

    函数的两种调用方式:

    StdCall:stdcall调用约定又称为passcal调用约定,其调用约定申明的语法为:int_stdcall function(int a,int b).stdcall的调用约定意味着:1.参数从右向左压入堆栈。2.函数自身修改堆栈。3.函数名自动加前导的下划线,后面紧跟一个@符号。其后紧跟着参数的尺寸。在C#中,函数只支持stdcall的调用方式。

    Cdecl:cdecl调用约定又称为C调用约定,是C语言缺省的调用约定,它的定义语法是:int_cdecl function(int a,int b).cdecal调用约定的参数压栈顺序和stdcall是一样的。参数首先由右向左压入堆栈。所不同的是,函数本身不清理堆栈,调用者复制清理堆栈。由于这种变化,C调用约定允许函数的参数的个数是不固定的,这也是C语言的一大特色。VC++ 6.0默认使用cdecl的调用方式。

    所以当C#通过消息回调dll函数时候,由于函数调用的约定不同,函数不能正确的调用。

    采取方式:1.修改dll文件,支持stdCall的调用方式。2.手工方式修改.il中间文件(C#不支持_cdecl修饰符,可.net中间文件.il是支持cdecl的调用方式。)cdecl

    3.用VC++ 7.0给windows dll封装一层外壳。(在VC++ 7.0中,可以通过_cdecl修饰符,指定函数的调用方式)

    四、复杂类型Demo示例(修改PC机的系统时间)

    ModifyTime

    public struct SystemTime
       {
           public short wYear;
           public short wMonth;
           public short wDayOfWeek;
           public short wDay;
           public short wHour;
           public short wMinute;
           public short wSecond;
           public short wMilliseconds;
       }

    [DllImport("kernel32.dll", SetLastError = true, EntryPoint = "SetLocalTime")]
        public static extern int SetSystemTime(ref SystemTime lpSystemTime);

    [DllImport("kernel32.dll", SetLastError = true)]
        public static extern long GetLastError();

        private void button1_Click(object sender, EventArgs e)
        {
              DateTime setdt=DateTime.Parse(this.textBox1.Text);
              SystemTime st = new SystemTime();
              st.wYear = (short)setdt.Year;
              st.wMonth = (short)setdt.Month;
              st.wDay =(short) setdt.Day;
              st.wHour = (short)setdt.Hour;
              st.wMinute =(short)setdt.Minute;
              st.wSecond =(short) setdt.Second;
              int result = SetSystemTime(ref st);
              if (result == 1)
              {
                  MessageBox.Show("修改成功!");
              }
              else
              {
                  long errorCode = GetLastError();
                  //int code = Marshal.GetLastWin32Error();//.net常用这种方式代替GetLastError API
                  MessageBox.Show("修改失败,Win32错误代码是{0},请查看GetLastError返回值的意义列表或调用FormatMessage查看" + errorCode.ToString());
              }
         }

    PS:

    1、SetLocalTime与SetSystemTime的区别:SetLocalTime的用法与SetSystemTime基本相同,差别在于SetSystemTime所带的参数指定的是UTC时间(国际标准时间),也就是说,针对我们地区的电脑(东八区),这样的话,使用SysteSystemTime设置后,系统的时间,会比参数所设置的时间快8个小时)

    2、GetLastError返回值的意义(http://blog.chinaunix.net/u2/82288/showart_1335456.html)

    Best Regards,

    Charles Chen

  • 相关阅读:
    JDBC 复习4 批量执行SQL
    JDBC 复习3 存取Oracle大数据 clob blob
    Oracle复习
    Linux命令(1)grep
    JDBC 复习2 存取mysql 大数据
    JDBC 复习1 DBUtil
    php 环境搭建问题
    Windows 批处理 bat 开启 WiFi 菜单选项 设置ID PWD
    Bat 批处理启动和停止Oracle 服务
    docker 学习1 WSL docker ,Windows docker
  • 原文地址:https://www.cnblogs.com/Charles2008/p/1643449.html
Copyright © 2011-2022 走看看