周公的专栏

执子之手,与子偕老

周金桥ID:zhoufoxcn
170395次访问,排名434好友162人,关注者195
6年的Java和.net开发经验。熟悉数据库及软件性能优化。
zhoufoxcn的文章
原创 142 篇
翻译 5 篇
转载 70 篇
评论 390 篇
周公的公告
说明:本博客示例代码自2007年以后所写的代码均是在Vs2005(.net2.0)下编写,在Vs2003(.net1.1)下可能编译不会通过。
做人要厚道,转载请注明来源于本博客!
最近评论
lavendersh:hehe
lavendersh:hao
aiyaoo:xuexi
Hatter Jiang:今天找到知音了,我也是十多年前开始接触计算机的,虽然当时只能用DOS也没有互联网,从Basic开始慢慢开始被计算机的神秘吸引,从高中开始自觉C,C++,Java,PHP,C#,HTML等等,不知道花了多少课堂,休息和晚上睡觉的时间来学习这些东西,但现在也算有些成就了,
我非常喜欢JS,当然也喜欢Java和C#,有兴趣可以交流一下,TALK:jht5945@gmail.com
Hatter Jiang:今天找到知音了,我也是十多年前开始接触计算机的,虽然当时只能用DOS也没有互联网,从Basic开始慢慢开始被计算机的神秘吸引,从高中开始自觉C,C++,Java,PHP,C#,HTML等等,不知道花了多少课堂,休息和晚上睡觉的时间来学习这些东西,但现在也算有些成就了,
我非常喜欢JS,当然也喜欢Java和C#,有兴趣可以交流一下,TALK:jht5945@gmail.com
文章分类
收藏
    相册
    .net
    mengyao||Andy 路鑫(RSS)
    The Code Project
    剑了(RSS)
    山西.net俱乐部
    张子阳(RSS)
    Java
    张森炜的博客(RSS)
    娱乐资源
    天下网
    天下网生活论坛(RSS)
    存档
    订阅我的博客
    XML聚合  FeedSky
    订阅到鲜果
    订阅到Google
    订阅到抓虾
    订阅到BlogLines
    订阅到Yahoo
    订阅到GouGou
    订阅到飞鸽
    订阅到Rojo
    订阅到newsgator
    订阅到netvibes

    原创 C#调用Windows API详解(上)收藏

    新一篇: 用C#绘制实时曲线图 | 旧一篇: 评选MVP当选有感

    以前我写过通过WMI来获取有关系统信息的系列文章,确实通过WMI能够恨轻易地实现很多我们想实现的功能,不过有些情况下我们很难利用WMI来实现一些复杂的功能,比如最近我做的一个项目,其中有一个功能就是要更改系统当前时间,利用WMI就很难实现(我没有找到相关的方法),还有一些其它方面的功能,也比较难以通过WMI来实现,也许是WMI需要较高的权限才能执行的原因吧。所以,尽管我们不愿意,但是又不得不通过调用Windows 的API来实现。本文的目的就是讲述如何在C#中调用Windows的系统API。
    本文将按照下面的步骤分别讲解:
    API简介
    C#中的简单数据类型与API中的数据类型对应关系
    如何在调用API时传递复杂参数:封装类、结构和联合
    如何调用API
    如何确保成功调用API

    API简介
    Windows API(Application Programming Interface,应用编程接口)是微软为了方便广大Windows开发人员调用系统底层功能而公开的一系列函数接口。.net中的函数很多就是对系统底层API的一些封装,但是在.net中并没有包含Windows所有的API函数。所幸的是,在.net中允许我们调用系统的API函数,并且还可以根据需要向系统API传递输入或者输出参数。

    当调用非托管API函数时,它将依次执行以下操作:
    1.查找包含该函数的 DLL。
    2.将该 DLL 加载到内存中。
    3.查找函数在内存中的地址并将其参数推到堆栈上,以封送所需的数据(注意:只在第一次调用函数时,才会查找和加载 DLL 并查找函数在内存中的地址。)。
    4.将控制权转移给非托管函数。
    5.对非托管 DLL 函数的“平台调用”调用
    平台调用会向托管调用方引发由非托管函数生成的异常。

    DLL 函数的标识包括以下元素:
    函数的名称或序号
    实现所在的 DLL 文件的名称

    例如,如果指定 User32.dll 中的 MessageBox 函数,需要标识该函数 (MessageBox) 及其位置(User32.dll、User32 或 user32)。Microsoft Windows 应用程序编程接口 (Win32 API) 可以包含每个字符和字符串处理函数的两个版本:单字节字符 ANSI 版本和双字节字符 Unicode 版本。如果不进行指定,CharSet 字段所表示的字符集将默认为 ANSI。某些函数可以有两个以上的版本。

    MessageBoxA 是 MessageBox 函数的 ANSI 入口点;而 MessageBoxW 是 Unicode 版本。可以通过运行各种命令行工具,为特定 DLL(例如 user32.dll)列出函数名。例如,可以使用 dumpbin /exports user32.dll 或 link /dump /exports user32.dll 来获取函数名。

    您可以在代码中将非托管函数重命名为任何所需的名称,但是要将该新名称映射到 DLL 中的初始入口点。有关在托管源代码中重命名非托管 DLL 函数的说明,请参见指定入口点。

    利用平台调用,可以通过调用 Win32 API 和其他 DLL 中的函数来控制操作系统中相当大的一部分。除了 Win32 API 之外,还有许多其他的 API 和 DLL 可通过平台调用来调用。

    下面将说明 Win32 API 中几个常用的 DLL。
    GDI32.dll:用于设备输出的图形设备接口 (GDI) 函数,例如用于绘图和字体管理的函数。
    Kernel32.dll:用于内存管理和资源处理的低级别操作系统函数。
    User32.dll:用于消息处理、计时器、菜单和通信的 Windows 管理函数。

    涉及到函数调用,自然免不了要向系统API提供参数或者获取调用系统API之后的返回值,由于Windows采用了C/C++开发的,而我们调用的程序语言是C#,二者的数据类型自然会存在一些不一致的情况,下面的表列出了二者之间的一个对应关系。


    下表列出了在 Win32 API(在 Wtypes.h 中列出)和 C 样式函数中使用的数据类型。许多非托管库包含将这些数据类型作为参数传递并返回值的函数。第三列列出了在托管代码中使用的相应的 .NET Framework 内置值类型或类。某些情况下,您可以用大小相同的类型替换此表中列出的类型。

    Wtypes.h 中的非托管类型 非托管 C 语言类型 托管类名 说明

    HANDLE

    void*

    System.IntPtr

    在 32 位 Windows 操作系统上为 32 位,在 64 位 Windows 操作系统上为 64 位。

    BYTE

    unsigned char

    System.Byte

    8 位

    SHORT

    short

    System.Int16

    16 位

    WORD

    unsigned short

    System.UInt16

    16 位

    INT

    int

    System.Int32

    32 位

    UINT

    unsigned int

    System.UInt32

    32 位

    LONG

    long

    System.Int32

    32 位

    BOOL

    long

    System.Int32

    32 位

    DWORD

    unsigned long

    System.UInt32

    32 位

    ULONG

    unsigned long

    System.UInt32

    32 位

    CHAR

    char

    System.Char

    用 ANSI 修饰。

    LPSTR

    char*

    System.String 或 System.Text.StringBuilder

    用 ANSI 修饰。

    LPCSTR

    Const char*

    System.String 或 System.Text.StringBuilder

    用 ANSI 修饰。

    LPWSTR

    wchar_t*

    System.String 或 System.Text.StringBuilder

    用 Unicode 修饰。

    LPCWSTR

    Const wchar_t*

    System.String 或 System.Text.StringBuilder

    用 Unicode 修饰。

    FLOAT

    Float

    System.Single

    32 位

    DOUBLE

    Double

    System.Double

    64 位

    如何在调用API时传递复杂参数:封装类、结构和联合
    类和结构在 .NET Framework 中是类似的。它们都可以具有字段、属性和事件。它们也有静态和非静态方法。一个显著区别是结构属于值类型而类属于引用类型。
    结构:
    比如一个常用函数,用于获取日期时间的,原始声明如下:
    VOID GetSystemTime(LPSYSTEMTIME lpSystemTime);

    这个方法位于Kernel32.dll类库中,这个方法需要一个SYSTEMTIME的结构,其原始声明如下:
    typedef struct _SYSTEMTIME {
    WORD wYear;
    WORD wMonth;
    WORD wDayOfWeek;
    WORD wDay;
    WORD wHour;
    WORD wMinute;
    WORD wSecond;
    WORD wMilliseconds;
    } SYSTEMTIME, *PSYSTEMTIME;

    根据C#中简单数据类型与C/C++中数据类型的对应关系,我们可以完成如下代码:

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

    }

    对上面的API方法的调用声明如下:
    [DllImport("kernel32.dll", EntryPoint = "SetSystemTime")]
    public static extern void GetSystemTime(
    SystemTime systemTime
    );

    在默认情况下,上面的方法将SystemTime类In/Out 参数进行传递。必须用 InAttribute 和 OutAttribute 属性声明该参数,因为作为引用类型的类在默认情况下将作为输入参数进行传递。为使调用方接收结果,必须显式应用这些方向属性,如ref或者out。
    另外,我们还需要指定结构在内存中的布局,这个我们可以在声明结构时加以StructLayout属性来指明。而StructLayout属性需要一个layoutKind的枚举值。它有如下几个值:

    成员名称 说明
    Auto 运行库自动为非托管内存中的对象的成员选择适当的布局。使用此枚举成员定义的对象不能在托管代码的外部公开。尝试这样做将引发异常。
    Explicit 对象的各个成员在非托管内存中的精确位置被显式控制。每个成员必须使用 FieldOffsetAttribute 指示该字段在类型中的位置。
    Sequential 对象的成员按照它们在被导出到非托管内存时出现的顺序依次布局。这些成员根据在 StructLayoutAttribute.Pack 中指定的封装进行布局,并且可以是不连续的。

    在本例中,使用Sequential就行了。上面的C#结构描述修正如下:
    [StructLayout(LayoutKind.Sequential)]
    public struct SystemTime
    {
    public ushort wYear;
    public ushort wMonth;
    public ushort wDayOfWeek;
    public ushort wDay;
    public ushort wHour;
    public ushort wMinute;
    public ushort wSecond;
    public ushort wMilliseconds;

    }

    当然如果想声明成Explicit也是可以的,如下:


    [StructLayout(LayoutKind.Explicit, Size=16, CharSet=CharSet.Ansi)]
    public struct MySystemTime
    { [FieldOffset(0)]
    public ushort wYear;
    [FieldOffset(2)]
    public ushort wMonth;
    [FieldOffset(4)]
    public ushort wDayOfWeek;
    [FieldOffset(6)]
    public ushort wDay;
    [FieldOffset(8)]
    public ushort wHour;
    [FieldOffset(10)]
    public ushort wMinute;
    [FieldOffset(12)]
    public ushort wSecond;
    [FieldOffset(14)]public ushort wMilliseconds;
    }
    每个字段的FieldOffset依次递增为2字节,因为严格ushort占用的内存大小也正好是2字节。总共8个字段,因此总共16字节。在这里又多用了一个CharSet属性声明,它是用来规定封送字符串应使用何种字符集。它也是一个枚举类型,对可能值和对应描述如下:

    成员名称 说明
    Auto 针对目标操作系统适当地自动封送字符串。在 Windows NT、Windows 2000、Windows XP 和 Windows Server 2003 系列上默认值为 Unicode;在 Windows 98 和 Windows Me 上默认值为 Ansi。尽管公共语言运行库默认值为 Auto,使用语言可重写此默认值。例如,默认情况下,C# 将所有方法和类型都标记为 Ansi。
    Ansi 以多字节字符串的形式封送字符串。
    None 此值已过时,它与 CharSet.Ansi 具有相同的行为
    Unicode 以 Unicode 2 字节字符形式封送字符串。

    待续.....

    发表于 @ 2008年07月17日 08:12:00|评论(loading...)|收藏

    新一篇: 用C#绘制实时曲线图 | 旧一篇: 评选MVP当选有感

    评论:没有评论。

    发表评论  


    登录
    Csdn Blog version 3.1a
    Copyright © 周公