从C#程序中调用非受管DLLs
从C#程序中调用非受管DLLs
- 文章概要:
- 众所周知,.NET已经渐渐成为一种技术时尚,那么C#很自然也成为一种编程时尚。如何利用浩如烟海的Win32 API以及以前所编写的 Win32 代码已经成为越来越多的C#程序员所关注的问题。本文将介绍如何从C#代码中调用非受管DLLs。内容包括:1、如果某个函数是一个带有串类型(char*)输出参数的Win32 API 或者是DLL输出函数,那么从C#中如何调用它并获取从参数中返回的串呢?2、如何调用有结构(struct)和回调(callback)作为参数的函数,如GetWindowsRect 和EnumWindows?
众所周知,.NET已经渐渐成为一种技术时尚,那么C#很自然也成为一种编程时尚。如何利用浩如烟海的Win32 API以及以前所编写的 Win32 代码已经成为越来越多的C#程序员所关注的问题。本文将介绍如何从C#代码中调用非受管DLLs。如果某个函数是一个带有串类型(char*)输出参数的Win32 API 或者是DLL输出函数,那么从C#中如何调用它呢?对于输入参数的情形问题到不大,但如何获取从参数中返回的串呢?此外,如何调用有结构(struct)和回调(callback)作为参数的函数,如GetWindowsRect 和EnumWindows?那我们又如何将参数从C++和MFC中转换成C# 所要的类型呢?下面就让我们来一一解决这些问题。 微软.NET的一个最主要的优势是它提供一个语言无关的开发系统。我们可以用Visual Basic、C++、C#等等语言来编写类,然后在其它语言中使用,我们甚至可以用不同的语言来派生类。但是如何调用以前开发的非受管DLL呢?方法是必须将.NET对象转化成结构、char*以及C语言的指针。用行话说就是参数必须被列集(marshal)。说到列集,用一两句话也说不清楚。所幸的是实现列集并不要我们知道太多的东西。 为了从C# 中调用DLL函数,首先必须要有一个声明,就象长期以来使用Visual Basic的程序员所做的那样,只不过在C#中使用的是DllImport关键字: 1. using System.Runtime.InteropServices; // DllImport所在的名字空间 2. public class Win32 { 3. [DllImport( "User32.Dll" )] 4. public static extern void SetWindowText( int h, String s); 5. } 在C#中,DllImport关键字作用是告诉编译器入口点在哪里,并将打包函数捆绑在一个类中。我们可以为这类取任何名字,这里不妨将类名取为 Win32。我们甚至可以将这个类放到一个名字空间中,就象下面的代码这样: 01. /////////////////////// 02. Win32API.cs 源代码 03. 04. // Win32API: 此为名字空间,打包所选的Win32 API 函数 05. // 编译方法: 06. // csc /t:library /out:Win32API.dll Win32API.cs 07. // 08. using System; 09. using System.Drawing; 10. using System.Text; 11. using System.Runtime.InteropServices; 12. 13. ///////////////////////////////////////////////////////////////// 14. // 包装Win32 API函数的名字空间。想用哪个Win32 API,往里添加即可。 15. // 16. namespace Win32API { 17. [StructLayout(LayoutKind.Sequential)] 18. public struct POINT { 19. public POINT( int xx, int yy) { x=xx; y=yy; } 20. public int x; 21. public int y; 22. public override string ToString() { 23. String s = String.Format( "({0},{1})" , x, y); 24. return s; 25. } 26. } 27. 28. [StructLayout(LayoutKind.Sequential)] 29. public struct SIZE { 30. public SIZE( int cxx, int cyy) { cx=cxx; cy=cyy; } 31. public int cx; 32. public int cy; 33. public override string ToString() { 34. String s = String.Format( "({0},{1})" , cx, cy); 35. return s; 36. } 37. } 38. 39. [StructLayout(LayoutKind.Sequential)] 40. public struct RECT { 41. public int left; 42. public int top; 43. public int right; 44. public int bottom; 45. public int Width() { return right - left; } 46. public int Height() { return bottom - top; } 47. public POINT TopLeft() { return new POINT(left,top); } 48. public SIZE Size() { return new SIZE(Width(), Height()); } 49. public override string ToString() { 50. String s = String.Format( "{0}x{1}" , TopLeft(), Size()); 51. return s; 52. } 53. } 54. 55. public class Win32 { 56. [DllImport( "user32.dll" )] 57. public static extern bool IsWindowVisible( int hwnd); 58. 59. [DllImport( "user32.dll" )] 60. public static extern int GetWindowText( int hwnd, 61. StringBuilder buf, int nMaxCount); 62. 63. [DllImport( "user32.dll" )] 64. public static extern int GetClassName( int hwnd, 65. [MarshalAs(UnmanagedType.LPStr)] StringBuilder buf, 66. int nMaxCount); 67. 68. [DllImport( "user32.dll" )] 69. public static extern int GetWindowRect( int hwnd, ref RECT rc); 70. 71. [DllImport( "user32.dll" )] 72. // 注意,运行时知道如何列集一个矩形 73. public static extern int GetWindowRect( int hwnd, ref Rectangle rc); 74. 75. ...... 76. } 77. } 用下面的命令行可以编译这段代码: 1. csc /t:library /out:Win32API.dll Win32API.cs 成功编译后,我们就有了一个可以在C#工程中使用的动态库了(Win32API.dll)。 1. using Win32API; 2. int hwnd = // get it... 3. String s = "I''''m so cute." ; 4. Win32.SetWindowText(hwnd, s); 编译器知道在user32.dll中找到SetWindowText,并在调用前自动将串转换为LPTSTR (TCHAR*)。真是神奇!.NET是如何实现的呢?其实,每一个C#类型都有一个缺省的列集类型。对于串来说,它的列集类型就是LPTSTR。但如果调用的是GetWindowText,它的串参数是一个输出参数,而非输入参数,因为串是不变的,再象上面这样处理就行不通了。我们可能一点都没有注意到,不论什么时候处理一个串时,都会创建一个新串。要想修改这个串,必须用StringBuilder: 1. using System.Text; // StringBuilder所在的名字空间 2. public class Win32 { 3. [DllImport( "user32.dll" )] 4. public static extern int GetWindowText( int hwnd, 5. StringBuilder buf, int nMaxCount); 6. } StringBuilder缺省的列集类型是LPTSTR,但是GetWindowText现在可以修改实际的串。 1. int hwnd = // get it... 2. StringBuilder sb = new StringBuilder(256); 3. Win32.GetWindowText(hwnd, sb, sb.Capacity); 所以我们第一个问题的答案就是:使用StringBuilder。 前面讨论的方法固然可以行得通,但有一种情况没有考虑,那就是如果缺省的列集类型不是你想要的类型怎么办?例如想要调用GetClassName,Windows编程高手都知道,GetClassName的参数与大多数其它的API函数的参数有所不同,它的串参数是LPSTR (char*),甚至是Unicode串。如果传递一个串,公共语言运行时(CLR)将把它转换成TCHARs――是不是很糟啊!不用害怕,我们可以用MarshalAs来改写缺省的处理: 1. [DllImport( "user32.dll" )] 2. public static extern int GetClassName( int hwnd, 3. [MarshalAs(UnmanagedType.LPStr)] StringBuilder buf, 4. int nMaxCount); 现在我们调用GetClassName,.NET将串作为ANSI字符传递,而不是宽字符,搞掂! 以上我们解决了如何获取函数载参数中返回的字符串。下面我们来看看结构参数和回调参数的情形。不用说,.NET肯定有办法处理它们。就拿GetWindowRect为例。这个函数用窗口屏幕坐标填充一个RECT。 1. // 在C/C++中 2. RECT rc; 3. HWND hwnd = FindWindow( "foo" ,NULL); 4. ::GetWindowRect(hwnd, &rc); 在C#中如何调用呢?如何传递RECT呢?方法是将它作为一个C#结构,用另一个属性:它就是StructLayout: 1. [StructLayout(LayoutKind.Sequential)] 2. public struct RECT { 3. public int left; 4. public int top; 5. public int right; 6. public int bottom; 7. } 一旦有了结构定义,便可以象下面这样来打包实现: 1. [DllImport( "user32.dll" )] 2. public static extern int 3. GetWindowRect( int hwnd, ref RECT rc); 注意这里用到了ref,这一点很重要,CLR会将RECT作为引用传递,以便函数可以修改我们的对象,而不是无名字的堆栈拷贝。定义了GetWindowRect之后,我们可以象下面这样调用: 1. RECT rc = new RECT(); 2. int hwnd = // get it ... 3. Win32.GetWindowRect(hwnd, ref rc); 注意这里必须声明并使用ref――罗嗦!C# 结构的缺省列集类型还能是什么?――LPStruct,所以就不必再用MarshalAs了。但如果RECT是个类,而非结构的话,那就必须象下面这样实现打包: 1. // 如果RECT 是个类,而不是结构 2. [DllImport( "user32.dll" )] 3. public static extern int 4. GetWindowRect( int hwnd, 5. [MarshalAs(UnmanagedType.LPStruct)] RECT rc); C#与C++类似,许多事情都可以殊途同归,System.Drawing中已经有了一个Rectangle结构用来处理矩形,所以为什么要重新发明轮子呢? 1. [DllImport( "user32.dll" )] 2. public static extern int GetWindowRect( int hwnd, ref Rectangle rc); 运行时既然已经知道如何将Rectangle作为Win32 RECT进行列集。请注意,在实际的代码中就没有必要再调用GetWindowRect(Get/SetWindowText亦然),因为Windows.Forms.Control类已具有这样的属性:用Control.DisplayRectangle获取窗口矩形,用Control.Text设置/获取控件文本 1. Rectangle r = mywnd.DisplayRectangle; 2. mywnd.Text = "I''''m so cute" ; 如果出于某种原因已知的是某个HWND,而不是一个控件派生对象,那么只需要象示范的那样来打包API。以上我们已经搞掂了串、结构以及矩形……还有什么呢?对了,还有回调(callbacks)。如何将回调从C#传递到非受管代码呢?记住只要用委托(delegate)即可: 1. delegate bool EnumWindowsCB( int hwnd, int lparam); 一旦声明了委托/回调类型,就可以象下面这样打包: 1. [DllImport( "user32" )] 2. public static extern int 3. EnumWindows(EnumWindowsCB cb, int lparam); 上面的delegate仅仅是声明了一个委托类型,我们还必须在类中提供一个实际的委托实现: 1. // 在类中 2. public static bool MyEWP( int hwnd, int lparam) { 3. // do something 4. return true ; 5. } 然后对它进行打包处理: 1. EnumWindowsCB cb = new EnumWindowsCB(MyEWP); 2. Win32.EnumWindows(cb, 0); 聪明的读者回注意到我们这里掩饰了lparam的问题,在C中,如果你给EnumWindows一个LPARAM,则Windows会用它通知回调函数。一般典型的lparam是一个结构或类指针,其中包含着我们需要的上下文信息。但是记住,在.NET中绝对不能提到"指针"!那么如何做呢?这是可以将lparam声明为IntPtr并用GCHandle对它进行打包: 01. // 现在lparam 是 IntPtr 02. delegate bool EnumWindowsCB( int hwnd, IntPtr lparam); 03. 04. // 在GCHandle中打包对象 05. MyClass obj = new MyClass(); 06. GCHandle gch = GCHandle.Alloc(obj); 07. EnumWindowsCB cb = new EnumWindowsCB(MyEWP); 08. Win32.EnumWindows(cb, (IntPtr)gch); 09. gch.Free(); 最后不要忘了调用Free! C#中有时也需要与以往一样必须要我们自己释放占用的内存。为了存取载枚举器中的lparam"指针",必须使用GCHandle.Target。 1. public static bool MyEWP( int hwnd, IntPtr param) { 2. GCHandle gch = (GCHandle)param; 3. MyClass c = (MyClass)gch.Target; 4. // ... use it 5. return true ; 6. } 下面是一个窗口数组类: 01. ////////////////////////////////////////////////////////////////////////// 02. WinArray.cs 03. 04. // WinArray: 用EnumWindows 产生顶层窗口的清单ArrayList 05. // 06. using System; 07. using System.Collections; 08. using System.Runtime.InteropServices; 09. 10. namespace WinArray { 11. 12. public class WindowArray : ArrayList { 13. private delegate bool EnumWindowsCB( int hwnd, IntPtr param); 14. 15. // 这里声明的是private类型的委托,因为只有我使用它,其实没必要这样做。 16. [DllImport( "user32" )] 17. private static extern int EnumWindows(EnumWindowsCB cb, 18. IntPtr param); 19. 20. private static bool MyEnumWindowsCB( int hwnd, IntPtr param) { 21. GCHandle gch = (GCHandle)param; 22. WindowArray itw = (WindowArray)gch.Target; 23. itw.Add(hwnd); 24. return true ; 25. } 26. 27. // 这是唯一的public 类型方法,你需要调用的唯一方法 28. public WindowArray() { 29. GCHandle gch = GCHandle.Alloc( this ); 30. EnumWindowsCB ewcb = new EnumWindowsCB(MyEnumWindowsCB); 31. EnumWindows(ewcb, (IntPtr)gch); 32. gch.Free(); 33. } 34. } 35. } 这个类将EnumWindows封装在一个数组中,不用我们再去进行繁琐的委托和回调,我们可以象下面这样轻松使用这个类: 1. WindowArray wins = new WindowArray(); 2. foreach ( int hwnd in wins) { 3. // do something 4. } 是不是很帅啊!我们甚至还可以在受管C++中使用DllImport风格的包装类。在.NET环境中,只要能进行相应的转换,便可以在受管和非受管世界之间随心所欲地聘驰, 大多数情况下的转换是自动的,不必关心太多的事情。需要进行MarshalAs或者打包GCHandle的情况很少。有关C#和非受管C++之间的平台调用的其它细节问题,可以参考.NET的有关文档。 下面是本文提供的一个带有开关的控制台小程序ListWin。它的功能是列出所有顶层窗口,输出可以显示HWNDs、窗口类名、窗口标题以及窗口矩形,用RECT或Rectangle。这些内容的显示可用开关控制。 01. ////////////////////////////////////////////////////// 02. ListWin.cs 源代码 03. using System; 04. using System.Text; 05. using System.Drawing; 06. using System.Diagnostics; 07. using System.Runtime.InteropServices; 08. using Win32API; // 自己写的API打包器 09. using WinArray; // 自己写的窗口迭代器 10. class MyApp { 11. // 全局命令行开关 12. static bool bRectangle = false ; // 用Rectangle显示窗口矩形 13. static bool bRect = false ; // 用RECT显示窗口矩形 14. static bool bClassName = false ; // 显示类名 15. static bool bTitle = false ; // 显示标题 16. static bool bHwnd = false ; // 显示HWND 17. [STAThread] 18. // 主程序入口 19. static int Main(string[] args) { 20. // 解析命令行,开关的顺序可以任意定 21. if (args.GetLength(0)<=0) 22. return help(); 23. for ( int i=0, len=args.GetLength(0); i sr下图是ListWin运行的输出: 图一ListWin 运行后的输出 有关细节请参考源代码。 |
从C#程序中调用非受管DLLs的更多相关文章
- Native Application 开发详解(直接在程序中调用 ntdll.dll 中的 Native API,有内存小、速度快、安全、API丰富等8大优点)
文章目录: 1. 引子: 2. Native Application Demo 展示: 3. Native Application 简介: 4. Native Ap ...
- 在网页程序或Java程序中调用接口实现短信猫收发短信的解决方案
方案特点: 在网页程序或Java程序中调用接口实现短信猫收发短信的解决方案,简化软件开发流程,减少各应用系统相同模块的重复开发工作,提高系统稳定性和可靠性. 基于HTTP协议的开发接口 使用特点在网页 ...
- iOS程序中调用系统自带应用(短信,邮件,浏览器,地图,appstore,拨打电话,iTunes,iBooks )
在网上找到了下在记录下来以后方便用 在程序中调用系统自带的应用,比如我进入程序的时候,希望直接调用safar来打开一个网页,下面是一个简单的使用:
- Java程序中调用Python脚本的方法
在程序开发中,有时候需要Java程序中调用相关Python脚本,以下内容记录了先关步骤和可能出现问题的解决办法. 1.在Eclipse中新建Maven工程: 2.pom.xml文件中添加如下依赖包之后 ...
- 如何在程序中调用Caffe做图像分类
Caffe是目前深度学习比较优秀好用的一个开源库,采样c++和CUDA实现,具有速度快,模型定义方便等优点.学习了几天过后,发现也有一个不方便的地方,就是在我的程序中调用Caffe做图像分类没有直接的 ...
- C++程序中调用WebService的实现
前言 因为最近的项目中需要运用到在MFC程序中调用WebService里面集成好了的函数,所以特意花了一天的时间来研究WebService的构建以及如何在MFC的程序中添加Web引用,进而来实现在C+ ...
- 利用 gnuplot_i 在你的 c 程序中调用 GNUPLOT
这是一篇非常早曾经写的小文章,最初发表于我的搜狐博客(2008-09-23 22:55).由于自从转移到这里后,sohu 博客就不再维护了,所以把这篇文章也一起挪了过来. GNUPLOT 是一款功能强 ...
- ASP程序中调用Now()总显示“上午”和“下午”,如何解决?
ASP程序中调用Now()总显示这样的格式:“2007-4-20 下午 06:06:38”,我要的正确格式为“2007-4-20 18:06:38”,我已经通过控制面板==>区域和语言选项==& ...
- Java-main方法中调用非static方法
java的calss中,在public static void main(String[] args) { }方法中调用非static的方法:在main方法中创建该calss的对象,用对象调用非sta ...
随机推荐
- 使用 SQL Server Management Studio的活动和监视器 查看运行的SQL语句
使用SQL Server Management Studio可以查看SQL Server 服务器执行的SQL语句,支持sql server,(LocalDB)\V11.0,Projects\v12和s ...
- LA 4670 Dominating Patterns (AC自动机)
题意:给定一个一篇文章,然后下面有一些单词,问这些单词在这文章中出现过几次. 析:这是一个AC自动机的裸板,最后在匹配完之后再统计数目就好. 代码如下: #pragma comment(linker, ...
- 《Windows核心编程系列》十四谈谈默认堆和自定义堆
堆 前面我们说过堆非常适合分配大量的小型数据.使用堆可以让程序员专心解决手头的问题,而不必理会分配粒度和页面边界之类的事情.因此堆是管理链表和数的最佳方式.但是堆进行内存分配和释放时的速度比其他方式都 ...
- 【洛谷4769】[NOI2018] 冒泡排序(动态规划_组合数学)
题目: 洛谷 4769 博客页面左下角的嘴嘴瓜封神之战中的题目 分析: 一个排列交换次数为 \(\frac{1}{2}\sum_{i=1}^{n}|i-p_i|\) 的充要条件是这个排列不存在长度为 ...
- C# System.IO 文件流输入输出
一.读写文本文件 可以用fileStream来读写文本文件,但是FileStream是通过字节形式来读写数据的,要把字节数据转换为文本,要自己处理编码转换. 对于文本文件的读写,通常用 StreamR ...
- WebSphere中数据源连接池太小导致的连接超时错误记录
WebSphere中数据源连接池太小导致的连接超时错误记录. 应用连接超时错误信息: [// ::: CST] webapp E com.ibm.ws.webcontainer.webapp.WebA ...
- Statistics gathering and SQL Tuning Advisor
1. https://www.pythian.com/blog/statistics-gathering-and-sql-tuning-advisor/ Our monitoring software ...
- jmeter(四)检查点
JMeter也有像LR中的检查点,本篇就来介绍下JMeter的检查点如何去实现. JMeter里面的检查点通过添加断言来完成. 检查点:上一章讲到,我们对用户名和密码进行了参数化,那么怎样来判断jme ...
- 每天学点linux命令之locate 与 find 命令
定位某个文件的位置 方法一 sudo find / -name libGLEW.so.1.13 -type f 方法二 sudo updatedb && locate libopenc ...
- Oracle数据库的SQL语句之完整性约束——基础篇
SELECT * FROM tb_clazz;SELECT * FROM tb_student; INSERT INTO tb_clazz(code,NAME,bzr) VALUES('1401',' ...