C#调用API

API(application programming interface)应用编程接口,这个是Windows编程人员用来操纵Windows系统的工具,其中包含了大量的方法供编程人员来使用。.NET平台同样提供了Framework类库,其实这个类库就是将大量的API函数进行了重写和封装,但是有时光使用类库的方法完成不了或很麻烦才可以完成我们的项目,这时就可以考虑使用Windows API函数来操作。

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

Win16 和 Wint32 API:

Win16 是为十六位处理器开发的,早期的操作系统都支持

Wint32 是为三十二位处理器开发的,它的移植性比较强,被大部分处理器所支持。当前我们主要使用的都是这个。

Windows 32 API主要有三个类库:(它们的详细内容可以自己去查阅资料)

Kernel32.dll

User32.dll

GDI32.dll

首先,有一个问题:Win32 API函数放在哪里?
  Win32 API函数是Windows的核心,比如我们看到的窗体、按钮、对话框什么的,都是依靠Win32函数“画”在屏幕上的,由于这些控件(有时也称组件)都用于用户与Windows进行交互,所以控制这些控件的Win32 API函数称为“用户界面”函数(User Interface Win32 API),简称UI函数;还有一些函数,并不用于交互,比如管理当前系统正在运行的进程、硬件系统状态的监视等等……这些函数只有一套,但是可以被所有的Windows程序调用(只要这个程序的权限足够高),简而言之,API是为程序所共享的。为了达到所有程序能共享一套API的目的,Windows采用了“动态链接库”的办法。之所以叫“动态链接库”,是因为这样的函数库的调用方式是“随用随取”而不是像静态链接库那样“用不用都要带上”。
  这里不太好理解,不要紧,我们举个小例子。我们把Windows比做一个游乐场,而把在游乐场里玩儿的小孩比做一个一个程序。小孩在玩的过程中可能要喝水。我们有两个办法让小家伙们想喝水的时候就有水喝:1.给每个小家伙配一个水壶,小家伙们喝了的话就喝自己带的水;2.给游乐场配一个饮水机,谁渴了谁来喝。显然,第二个方法要好得多,这体现在三个地方。第一,带着水壶,小家伙身体不灵活、玩不爽(影响程序的速度),况且这只是带了一个水壶,要是再带上饭盒呢?还有轮滑、头盔、创可贴、纱布……AK-47 My God,如果带全了就赶上美国大兵了。所以游乐园里还是有个公用“仓库”要来的方便,让大家随用随取(动态链接)。第二,小家伙们带了那么多东西,占了游乐场很多地方,让游乐场拥挤不堪,别的小朋友就进不来了(程序体积大,影响程序和系统的性能)。第三,如果某件物品升级了,比如水壶从一升的改为二升的,那么每个小家伙就必须go home去换新的(重新编译程序,由编译器把新的静态库链接进程序主体里),而第二种情况里,只要游乐场把自己仓库里的水壶换个型号,那么所有小家伙就都在同一时间拥有了大容量的水壶。Win32 API函数是放在Windows系统的核心库文件中的,这些库在硬盘里的存储形式是.dll文件。我们常用到的dll文件是user32.dll和kernel32.dll两个文件,还有其它一些dll文件也非常重要,大家要在实践中多积累经验。
  我们知道Win32 API函数是放在dll文件中了,但新问题又来了——我们怎么调用它们呢?这些dll文件是用C语言写的,源代码经C语言编译器编译之后,会以二进制可执行代码形式存放在这些dll文件中,就好像苹果被打碎机打成果酱后装在罐子里一样——你再也分不清哪个是你GF给你的,哪个是你老妈给你的一样。为了能让程序使用这些函数,微软在发布每个新的操作系统的时候,也会放出这个系统的SDK,目前最新的是Win2003 SP1 SDK,据说Vista的马上就要放出来,而且已经把UI的API从核心库中分离出去以提高系统的稳定性了。SDK里有一些C语言的头文件(.h文件),这些文件里描述了核心dll文件里都有哪些Win32 API函数,在写程序的时候,把这些.h文件用#include"....."指令包含进你的程序里,你就可以使用这些Win32 API了。至于程序是怎样链接的,超出了本文的范围——也超出了本人的知识范围:D
  至此,如果你是C语言高手,已经可以使用Windows SDK去调教Windows了!不过,今天我们讨论的是C#语言调用Win32 API的问题。我们现在已经知道API函数放在dll动态链接库文件里,也知道C语言怎么调用它们了,那么C#语言怎么办呢?C#语言是不能使用C语言的.h文件的。C#语言也使用dll动态链接库,不过这些dll都是.NET版本的,具有“自描述性”,也就是自己肚子里都有哪些函数都已经写在自己的metadata里了,不用再附加一个.h文件来说明。现在,我们已经找到了问题的关键点:如何用.NET平台上的C#语言来调用Win32平台上的dll文件。答案非常简单:使用DllImport特性。

