| 雷's profileAbbeyGong's SpacesPhotosBlog | Help |
|
9/24/2008 什么是美国次贷危机,看后你就明了在美国,贷款是非常普遍的现象,从房子到汽车,从信用卡到电话账单,贷款无处不在。当地人很少全款买房,通常都是长时间贷款。可是我们也知道,在这里失业和再就业是很常见的现象。这些收入并不稳定甚至根本没有收入的人,他们怎么买房呢?因为信用等级达不到标准,他们就被定义为次级贷款者。 (注:Credit Default Swap,信用违约交换)好了,华尔街就是这些天才产品的床:不是都觉得原来的CDO风险高吗,那我投保好了,每年从CDO里面拿出一部分钱作为保金,白送给保险公司,但是将来出了风险,大家一起承担。 9/19/2008 “老头子,我要走了,抱抱我吧。”老太太醒过来了,心脏跳的忽快忽慢的,让她有些吃不消了。 9/17/2008 sqlserver 提取一定范围记录select top N * from table 9/13/2008 MCP68 主板开启AHCI模式攻略1、在SATA普通状态安装Windows XP(或Windows 2003)。 9/6/2008 英文地址翻译翻译原则:先小后大。 9/3/2008 动态链接库什么是动态链接库?DLL三个字母对于你来说一定很熟悉吧,它是Dynamic Link Library 的缩写形式,动态链接库 (DLL) 是作为共享函数库的可执行文件。动态链接提供了一种方法,使进程可以调用不属于其可执行代码的函数。函数的可执行代码位于一个 DLL 中,该 DLL 包含一个或多个已被编译、链接并与使用它们的进程分开存储的函数。DLL 还有助于共享数据和资源。多个应用程序可同时访问内存中单个 DLL 副本的内容。 和大多数程序员一样,你一定很使用过DLL吧。也曾感受到它的带给你程序设计和编码上的好错吧今天我想和大家探讨一个主题:如何在C#创建和调用DLL(动态链接库), 其实在很大意义上而讲,DLL让我更灵活的组织编写我们的应用程序,作为软件设计者,可一个根据它来达到很高的代码重用效果。下面我来介绍一下在C#中如何创建和调用DLL。 二、准备工作 我们需要对我们接下来要做的事情做个简单的介绍,在本文我们将利用C#语言创建一个名为 MyDLL.DLL的动态链接库,在这个动态链接库文件中我们将提供两个功能一个是对两个参数交换他们的值,另一个功能是求两个参数的最大公约数。然后创建一个应用程序使用这个DLL。运行并输出结果。 三、创建DLL 让我们创建以下三个C#代码文件: 1、 MySwap.cs using System; namespace MyMethods { public class SwapClass { public static bool Swap(ref long i,ref long j) { i = i+j; j = i-j; i = i-j; return true; } } } 2、 MyMaxCD.cs using System; namespace MyMethods { public class MaxCDClass { public static long MaxCD(long i, long j) { long a,b,temp; if(i>j) { a = i; b = j; } else { b = i; a = j; } temp = a % b; while(temp!=0) { a = b; b = temp; temp = a % b; } return b; } } } }需要注意的是:我们在制作这两个文件的时候可以用Visual Studio.NET或者其他的文本编辑器,就算是记事本也可以。这两个文件虽然不在同一个文件里面,但是他们是属于同一个namespace(名称空间)这对以后我们使用这两个方法提供了方便。当然他们也可以属于不同的名称空间,这是完全可以的,但只是在我们应用他们的时候就需要引用两个不同的名称空间,所以作者建议还是写在一个名称空间下面比较好。 using MyMethods; //这里我们引用刚才定义的名称空间,如果刚才的两个文件我们写在两个不同的名称空间 { public static void Main(string[] args) { if (args.Length != 2) { Console.WriteLine("Usage: MyClient <num1> <num2>"); return; } long num1 = long.Parse(args[0]); long num2 = long.Parse(args[1]); SwapClass.Swap(ref num1,ref num2); // 请注意,文件开头的 using 指令使您得以在编译时使用未限定的类名来引用 DLL 方法 Console.WriteLine("The result of swap is num1 = {0} and num2 ={1}",num1, num2); long maxcd = MaxCDClass.MaxCD(num1,num2); Console.WriteLine("The MaxCD of {0} and {1} is {2}",num1, num2, maxcd); } } 若要生成可执行文件 MyClient.exe,请使用以下命令行: csc /out:MyClient.exe /reference:MyLibrary.DLL MyClient.cs /out 编译器选项通知编译器输出 EXE 文件并且指定输出文件名 (MyClient.exe)。/reference 编译器选项指定该程序所引用的 DLL 文件。 五、执行 若要运行程序,请输入 EXE 文件的名称,文件名的后面跟两个数字,例如: MyClient 123 456 六、输出 The result of s wap is num1 = 456 and num2 = 123 The MaxCD of 456 and 123 is 3 七、小结 动态链接具有下列优点: 节省内存和减少交换操作。很多进程可以同时使用一个 DLL,在内存中共享该 DLL 的一个副本。相反,对于每个用静态链接库生成的应用程序,Windows 必须在内存中加载库代码的一个副本。 c#调用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”的binDebug文件夹中,按“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”上面添加如下代码声明参数传递方式枚举: /// <summary> /// 参数传递方式枚举 ,ByValue 表示值传递 ,ByRef 表示址传递 /// </summary> public enum ModePass { ByValue = 0x0001, ByRef = 0x0002 } 3. 声明LoadLibrary、GetProcAddress、FreeLibrary及私有变量hModule和farProc: /// <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. 添加LoadDll方法,并为了调用时方便,重载了这个方法: /// <summary> /// 装载 Dll /// </summary> /// <param name="lpFileName">DLL 文件名 </param> 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方法,并为了调用时方便,也重载了这个方法,方法的具体代码及注释如下: /// <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; } 用Visual C#调用Windows API函数Api函数是构筑Windws应用程序的基石,每一种Windows应用程序开发工具,它提供的底层函数都间接或直接地调用了Windows API函数,同时为了实现功能扩展,一般也都提供了调用WindowsAPI函数的接口, 也就是说具备调用动态连接库的能力。Visual C#和其它开发工具一样也能够调用动态链接库的API函数。.NET框架本身提供了这样一种服务,允许受管辖的代码调用动态链接库中实现的非受管辖函数,包括操作系统提供的Windows API函数。它能够定位和调用输出函数,根据需要,组织其各个参数(整型、字符串类型、数组、和结构等等)跨越互操作边界。 下面以C#为例简单介绍调用API的基本过程: 动态链接库函数的声明 动态链接库函数使用前必须声明,相对于VB,C#函数声明显得更加罗嗦,前者通过 Api Viewer粘贴以后,可以直接使用,而后者则需要对参数作些额外的变化工作。 动态链接库函数声明部分一般由下列两部分组成,一是函数名或索引号,二是动态链接库的文件名。 譬如,你想调用User32.DLL中的MessageBox函数,我们必须指明函数的名字MessageBoxA或MessageBoxW,以及库名字User32.dll,我们知道Win32 API对每一个涉及字符串和字符的函数一般都存在两个版本,单字节字符的ANSI版本和双字节字符的UNICODE版本。 下面是一个调用API函数的例子: [DllImport("KERNEL32.DLL", EntryPoint="MoveFileW", SetLastError=true, CharSet=CharSet.Unicode, ExactSpelling=true, CallingConvention=CallingConvention.StdCall)] public static extern bool MoveFile(String src, String dst); 其中入口点EntryPoint标识函数在动态链接库的入口位置,在一个受管辖的工程中,目标函数的原始名字和序号入口点不仅标识一个跨越互操作界限的函数。而且,你还可以把这个入口点映射为一个不同的名字,也就是对函数进行重命名。重命名可以给调用函数带来种种便利,通过重命名,一方面我们不用为函数的大小写伤透脑筋,同时它也可以保证与已有的命名规则保持一致,允许带有不同参数类型的函数共存,更重要的是它简化了对ANSI和Unicode版本的调用。CharSet用于标识函数调用所采用的是Unicode或是ANSI版本,ExactSpelling=false将告诉编译器,让编译器决定使用Unicode或者是Ansi版本。其它的参数请参考MSDN在线帮助. 在C#中,你可以在EntryPoint域通过名字和序号声明一个动态链接库函数,如果在方法定义中使用的函数名与DLL入口点相同,你不需要在EntryPoint域显示声明函数。否则,你必须使用下列属性格式指示一个名字和序号。 [DllImport("dllname", EntryPoint="Functionname")] [DllImport("dllname", EntryPoint="#123")] 值得注意的是,你必须在数字序号前加“#” 下面是一个用MsgBox替换MessageBox名字的例子: [C#] using System.Runtime.InteropServices; public class Win32 { [DllImport("user32.dll", EntryPoint="MessageBox")] public static extern int MsgBox(int hWnd, String text, String caption, uint type); } 许多受管辖的动态链接库函数期望你能够传递一个复杂的参数类型给函数,譬如一个用户定义的结构类型成员或者受管辖代码定义的一个类成员,这时你必须提供额外的信息格式化这个类型,以保持参数原有的布局和对齐。 C#提供了一个StructLayoutAttribute类,通过它你可以定义自己的格式化类型,在受管辖代码中,格式化类型是一个用StructLayoutAttribute说明的结构或类成员,通过它能够保证其内部成员预期的布局信息。布局的选项共有三种: 布局选项 描述 LayoutKind.Automatic 为了提高效率允许运行态对类型成员重新排序。 注意:永远不要使用这个选项来调用不受管辖的动态链接库函数。 LayoutKind.Explicit 对每个域按照FieldOffset属性对类型成员排序 LayoutKind.Sequential 对出现在受管辖类型定义地方的不受管辖内存中的类型成员进行排序。 传递结构成员 下面的例子说明如何在受管辖代码中定义一个点和矩形类型,并作为一个参数传递给User32.dll库中的PtInRect函数, 函数的不受管辖原型声明如下: BOOL PtInRect(const RECT *lprc, POINT pt); 注意你必须通过引用传递Rect结构参数,因为函数需要一个Rect的结构指针。 [C#] using System.Runtime.InteropServices; [StructLayout(LayoutKind.Sequential)] public struct Point { public int x; public int y; } [StructLayout(LayoutKind.Explicit] public struct Rect { [FieldOffset(0)] public int left; [FieldOffset(4)] public int top; [FieldOffset(8)] public int right; [FieldOffset(12)] public int bottom; } class Win32API { [DllImport("User32.dll")] public static extern Bool PtInRect(ref Rect r, Point p); } 类似你可以调用GetSystemInfo函数获得系统信息: ? using System.Runtime.InteropServices; [StructLayout(LayoutKind.Sequential)] public struct SYSTEM_INFO { public uint dwOemId; public uint dwPageSize; public uint lpMinimumApplicationAddress; public uint lpMaximumApplicationAddress; public uint dwActiveProcessorMask; public uint dwNumberOfProcessors; public uint dwProcessorType; public uint dwAllocationGranularity; public uint dwProcessorLevel; public uint dwProcessorRevision; } [DllImport("kernel32")] static extern void GetSystemInfo(ref SYSTEM_INFO pSI); SYSTEM_INFO pSI = new SYSTEM_INFO(); GetSystemInfo(ref pSI); 类成员的传递 同样只要类具有一个固定的类成员布局,你也可以传递一个类成员给一个不受管辖的动态链接库函数,下面的例子主要说明如何传递一个sequential顺序定义的MySystemTime类给User32.dll的GetSystemTime函数, 函数用C/C++调用规范如下: void GetSystemTime(SYSTEMTIME* SystemTime); 不像传值类型,类总是通过引用传递参数. [C#] [StructLayout(LayoutKind.Sequential)] public class MySystemTime { public ushort wYear; public ushort wMonth; public ushort wDayOfWeek; public ushort wDay; public ushort wHour; public ushort wMinute; public ushort wSecond; public ushort wMilliseconds; } class Win32API { [DllImport("User32.dll")] public static extern void GetSystemTime(MySystemTime st); } 回调函数的传递: 从受管辖的代码中调用大多数动态链接库函数,你只需创建一个受管辖的函数定义,然后调用它即可,这个过程非常直接。 如果一个动态链接库函数需要一个函数指针作为参数,你还需要做以下几步: 首先,你必须参考有关这个函数的文档,确定这个函数是否需要一个回调;第二,你必须在受管辖代码中创建一个回调函数;最后,你可以把指向这个函数的指针作为一个参数创递给DLL函数,. 回调函数及其实现: 回调函数经常用在任务需要重复执行的场合,譬如用于枚举函数,譬如Win32 API 中的EnumFontFamilies(字体枚举), EnumPrinters(打印机), EnumWindows (窗口枚举)函数. 下面以窗口枚举为例,谈谈如何通过调用EnumWindow 函数遍历系统中存在的所有窗口 分下面几个步骤: 1. 在实现调用前先参考函数的声明 BOOL EnumWindows(WNDENUMPROC lpEnumFunc, LPARMAM IParam) 显然这个函数需要一个回调函数地址作为参数. 2. 创建一个受管辖的回调函数,这个例子声明为代表类型(delegate),也就是我们所说的回调,它带有两个参数hwnd和lparam,第一个参数是一个窗口句柄,第二个参数由应用程序定义,两个参数均为整形。 当这个回调函数返回一个非零值时,标示执行成功,零则暗示失败,这个例子总是返回True值,以便持续枚举。 3. 最后创建以代表对象(delegate),并把它作为一个参数传递给EnumWindows 函数,平台会自动地 把代表转化成函数能够识别的回调格式。 [C#] using System; using System.Runtime.InteropServices; public delegate bool CallBack(int hwnd, int lParam); public class EnumReportApp { [DllImport("user32")] public static extern int EnumWindows(CallBack x, int y); public static void Main() { CallBack myCallBack = new CallBack(EnumReportApp.Report); EnumWindows(myCallBack, 0); } public static bool Report(int hwnd, int lParam) { Console.Write("窗口句柄为"); Console.WriteLine(hwnd); return true; } } 指针类型参数传递: 在Windows API函数调用时,大部分函数采用指针传递参数,对一个结构变量指针,我们除了使用上面的类和结构方法传递参数之外,我们有时还可以采用数组传递参数。 下面这个函数通过调用GetUserName获得用户名 BOOL GetUserName( LPTSTR lpBuffer, // 用户名缓冲区 LPDWORD nSize // 存放缓冲区大小的地址指针 ); [DllImport("Advapi32.dll", EntryPoint="GetComputerName", ExactSpelling=false, SetLastError=true)] static extern bool GetComputerName ( [MarshalAs(UnmanagedType.LPArray)] byte[] lpBuffer, [MarshalAs(UnmanagedType.LPArray)] Int32[] nSize ); 这个函数接受两个参数,char * 和int *,因为你必须分配一个字符串缓冲区以接受字符串指针,你可以使用String类代替这个参数类型,当然你还可以声明一个字节数组传递ANSI字符串,同样你也可以声明一个只有一个元素的长整型数组,使用数组名作为第二个参数。上面的函数可以调用如下: byte[] str=new byte[20]; Int32[] len=new Int32[1]; len[0]=20; GetComputerName (str,len); MessageBox.Show(System.Text.Encoding.ASCII.GetString(str)); 最后需要提醒的是,每一种方法使用前必须在文件头加上: using System.Runtime.InteropServices; |
|
|