API编程系列之一:窥豹——API调用优美代码赏析
时间:2011-01-19 来源:南子
像所有的明星上台,都必须有显赫的背景介绍,我们这一系列的编写也必须要有深刻而迫切的原因的:一、市场上对设备编程的迫切需要,一般的设备系统通常都是用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原型必须细致入微,因为一个参数值的差别就是地狱与天堂的差别。下一章,就专门讲述这些特点,以及他们产生的原因和处理的办法。