首先,我举个例子看看:

using System;

using System.Collections.Generic;

using System.ComponentModel;

using System.Data;

using System.Drawing;

using System.Linq;

using System.Text;

using System.Windows.Forms;

using System.Runtime.InteropServices;

namespace ConsoleApp1

{

public partial class Form1 : Form

{

[DllImport("User32.dll")]

private static extern int MessageBox(int h, string m, string c, int type);

public Form1()

{

InitializeComponent();

}

private void button1_Click(object sender, EventArgs e)

{

MessageBox(0, "API Message Box", "API Demo", 0);

}

}

}

上面的是一个完整的WinForm窗体应用程序,点击按钮会产生一个弹窗。

在新建一个WinForm窗体应用程序后,首先,你要添加一个命名空间

System.Runtime.InteropServices,在这个命名空间中包含了DllImport特性类,之后我们需要声明要用到的API函数,如下:

[DllImport("User32.dll")]

private static extern int MessageBox(int h, string m, string c, int type);

DllImport指定了要使用的类库,即要使用的DLL,User32.dll为类库名,"static"修饰符声明一个静态元素,而该元素属于类型本身而不是指定的对象;"extern"表示该方法将在工程外部执行,同时使用DllImport导入的方法必须使用"extern"修饰符;MessageBox是API函数的名称。其中DLL是必要的参数,同时在声明中还有很多可选的参数。如下图:

在上图中,主要的字段:

(1)CallingConvention:它指示入口点的调用协议,它的值有下面几个

Cdecl = 2,调用方清理堆栈。这使您能够调用具有 varargs 的函数(如 Printf),使之可用于接受可变数目的参数的方法。

StdCall = 3,被调用方清理堆栈。这是使用平台 invoke 调用非托管函数的默认约定。

ThisCall = 4,第一个参数是 this 指针,它存储在寄存器 ECX 中。其他参数被推送到堆栈上。此调用约定用于对从非托管 DLL 导出的类调用方法。

FastCall = 5,不支持此调用约定。

(2)CharSet:规定封送字符床用使用何种字符集,并控制名称的重整(后面这句不明白),它的值也有几种:

Ansi = 2,以多字节字符串的形式封送字符串。

Unicode = 3,以 Unicode 2 字节字符形式封送字符串。

Auto = 4,针对目标操作系统适当地自动封送字符串。 默认情况下,C# 将所有方法和类型都标记为System.Runtime.InteropServices.CharSet.Ansi。

(3)EntryPoint:指示要调用的 DLL 入口点的名称或序号,简单点说就是如果你想用自己定义的函数名而不用API定义好的,这时它就派上用场了。举个例子:

[DllImport("User32.dll",EntryPoint = “MessageBox”)]

private static extern int MsgBox(int h, string m, string c, int type);

这个调用的函数其实就是 MessageBox,但是你不想用时,就用EntryPoint定义这个函数,声明方法时可以用自己的函数名,如这里的MsgBox.

(4)ExactSpelling:控制System.Runtime.InteropServices.DllImportAttribute.CharSet是否使用公共语言运行时非托管DLL 中搜索入口点名称,而不使用指定的入口点名称,这个准确的说我不太明白是什么意思,但是我这里有一个例子可以给大家看看:

using System;

using System.Collections.Generic;

using System.ComponentModel;

using System.Data;

using System.Drawing;

using System.Linq;

using System.Text;

using System.Windows.Forms;

using System.Runtime.InteropServices;

namespace ConsoleApp1

{

public partial class Form1 : Form

{

[DllImport("User32.dll", EntryPoint = "MessageBox",ExactSpelling = false)]

private static extern int MsgBox(int h, string m, string c, int type);

public Form1()

{

InitializeComponent();

}

private void button1_Click(object sender, EventArgs e)

{

MsgeBox(0, "API Message Box", "API Demo", 0);

}

}

}

上面的这个程序是可以运行的.但是当你将ExactSpelling 设置为true时,就会报错。

所以,就是说ExactSpelling与EntryPoint 是相关的。

(5)SetLastError:指示被调用方在从特性方法返回之前是否调用SetLastError Win32 API函数。

涉及到函数调用,自然免不了要向系统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 位

再思考一个问题,如果你要传递或返回的参数是一个复杂的参数,如封装类、结构和联合。

类和结构在 .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("Kernal.dll",EntryPoint = “SetSystemTime”)]

private 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 字节字符形式封送字符串。

时间: 2024-12-14 18:09:38

C#调用API的相关文章

OAuth2.0学习(5-4)新浪开放平台-微博API-使用OAuth2.0调用API

使用OAuth2.0调用API 使用OAuth2.0调用API接口有两种方式: 1. 直接使用参数,传递参数名为 access_token URL 1 https://api.weibo.com/2/statuses/public_timeline.json?access_token=abcd 2.在header里传递,形式为在header里添加 Authorization:OAuth2空格abcd,这里的abcd假定为Access Token的值,其它接口参数正常传递即可. 注:所有的微博开放

