C#调用DLL函数

托管代码与非托管代码的区别:

   1.托管代码所申请的资源统一由.NetFramework管理,你不用操心,非托管代码所申请的内存等资源则需要你手动去释放  

   2.非托管程序运行会很快,是二进制的,托管程序好写,但是速度就差的很多,资源会用的很多

   3.“程序"一般都是在对操作系统进行直接或者间接的操作  

   "托管程序"是需要通过访问公共语言运行时(cls)才能访问操作系统的程序,而“非托管程序”不用通过访问公共语言运行时(cls)可以直接访问操作系统的程序  

   4.vb.net,C#等写的程序是托管程序,VC++可以写托管程序,如果用到了内存管理,则只能编译为非托管程序

   VC++写托管的是要用.net的库,因为我们没有用.net,所以只用了非托管方式。

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

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

   [DLLImport(“DLL文件”)]

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

    其中:

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

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

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

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

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

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

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

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

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

    其它可选的DllImportAttribute 属性:

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

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

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

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

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

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

    举例:

    Newfile,选择visual C# Class。在文件中,创建一个public的类。把待测试的函数在这个类中作声明。

public class ClassName     
{        
   [DllImport("xxx.dll", EntryPoint = "xx")] 
        public static extern int StartVideo(
int nDevNum, int SwitchingChans, IntPtr Main, IntPtr hwndPreview);  
}  


   Xxx为待测试的dll名称,xx为dll中提供的方法函数。若要使用其它函数名,可以使用EntryPoint属性设置。

   如何用DllImport调用DLL中的非托管函数,但是这个是全局的函数,假若DLL中的非托管函数有一个静态变量S,每次调用这个函数的时候,静态变量S就自动加1。结果,当需要重新计数时,就不能得出想要的结果。所以,要注意啊,用DllImport调用DLL中的非托管函数是全局的、静态的函数。

因为C#中使用DllImport是不能像动态load/unloadassembly那样,所以只能借助API函数了。在kernel32.dll中,与动态库调用有关的函数包括[3]:

   ①LoadLibrary(或MFC 的AfxLoadLibrary),装载动态库。

   ②GetProcAddress,获取要引入的函数,将符号名或标识号转换为DLL内部地址。

   ③FreeLibrary(或MFC的AfxFreeLibrary),释放动态链接库。

   它们的原型分别是:

    HMODULELoadLibrary(LPCTSTR lpFileName);

    FARPROCGetProcAddress(HMODULE hModule, LPCWSTR lpProcName);

    BOOLFreeLibrary(HMODULE hModule);

   现在,我们可以用IntPtr hModule=LoadLibrary(“Count.dll”);来获得Dll的句柄,用IntPtrfarProc=GetProcAddress(hModule,”_count@4”);来获得函数的入口地址。

   但是,知道函数的入口地址后,怎样调用这个函数呢?因为在C#中是没有函数指针的,没有像C++那样的函数指针调用方式来调用函数,所以我们得借助其它方法。经过研究,发现我们可以通过结合使用System.Reflection.Emit及System.Reflection.Assembly里的类和函数达到我们的目的。为了以后使用方便及实现代码的复用,我们可以编写一个类。

   1) dld类的编写:

   1.打开项目“Test”,打开类视图,右击“Tzb”,选择“添加”-->“类”,类名设置为“dld”,即dynamicloading dll 的每个单词的开头字母。

   2.添加所需的命名空间及声明参数传递方式枚举:

using System.Runtime.InteropServices; // 用DllImport 需用此命名空间 
using System.Reflection; // 使用Assembly 类需用此命名空间  
using System.Reflection.Emit; // 使用ILGenerator 需用此命名空间


    3.在namespace test中,“public class dld”的上面,添加如下代码声明参数传递方式枚举:

 

/// < summary>   
    /// 参数传递方式枚举,ByValue 表示值传递,ByRef 表示址传递  
    /// < /summary>   
    public enum ModePass  
    { 
        ByValue = 0x0001,  
        ByRef = 0x0002  
}  

    4、在publicclass DLD中,添加如下代码:

 

public class DLD 
    { 
        [DllImport("kernel32.dll")] 
        public static extern IntPtr LoadLibrary(string lpFileName); 
 
        [DllImport("kernel32.dll")] 
        public static extern IntPtr GetProcAddress(IntPtr hModule, string lpProceName); 
 
  
 
        [DllImport("kernel32", EntryPoint = "FreeLibrary", SetLastError = true)] 
        public static extern bool FreeLibrary(IntPtr hModule); 
 
  
 
        /// < summary>   
        /// Loadlibrary 返回的函数库模块的句柄   
        /// < /summary>   
        private IntPtr hModule = IntPtr.Zero; 
  
        /// < summary>   
        /// GetProcAddress 返回的函数指针   
        /// < /summary>  
        private IntPtr farProc = IntPtr.Zero; 
 
        /// < summary>   
        /// 装载 Dll   
        /// < /summary>   
        /// < param name="lpFileName">DLL 文件名 < /param>  
        public void LoadDll(string lpFileName) 
        { 
  
            hModule = LoadLibrary(lpFileName); 
 
            if (hModule == IntPtr.Zero) 
                throw (new Exception(" 没有找到 :" + lpFileName + ".")); 
        } 
 
 
        /// < 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>   
        /// 卸载 Dll   
        /// < /summary>   
        public void UnLoadDll()  
        { 
           FreeLibrary(hModule);  
            hModule = IntPtr.Zero;  
            farProc = IntPtr.Zero; 
        } 
 
  
        /// < 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() + " 个参数没有给定正确的传递方式 .")); 
                    break;
                } 
            } 
 
            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);   
        } 
    }  

   2) dld类的使用:

   1.打开项目“Test”,向“Form1”窗体中添加一个按钮,和一个TestBox,Name改为txRet。视图中双击按钮,在“button1_Click”方法体上面添加代码,创建一个dld类实例,并进行测试。具体如下:

        private void button1_Click(object sender, EventArgs e) 
        { 
            int ret = 0;  
            dld myDLD = new dld();   
            myDLD.LoadDll("xxx.dll");  
            myDLD.LoadFun("InitSDK"); 
            object[] Parameters = new object[] { }; // 实参为0   
            Type[] ParameterTypes = new Type[] { }; // 实参类型为int  
            ModePass[] themode = new ModePass[] { }; // 传送方式为值传 
 
            Type Type_Return = typeof(int); // 返回类型为int 
            ret = (int)myDLD.Invoke(Parameters, ParameterTypes, themode, Type_Return); 
 
            txRet.Text = ret.ToString(); 
            if (ret != 1) 
            { 
                MessageBox.Show("InitSDK failed !");  
            } 
 
            if (ret == 1) 
            {  
                MessageBox.Show("InitSDK Sucessed !"); 
            }  
        }  


   其中,xxx为要测试的dll名称,InitSDK为dll中的要测试的函数。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值