您的位置:首页 > 编程语言

API编程系列之一:窥豹——API调用优美代码赏析

2011-01-19 04:13 513 查看
窥豹——API调用优美代码赏析

像所有的明星上台,都必须有显赫的背景介绍,我们这一系列的编写也必须要有深刻而迫切的原因的:一、市场上对设备编程的迫切需要,一般的设备系统通常都是用C或C++编写的;二、网络上中文资料的残缺(要么不实用、要么不完整、而且整个GOOGLE实际就两、三篇文章,打开一看一字不差、百度则更差,经常气的半死);三、便于以后复习,好不容易凑全了,万一忘了怎么办?四、如钱钟书所说:愛情這功課,就像麻疹,一生中,總得出上那麼一、兩回,據說從沒出過麻疹的人,免疫力特差;不管麻疹的程度怎么样,好歹也出过,面试经理也经常这样想,所以出麻疹的人被逼的越来越多。

会不会有担心:连WINDOWS函数都看不懂,学也是白学!这个是不用担心的,因为API是很成熟的技术,.NET也是比较成熟的技术,.NET对API兼容调用依然是比较成熟的技术。所以只要掌握了C#对API编程的关键要点,一样能把API玩的顺风顺水。当然如果你能把C++WINDOWS编程原理摸透,那自然是更好。你就不必看微软的脸色去做那怪模怪样的八股文章了,你甚至可以自己写好要用的API,肯定不会比徐娘半老又勉强整容的COM+们差吧?再则知识是有层次和深度的,这是自然规律,就算你精通的WINDOWS编程,操作系统原理又在等着你,精通了操作系统原理编译器原理又在等着你、等你精通编译器原理芯片设计原理又在等着你,等你精通芯片设计原理激光切片芯片设计技术INTEL公司早已发挥到极致了,这时候你对你写的程序的性能和效率又产生了莫大怀疑?等一路追寻下去就算你有幸全都了然,你的黄花菜已早已不只是凉了。那结果只得像天龙八部中萧远山与慕容博两位大侠一样!武功通神,正好出家!所以我们应该学会享受过程的精彩,该出手时就出手!

下面代码对API的调用具备极为精湛的掌握,也包含了绝大部分关键技术要点,尤其是对DocumentProperties函数阳关三叠的传神调用,极为形象深刻地说明了API编程的技术特点。让我们用仰慕和向往的心情来阅读它吧,再次强调这种代码是不多见的。

#region 为制定打印机加载自定义纸型_____________________________________________

/// <summary>

/// </summary>

/// <param name="printerName">The printer name</param>

/// <param name="paperName">Name of the printer form</param>

/// <param name="widthMm">Width given in millimeters</param>

/// <param name="heightMm">Height given in millimeters</param>

public static void AddCustomPaperSize(string printerName, string paperName, float

widthMm, float heightMm)