调用API接口下载腾讯CDN访问日志

公司使用腾讯cdn为网站静态内容加速,由于业务需求,需要每天下载昨天的日志(因为腾讯方面给出回复,访问日志会有2个小时或以上时间的延迟,所以不建议下载当天日志,所以每天统计前一天的日志以做分析).因为cdn是由运维来管理,但是这个需求是业务的,如果每天都由运维进行下载,再通过邮件或其他工具发送,可能就显得麻烦.所幸腾讯CDN提供了API接口,因此采用shell脚本调用API进行下载的方式,定期下载日志,这样只要业务人员运行这个脚本就能自行下载日志,解放了运维的工作. #!/bin/bash ##

openstack 调用API 实现云主机的IO 控制,CGroup 策略

# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright (c) 2011 X.commerce, a business unit of eBay Inc. # Copyright 2010 United States Government as represented by the # Administrator of the National Aeronautics and Space Administration. # All Righ

32位汇编第四讲,干货分享,汇编注入的实现,以及快速定位调用API的数量(OD查看)

32位汇编第四讲,干货分享,汇编注入的实现,以及快速定位调用API的数量(OD查看) 昨天,大家可能都看了代码了,不知道昨天有没有在汇编代码的基础上,实现注入计算器. 如果没有,今天则会讲解,不过建议把昨天代码熟悉一遍(课程是紧跟着来的,请不要拉下任何一天,因为今天的知识, 可能就和昨天的知识挂钩,昨天的知识,和前天的挂钩.....,当然你如你懂汇编,不是新手,那么则可以直接往下看) 一丶远程线程注入,和汇编远程注入的区别 昨天的代码,大家可能看了(没看也没有关系,就是远程线程注入的代码,开发角

C# 调用API接口处理公共类 自带JSON实体互转类

using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Net; using System.Net.Security; using System.Security.Cryptography.X509Certificates; using System.Text; using System.Threading.Tasks; using System.Web; n

ABP手机端调用API时的CORS

这个问题其实很早就考虑了,当时因为也没有特别着急去解决这个问题,就一直拖着.... 好吧,拖延症是不好的,所有不懒得做的,终将会逼着你去再很短的时间内去解决问题...实现项目 改写一个已有的webform项目,需要手机端和WEB端的数据保持互通,所以第一反应当然是abp了,既可以学习,也可以完成任务 在试着调用的api的时候就遇到了cors跨域请求的问题,之前很迷茫练cors是什么都不知道,这次也知道了.解决问题,带着问题去看文档 http://aspnetboilerplate.com/Pag

汇编中如果汇编和调用API混合用的话要注意Pushad popad

某群有个人提出一个奇怪的问题,这段代码的循环不正常 ;MASMPlus 代码模板 - 控制台程序 .386.model flat, stdcalloption casemap :none include windows.incinclude user32.incinclude kernel32.incinclude masm32.incinclude gdi32.inc includelib gdi32.libincludelib user32.libincludelib kernel32.li

C#调用API向外部程序发送数据

C#调用API向外部程序发送数据 最近有可能要做一个项目.在项目中有这么一个功能,在A程序中调用B程序,同时在A程序中进行登陆后,要将A程序的登录名和密码自动填充到B程序的登陆对话框中,这样B程序就不需要再输入一次用户名和密码了,简化操作人员的操作.刚好最近闲着没事,就在怎么想怎么去实现.经过两天的折腾,基本上完成了上述功能的实现.下面就把实现方法.过程与大家进行分享. 一.原理 要实现上述功能,需要调用Win API来实现.Win32 API即为Microsoft 32位平台的应用程序编程接口

Python调用API接口的几种方式

相信做过自动化运维的同学都用过API接口来完成某些动作.API是一套成熟系统所必需的接口,可以被其他系统或脚本来调用,这也是自动化运维的必修课. 本文主要介绍python中调用API的几种方式,下面是python中会用到的库. - urllib2 - httplib2 - pycurl - requests urllib2 import urllib2, urllib github_url = 'https://api.github.com/user/repos' password_manage

C#区域截图——调用API截图

前言:截图对于一个C++开发者来说无非是小菜一碟,也有朋友使用C#的 Graphics.CopyFromScreen 方法屏幕操作,作为一名整天想着用 C++ 开发游戏的初级 C#.NET 的程序员的我,只是自己研究区域截图,失败 n 多次,最后在半梦半醒中弄出来的简单的Demo,简单的分享一下而已.如此的班门弄斧,着实不堪,另外我的 C++ 水平也是处于入门水准,如果该博客有什么问题,请各位朋友留言指正,谢谢关照! 附件及其他: C#的 Graphics.CopyFromScreen 方法