从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的更多相关文章

  1. Native Application 开发详解(直接在程序中调用 ntdll.dll 中的 Native API,有内存小、速度快、安全、API丰富等8大优点)

    文章目录:                   1. 引子: 2. Native Application Demo 展示: 3. Native Application 简介: 4. Native Ap ...

  2. 在网页程序或Java程序中调用接口实现短信猫收发短信的解决方案

    方案特点: 在网页程序或Java程序中调用接口实现短信猫收发短信的解决方案,简化软件开发流程,减少各应用系统相同模块的重复开发工作,提高系统稳定性和可靠性. 基于HTTP协议的开发接口 使用特点在网页 ...

  3. iOS程序中调用系统自带应用(短信,邮件,浏览器,地图,appstore,拨打电话,iTunes,iBooks )

    在网上找到了下在记录下来以后方便用 在程序中调用系统自带的应用,比如我进入程序的时候,希望直接调用safar来打开一个网页,下面是一个简单的使用:

  4. Java程序中调用Python脚本的方法

    在程序开发中,有时候需要Java程序中调用相关Python脚本,以下内容记录了先关步骤和可能出现问题的解决办法. 1.在Eclipse中新建Maven工程: 2.pom.xml文件中添加如下依赖包之后 ...

  5. 如何在程序中调用Caffe做图像分类

    Caffe是目前深度学习比较优秀好用的一个开源库,采样c++和CUDA实现,具有速度快,模型定义方便等优点.学习了几天过后,发现也有一个不方便的地方,就是在我的程序中调用Caffe做图像分类没有直接的 ...

  6. C++程序中调用WebService的实现

    前言 因为最近的项目中需要运用到在MFC程序中调用WebService里面集成好了的函数,所以特意花了一天的时间来研究WebService的构建以及如何在MFC的程序中添加Web引用,进而来实现在C+ ...

  7. 利用 gnuplot_i 在你的 c 程序中调用 GNUPLOT

    这是一篇非常早曾经写的小文章,最初发表于我的搜狐博客(2008-09-23 22:55).由于自从转移到这里后,sohu 博客就不再维护了,所以把这篇文章也一起挪了过来. GNUPLOT 是一款功能强 ...

  8. ASP程序中调用Now()总显示“上午”和“下午”,如何解决?

    ASP程序中调用Now()总显示这样的格式:“2007-4-20 下午 06:06:38”,我要的正确格式为“2007-4-20 18:06:38”,我已经通过控制面板==>区域和语言选项==& ...

  9. Java-main方法中调用非static方法

    java的calss中,在public static void main(String[] args) { }方法中调用非static的方法:在main方法中创建该calss的对象,用对象调用非sta ...

随机推荐

  1. 【HNOI2004】 打鼹鼠

    [题目链接] 点击打开链接 [算法] 动态规划 f[i]表示上一次打了第i只鼹鼠,所能打死的最多的鼹鼠数量 [代码] #include<bits/stdc++.h> using names ...

  2. 分布式缓存一致性hash算法

    当服务器不多,并且不考虑扩容的时候,可直接使用简单的路由算法,用服务器数除缓存数据KEY的hash值,余数作为服务器下标即可. 但是当业务发展,网站缓存服务需要扩容时就会出现问题,比如3台缓存服务器要 ...

  3. ASP.NET Core MVC 打造一个简单的图书馆管理系统 (修正版)(二)数据库初始化、基本登录页面以及授权逻辑的建立

    前言: 本系列文章主要为我之前所学知识的一次微小的实践,以我学校图书馆管理系统为雏形所作. 本系列文章主要参考资料: 微软文档:https://docs.microsoft.com/zh-cn/asp ...

  4. 洛谷P4344 [SHOI2015]脑洞治疗仪(珂朵莉树)

    传送门 看到区间推倒……推平就想到珂朵莉树 挖脑洞直接assign,填坑先数一遍再assign再暴力填,数数的话暴力数 //minamoto #include<iostream> #inc ...

  5. 【转】Spring,Spring MVC及Spring Boot区别

    对于一个Java开发者来说,Spring可谓如雷贯耳,无论是Spring框架,还是Spring引领的IOC,AOP风格,都对后续Java开发产生的深远的影响,同时,Spring社区总能及时响应开发者的 ...

  6. python爬虫之requests+selenium+BeautifulSoup

    前言: 环境配置:windows64.python3.4 requests库基本操作: 1.安装:pip install requests 2.功能:使用 requests 发送网络请求,可以实现跟浏 ...

  7. SEO:Yahoo 14条优化建议

    腾讯前端设计的Leader推荐我背熟的.请大家都能好好学习,不要像我一样一扫而过,好好的记下来!不仅仅是晓得一些CSS xhtml就好了,深刻认识到很多的东西需要学习的.很早就用Firebug,但是却 ...

  8. SAMP论文学习

    SAMP:稀疏度自适应匹配追踪 实际应用中信号通常是可压缩的而不一定为稀疏的,而且稀疏信号的稀疏度我们通常也会不了解的.论文中提到过高或者过低估计了信号的稀疏度,都会对信号的重构造成影响.如果过低估计 ...

  9. poj 2632 Crashing Robots 模拟

    题目链接: http://poj.org/problem?id=2632 题目描述: 有一个B*A的厂库,分布了n个机器人,机器人编号1~n.我们知道刚开始时全部机器人的位置和朝向,我们可以按顺序操控 ...

  10. [转]MySQL游标的使用

    转自:http://www.cnblogs.com/sk-net/archive/2011/09/07/2170224.html 以下的文章主要介绍的是MySQL游标的使用笔记,其可以用在存储过程的S ...