{

if (PlatformID.Win32NT == Environment.OSVersion.Platform)

{

// 为什么要进行操作系统类型的判断是因为这个函数在

//因为操作函数在WINDOWS早期版本时不支持的,else后会给出早期版本的操作方式

//打印机访问方式,后面打开打印机操作句柄会用到

const int PRINTER_ACCESS_USE = 0x00000008;

const int PRINTER_ACCESS_ADMINISTER = 0x00000004;//打印机访问方式

const int FORM_PRINTER = 0x00000002;

//定义打印机这一结构体,不然程序不知道要分配多少内存给执行函数

PrinterSetting.PRINTER_DEFAULTSdefaults = new PrinterSetting.PRINTER_DEFAULTS();

defaults.pDatatype = 0;

defaults.pDevMode = 0;

//打印机访问方式,前面定义过的

defaults.DesiredAccess = PRINTER_ACCESS_ADMINISTER | PRINTER_ACCESS_USE;

IntPtr hPrinter = IntPtr.Zero;

// 打开打印机,这就是为什么前一行语句为什么要声明一个托管句柄

// 参数有打印机名前面传入的,刚声明的句柄用于承载函数输出的打印机变量体

// 最后一个参数就是所定义的打印机结构类型,让函数知道输出个什么结构的东东

// API就真的是烦躁,直接搞个打印机类型的输出型变量不就OK了吗?还非得要搞个

// 句柄变量来输出,后面还的告诉它是个什么类型的变量,有时间真要好好看看微软以

// 前怎么那么笨,要有C++高手就好鸟,这也就是我们C#优雅矜持之处了,时代在进步啊!

if (PrinterSetting.OpenPrinter(printerName, out hPrinter, ref defaults))

{

try

{

//直接就删了,要是本来没得出错咋办,该try就果断的try住了,果然高手哇!

//不像菜鸟,满篇都TRY的,也不像我们这样的菜鸟,该TRY得地方也不敢TRY

DeleteForm(hPrinter, paperName);

FormInfo1 formInfo = new FormInfo1();

//这个地方要注意了哦,这个Flags是大有文章的啊,很多老外都哭喊着说我定

//制了FORM可就是在打印机属性设置里头找不着它,可我没机会告诉他们,真

//的,CSDN里面也有个2007年的帖子在喊,可后面都没得正解。看来高手也不

//是我们想像的那样多,嘿嘿!因此,API编程贵在入微,入微则滋味不一样

formInfo.Flags =0;

formInfo.pName = paperName;

// all sizes in 1000ths of millimeters

formInfo.Size.width = (int)(widthMm * 1000.0);

formInfo.Size.height = (int)(heightMm * 1000.0);

formInfo.ImageableArea.left = 0;

formInfo.ImageableArea.right = formInfo.Size.width;

formInfo.ImageableArea.top = 0;

formInfo.ImageableArea.bottom = formInfo.Size.height;

//这里的第一个参数就是前面打开的打印机句柄,很多API刚接触的程序员,按

//习惯给个空值,以为"无用则空",连API常识都不了解,不说也罢

//添加定制的纸型给指定的打印机,按道理说做到这步也就够了。不过这都是猜

//鸟的思维,不过网上很多就做到这里的。

if (!AddForm(hPrinter, 1, ref formInfo))

{

//高手的素养就体现在这里,在这拒绝使用string而用StringBuilder

//套用某句名言:懂string与StringBuilder的不像我们想象中的那么多,也

//也不像我们想象中的那么少

StringBuilder strBuilder = new StringBuilder();

strBuilder.AppendFormat("Failed to add the custom paper size {0} to the printer {1}, System error number: {2}",

paperName, printerName, GetLastError());

throw new ApplicationException(strBuilder.ToString());

}

const int DM_OUT_BUFFER = 2;

const int DM_IN_BUFFER = 8;

structDevMode devMode = new structDevMode();

//好!真正精彩的地方开始了,这里声明了一大堆变量,有必要这么多吗?

IntPtr hPrinterInfo, hDummy;

PRINTER_INFO_9 printerInfo;

printerInfo.pDevMode = IntPtr.Zero;

int iPrinterInfoSize, iDummyInt;

//半夜听着美酒加咖啡的确让人心醉,不过好困了!美好的时光总过的特别快

//阳光三叠的第一叠,取打印机属性对象pDevMode 的内存大小,注意啊,她

//跟后面两次调用的区别,唉,算了,还是直接说吧,玄机就是参数的赋值

//仅仅一个参数的值,整个函数的作用就翻天覆地的变化,API真不可思议啊!

int iDevModeSize = DocumentProperties(IntPtr.Zero, hPrinter, printerName, IntPtr.Zero, IntPtr.Zero, 0);

if (iDevModeSize < 0)

throw new ApplicationException("Cannot get the size of the DEVMODE structure.");

//这个Marshal类很风骚的哦,用词香艳了一点,估计看到这的人也不多,

//如果要真正掌握API调用,这个必须掌握不可她的内涵绝不只是一般的丰富

//不过依葫芦画瓢也就可以了,呵呵!这里是为非托管对象分派内存

//API就这样麻烦,虽然功能强大,但又很脆弱,什么都要为她照顾好

IntPtr hDevMode = Marshal.AllocCoTaskMem(iDevModeSize + 100);

//注意了注意了,仔细才能看出区别,阳光二叠!DM_OUT_BUFFER

int iRet = DocumentProperties(IntPtr.Zero, hPrinter, printerName, hDevMode, IntPtr.Zero, DM_OUT_BUFFER);

if (iRet < 0)

throw new ApplicationException("Cannot get the DEVMODE structure.");

//这里是把非托管对象的数据封装到托管对象中来!API 编程必备绝技

devMode=(structDevMode)Marshal.PtrToStructure(hDevMode, devMode.GetType());

devMode.dmFields = 0x10000; // DM_FORMNAME

devMode.dmFormName = paperName;

//取出打印机本身属性更改过后,再塞进去,保证不会出问题

Marshal.StructureToPtr(devMode, hDevMode, true);

//阳关三叠!设置打印机指定属性

iRet = DocumentProperties(IntPtr.Zero, hPrinter, printerName,

printerInfo.pDevMode, printerInfo.pDevMode, DM_IN_BUFFER | DM_OUT_BUFFER);

if (iRet < 0)

//如果出错则提示不能设置指定属性

throw new ApplicationException("Unable to set the orientation setting for this printer.");

// GET THE PRINTER INFO SIZE

GetPrinter(hPrinter, 9, IntPtr.Zero, 0, out iPrinterInfoSize);

if (iPrinterInfoSize == 0)

throw new ApplicationException("GetPrinter failed. Couldn't get the # bytes needed for shared PRINTER_INFO_9 structure");

hPrinterInfo = Marshal.AllocCoTaskMem(iPrinterInfoSize + 100);

bool bSuccess = GetPrinter(hPrinter, 9, hPrinterInfo, iPrinterInfoSize, out iDummyInt);

if (!bSuccess)

throw new ApplicationException("GetPrinter failed. Couldn't get the shared PRINTER_INFO_9 structure");

printerInfo = (PRINTER_INFO_9)Marshal.PtrToStructure(hPrinterInfo, printerInfo.GetType());

printerInfo.pDevMode = hDevMode;

Marshal.StructureToPtr(printerInfo, hPrinterInfo, true);

//在这里才是真正的设置打印机纸型信息,好麻烦啊!

//windows操作系统就是这样,一个打印机属性设置有默认选项,首选选项,

//更邪恶的是,它还有高级设置,搞个自定义纸型设置不容易啊!

//这就是API 高手编程的手法!精湛,优雅,正点!不像菜鸟那样残缺也不像

//老菜鸟那样进退失据!

bSuccess = SetPrinter(hPrinter, 9, hPrinterInfo, 0);

if (!bSuccess)

throw new Win32Exception(Marshal.GetLastWin32Error(), "SetPrinter() failed. Couldn't set the printer settings");

//Tell all open programs that this change occurred.

SendMessageTimeout(

new IntPtr(HWND_BROADCAST),

WM_SETTINGCHANGE,

IntPtr.Zero,

IntPtr.Zero,

CustomPrintForm.SendMessageTimeoutFlags.SMTO_NORMAL,

1000,

out hDummy);

}

finally

{

ClosePrinter(hPrinter);

}

}

else

{

StringBuilder strBuilder = new StringBuilder();

strBuilder.AppendFormat("Failed to open the {0} printer, System error number: {1}",

printerName, GetLastError());

throw new ApplicationException(strBuilder.ToString());

}

}

else

{

//这里是WIN95,WIN98的操作,完整,优雅!

structDevMode pDevMode = new structDevMode();

IntPtr hDC = CreateDC(null, printerName, null, ref pDevMode);

if (hDC != IntPtr.Zero)

{

const long DM_PAPERSIZE = 0x00000002L;

const long DM_PAPERLENGTH = 0x00000004L;

const long DM_PAPERWIDTH = 0x00000008L;

pDevMode.dmFields = (int)(DM_PAPERSIZE | DM_PAPERWIDTH | DM_PAPERLENGTH);

pDevMode.dmPaperSize = 256;

pDevMode.dmPaperWidth = (short)(widthMm * 1000.0);

pDevMode.dmPaperLength = (short)(heightMm * 1000.0);

ResetDC(hDC, ref pDevMode);

DeleteDC(hDC);

}

}

}

好!能坚持看到这里的朋友很不容易了!因为高手是不会看的,白菜也看不到这里,能看到这里都对API比较投缘了。纵上所述,可见API编程的确有许多特殊的地方:一、函数的机构比C#函数奇怪很多 二、数据类型与C#的差别很大,尤其是结构类型和指针类型,搞死人 三、数据在托管与非托管代码间的封装有特色,必须熟练 四:对所调用的API原型必须细致入微,因为一个参数值的差别就是地狱与天堂的差别。下一章,就专门讲述这些特点,以及他们产生的原因和处理的办法。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: