《windows程序设计(C语言版)》笔记

《1》的笔记

1

2016-01-06
在早期,Windows的主要部分仅通过三个动态链接库实作。这代表了Windows的三个主要子系统,它们被称作Kernel、User和GDI。当子系统的数目在Windows最近版本中增多时,大多数典型的Windows程序产生的函数呼叫仍对应到这三个模块之一。Kernel(日前由16位的KRNL386.EXE和32位的KERNEL32.DLL实现)处理所有在传统上由操作系统核心处理的事务-内存管理、文件I/O和多任务管理。User(由16位的USER.EXE和32位的USER32.DLL实作)指使用者接口,实作所有窗口运作机制。GDI(由16位的GDI.EXE和32位的GDI32.DLL实作)是一个图形设备接口,允许程序在屏幕和打印机上显示文字和图形。
2016-01-06
WINDOWS.H是主要的含入文件,它包含了其它Windows表头文件,这些表头文件的某些也包含了其它表头文件。这些表头文件中最重要的和最基本的是:
WINDEF.H 基本型态定义。 
WINNT.H 支持Unicode的型态定义。 
WINBASE.H Kernel函数。 
WINUSER.H 使用者接口函数。 
WINGDI.H 图形设备接口函数。
多看笔记 来自多看阅读 for Android
duokanbookid:o823gb2fbe4418b827dd37e2630ge830

《5》的笔记

5

打印虽然本章限于讨论视讯显示,但是您在本章中所学到的全部知识都适用于打印。第十三章会讨论打印。
2016-01-11
GDI原理
Windows 98和Microsoft Windows NT中的图形主要由GDI32.DLL动态链接库输出的函数来处理。在Windows 98中,这个GDI32.DLL实际是利用16位GDI.EXE动态链接库来执行许多函数。在Windows NT中,GDI.EXE只用于16位的程序。
2016-01-11
使用GetTextMetrics函数来取得有关设备内容中目前所选字体的尺寸信息
2016-01-11
使用TextOut函数在窗口的显示区域显示一些文字
2016-01-11
用SetTextColor来指定TextOut(或者其它文字输出函数)所绘制的文字色彩
2016-01-11
使用SetTextAlign来告诉GDI:TextOut函数中的字符串的开始位置应该在字符串的右边而不是内定的左边
2016-01-11
可以通过在CreatePen、 CreatePenIndirect或ExtCreatePen函数中指定这些特征来建立一个逻辑画笔,这些函数传回一个逻辑画笔的句柄(虽然这些函数被认为是GDI的一部分,但是和大多数GDI函数呼叫不一样,它们不要求设备内容的句柄)。要使用这个画笔,就要将画笔句柄选进设备内容。我们认为,设备内容中目前选中的画笔就是设备内容的一个属性。这样,您画任何线都使用这个画笔,然后,您可以取消设备内容中的画笔选择,并清除画笔对象。清除画笔对象是必要的,因为画笔定义占用了分配的内存空间。除了画笔以外,GDI对象还用于建立填入封闭区域的画刷、字体、位图以及GDI的其它一些方面。
2016-01-15
从Windows 3.1开始,GDI开始支持TrueType字体,该字体是在填入轮廓线基础上建立的,这样的填入轮廓线可由其它GDI函数处理
2016-01-15
Windows程序还可以在处理非WM_PAINT消息时取得设备内容句柄:
hdc = GetDC (hwnd) ;
其它行程序
ReleaseDC (hwnd, hdc) ;
2016-01-15
Windows程序还可以取得适用于整个窗口(而不仅限于窗口的显示区域)的设备内容句柄:
hdc = GetWindowDC (hwnd) ;
其它行程序
ReleaseDC (hwnd, hdc) ;
2016-01-15
取得设备内容句柄的另一个更通用的函数是CreateDC:
hdc = CreateDC (pszDriver, pszDevice, pszOutput, pData) ;
其它行程序
DeleteDC (hdc) ;
例如,您可以通过下面的呼叫来取得整个屏幕的设备内容句柄:hdc = CreateDC (TEXT ("DISPLAY"), NULL, NULL, NULL) ;
在窗口之外写入画面一般是不恰当的,但对于一些不同寻常的应用程序来说,这样做很方便(您还可通过在呼叫GetDC时使用一个NULL参数,从而取得整个屏幕的设备内容句柄
2016-01-15
有时您只是需要取得关于某设备内容的一些信息而并不进行任何绘画,在这种情况下,您可以使用CreateIC来取得一个「信息内容」的句柄,其参数与CreateDC函数相同,例如:
hdc = CreateIC (TEXT ("DISPLAY"), NULL, NULL, NULL) ;
您不能用这个信息内容句柄往设备上写东西。
2016-01-15
使用位图时,取得一个「内存设备内容」有时是有用的:
hdcMem = CreateCompatibleDC (hdc) ;
其它行程序
DeleteDC (hdcMem) ;
您可以将位图选进内存设备内容,然后使用GDI函数在位图上绘画。
2016-01-15
metafile是一些GDI呼叫的集合,以二进制形式编码。您可以通过取得metafile设备内容来建立metafile:
hdcMeta = CreateMetaFile (pszFilename) ;
其它行程序
hmf = CloseMetaFile (hdcMeta) ;
在metafile设备内容有效期间,任何用hdcMeta所做的GDI呼叫都变成metafile的一部分而不会显示。在呼叫CloseMetaFile之后,设备内容句柄变为无效,函数传回一个指向metafile(hmf)的句柄
2016-01-15
取得设备内容信息
一个设备内容通常是指一个实际显示设备,如视讯显示器和打印机。通常,您需要取得有关该设备的信息,包括显示器的大小(单位为图素或者实际长度单位)和色彩显示能力。您可以通过呼叫GetDeviceCaps(「取得设备功能」)函数来取得这些信息:
iValue = GetDeviceCaps (hdc, iIndex) ;
其中,参数iIndex取值为WINGDI.H表头文件中定义的29个标识符之一。例如,iIndex为HORZRES时将使GetDeviceCaps传回设备的宽度(单位为图素);iIndex为VERTRES时将让GetDeviceCaps传回设备的高度(单位为图素)。如果hdc是打印机设备内容的句柄,则GetDeviceCaps传回打印机显示区域的高度和宽度,它们也是以图素为单位的。还可以使用GetDeviceCaps来确定设备处理不同型态图形的能力,这对于视讯显示器并不很重要,但是对于打印设备却是非常重要。例如,大多数绘图机不能画位图图像,GetDeviceCaps就可以将这一情况告诉您
2016-01-20
GetPixel函数传回指定坐标处的图素颜色:
crColor = GetPixel (hdc, x, y) ;
2016-01-20
Windows可以画直线、椭圆线(椭圆圆周上的曲线)和贝塞尔曲线。Windows 98支援的7个画线函数是:
LineTo 画直线。 
Polyline和PolylineTo 画一系列相连的直线。 
PolyPolyline 画多组相连的线。 
Arc 画椭圆线。 
PolyBezier和PolyBezierTo 画贝塞尔曲线。 
另外,Windows NT还支持3种画线函数:ArcTo和AngleArc 画椭圆线。
  PolyDraw 画一系列相连的线以及贝塞尔曲线。
  这三个函数Windows 98不支援。
2016-01-20
一些既画线也填入所画图形的封闭区域的函数,这些函数是:
Rectangle 画矩形。 
Ellipse 画椭圆。 
RoundRect 画带圆角的矩形。 
Pie 画椭圆的一部分,使其看起来像一个扇形。 
Chord 画椭圆的一部分,以呈弓形
2016-01-20
画一条直线,必须呼叫两个函数。第一个函数指定了线的开始点,第二个函数指定了线的终点:
MoveToEx (hdc, xBeg, yBeg, NULL) ;
LineTo (hdc, xEnd, yEnd) ;
MoveToEx实际上不会画线,它只是设定了设备内容的「目前位置」属性。然后LineTo函数从目前的位置到它所指定的点画一条直线。目前位置只是用于其它几个GDI函数的开始点。在内定的设备内容中,目前位置最初设定在点(0,0)。如果在呼叫LineTo之前没有设定目前位置,那么它将从显示区域的左上角开始画线
2016-01-20
如果您需要目前位置,就可以通过以下呼叫获得:
GetCurrentPositionEx (hdc, &pt) ;
其中,pt是POINT结构的。
2016-01-20
在PostScript中,所有曲线都用贝塞尔曲线表示-椭圆线用贝塞尔曲线来逼近。贝塞尔曲线也用于定义PostScript字体的字符轮廓(TrueType使用一种更简单更快速的曲尺公式)。
一条二维的贝塞尔曲线由四个点定义-两个端点和两个控制点。曲线的端点在两个端点上,控制点就好像「磁石」一样把曲线从两个端点间的直线处拉走。
2016-01-20
要画一条或多条连接的贝塞尔曲线,只需呼叫:
PolyBezier (hdc, apt, iCount) ;
或PolyBezierTo (hdc, apt, iCount) ;
两种情况下,apt都是POINT结构的数组。对PolyBezier,前四个点(按照顺序)给出贝塞尔曲线的起点、第一个控制点、第二个控制点和终点。此后的每一条贝塞尔曲线只需给出三个点,因为后一条贝塞尔曲线的起点就是前一条贝塞尔曲线的终点,如此类推。iCount参数等于1加上您所绘制的这些首尾相接曲线条数的三倍。
PolyBezierTo函数使用目前点作为第一个起点,第一条以及后续的贝塞尔曲线都只需要给出三个点。当函数传回时,目前点设定为最后一个终点。
2016-01-20
呼叫GetStockObject,可以获得现有画笔的句柄。例如,假设您想使用名为WHITE_PEN的现有画笔,可以如下取得画笔的句柄:
hPen = GetStockObject (WHITE_PEN) ;
现在必须将画笔选进设备内容:SelectObject (hdc, hPen) ;
目前的画笔是白色。在这个呼叫后,您画的线将使用WHITE_PEN,直到您将另外一个画笔选进设备内容或者释放设备内容句柄为止。
2016-01-20
您也可以不定义hPen变量,而将GetStockObject和SelectObject呼叫合并成一个叙述:
SelectObject (hdc, GetStockObject (WHITE_PEN)) ;
如果想恢复到使用BLACK_PEN的状态,可以用一个叙述取得这种画笔的句柄,并将其选进设备内容:SelectObject (hdc, GetStockObject (BLACK_PEN)) ;
SelectObject的传回值是此呼叫前设备内容中的画笔句柄。如果启动一个新的设备内容并呼叫
hPen = SelectObject (hdc, GetStockobject (WHITE_PEN)) ;
则设备内容中的目前画笔将为WHITE_PEN,变量hPen将会是BLACK_PEN的句柄。以后通过呼叫SelectObject (hdc, hPen) ;
就能够将BLACK_PEN选进设备内容。
2016-01-20
尽管使用现有画笔非常方便,但却受限于实心的黑画笔、实心的白画笔或者没有画笔这三种情况。如果想得到更丰富多彩的效果,就必须建立自己的画笔。
这一过程通常是:使用函数CreatePen或CreatePenIndirect建立一个「逻辑画笔」,这仅仅是对画笔的描述。这些函数传回逻辑画笔的句柄;然后,呼叫SelectObject将画笔选进设备内容。现在,就可以使用新的画笔来画线了。在任何时候,都只能有一种画笔选进设备内容。在释放设备内容(或者在选择了另一种画笔到设备内容中)之后,就可以呼叫DeleteObject来删除所建立的逻辑画笔了。在删除后,该画笔的句柄就不再有效了。
2016-01-20
逻辑画笔是一种「GDI对象」,它是您可以建立的六种GDI对象之一,其它五种是画刷、位图、区域、字体和调色盘。除了调色盘之外,这些对象都是通过SelectObject选进设备内容的。
在使用画笔等GDI对象时,应该遵守以下三条规则:最后要删除自己建立的所有GDI对象。
  当GDI对象正在一个有效的设备内容中使用时,不要删除它。
  不要删除现有对象。
2016-01-20
CreatePen函数的语法形如:
hPen = CreatePen (iPenStyle, iWidth, crColor) ;
2016-01-20
在与定义一个填入区域的函数一起使用时,PS_INSIDEFRAME画笔样式还有另外一个奇特之处:对于除了PS_INSIDEFRAME以外的所有画笔样式来说,如果用来画边界框的画笔宽度大于1个图素,那么画笔将居中对齐在边界框在线,这样边界框线的一部分将位于边界框之外;而对于PS_INSIDEFRAME画笔样式来说,整条边界框线都画在边界框之内。
2016-01-20
要使用CreatePenIndirect,首先定义一个LOGPEN型态的结构:
LOGPEN logpen ;
此结构有三个成员:lopnStyle(无正负号整数或UINT)是画笔样式,lopnWidth(POINT结构)是按逻辑单位度量的画笔宽度,lopnColor (COLORREF)是画笔颜色。Windows只使用lopnWidth结构的x值作为画笔宽度,而忽略y值。将结构的地址传递给CreatePenIndirect结构就可以建立画笔了:
hPen = CreatePenIndirect (&logpen) ;
注意,CreatePen和CreatePenIndirect函数不需要设备内容句柄作为参数。这些函数建立与设备内容没有联系的逻辑画笔。直到呼叫SelectObject之后,画笔才与设备内容发生联系。因此,可以对不同的设备(如屏幕和打印机)使用相同的逻辑画笔。
2016-01-20
您可以通过如下呼叫来改变Windows用来填入空隙的背景色:
SetBkColor (hdc, crColor) ;
与画笔色彩所使用的crColor参数一样,Windows将这里的背景色转换为纯色。可以通过用GetBkColor来取得设备内容中定义的目前背景色。通过将背景模式转换为TRANSPARENT,可以阻止Windows填入空隙:
SetBkMode (hdc, TRANSPARENT) ;
此后,Windows将忽略背景色,并且不填入空隙,可以通过呼叫GetBkMode来取得目前背景模式(TRANSPARENT或者OPAQUE)。
2016-01-20
Windows定义六种现有画刷:WHITE_BRUSH、LTGRAY_BRUSH、GRAY_BRUSH、DKGRAY_BRUSH、BLACK_BRUSH和NULL_BRUSH (也叫HOLLOW_BRUSH)。您可以将任何一种现有画刷选入您的设备内容中,就和您选择一种画笔一样。Windbws将HBRUSH定义为画刷的句柄,所以可以先定义一个画刷句柄变量:
HBRUSH hBrush ;
您可以通过呼叫GetStockObject来取得GRAY_BRUSH的句柄:hBrush = GetStockObject (GRAY_BRUSH) ;
您可以呼叫SelectObject将它选进设备内容:
SelectObject (hdc, hBrush) ;
现在,如果您要画上表中的任一个图形,则其内部将为灰色。
2016-01-20
下面是建立逻辑画刷的第一个函数:
hBrush = CreateSolidBrush (crColor) ;
函数中的Solid并不是指画刷为纯色。在将画刷选入设备内容中时,Windows建立一个混色色的位图,并为画刷使用该位图。您还可以使用由水平、垂直或者倾斜的线组成的「影线标记(hatch marks)」来建立画刷,这种风格的画刷对着色条形图的内部和在绘图机上进行绘图最有用。建立影线画刷的函数为:
hBrush = CreateHatchBrush (iHatchStyle, crColor) ;
iHatchStyle参数描述影线标记的外观。图5-18显示了六种可用的影线标记风格。
 
图5-18 六种影线画刷风格
CreateHatchBrush中的crColor参数是影线的色彩。在将画刷选进设备内容时,Windows将这种色彩转换为与之最相近的纯色。影线之间的区域根据设备内容中定义的背景方式和背景色来着色。如果背景方式为OPAQUE,则用背景色(它也被转换为纯色)来填入线之间的空间。在这种情况下,影线和填入色都不能是混色而成的颜色。如果背景方式为TRANSPARENT,则Windows只画出影线,不填入它们之间的区域。您也可以使用CreatePatternBrush和CreateDIBPatternBrushPt建立自己的位图画刷。
建立逻辑画刷的第五个函数包含其它四个函数:hBrush = CreateBrushIndirect (&logbrush) ;
变量logbrush是一个型态为LOGBRUSH(「逻辑画刷」)的结构,该结构的三个字段如表5-4所示,lbStyle字段的值确定了Windows如何解释其它两个字段的值:
表5-4
lbStyle (UINT)lbColor (COLORREF)lbHatch (LONG)BS_SOLID画刷的色彩忽略
BS_HOLLOW忽略忽略BS_HATCHED影线的色彩影线画刷风格
BS_PATTERN忽略位图的句柄BS_DIBPATTERNPT忽略指向DIB的指标
前面我们用SelectObject将逻辑画笔选进设备内容,用DeleteObject删除画笔,用GetObject来取得逻辑画笔的信息。对于画刷,同样能使用这三个函数。一旦您取得到了画刷句柄,就可以使用SelectObject将该画刷选进设备内容:
SelectObject (hdc, hBrush) ;
然后,您可以使用DeleteObject函数删除所建立的画刷:DeleteObject (hBrush) ;
但是,不要删除目前选进设备内容的画刷。
如果您需要取得画刷的信息,可以呼叫GetObject:GetObject (hBrush, sizeof (LOGBRUSH), (LPVOID) &logbrush) ;
其中,logbrush是一个型态为LOGBRUSH的结构。
2016-01-20
从GetTextMetrics呼叫中传回的TEXTMETRIC结构的值是使用逻辑单位的。如果在进行此呼叫时映像方式为MM_LOENGLISH,则GetTextMetrics将以百分之一英寸为单位提供字符的宽度和高度。在呼叫GetTextMetrics以取得关于字符的宽度和高度信息时,映像方式必须设定成根据这些信息输出文字时所使用的映像方式,这样就可以简化工作。
2016-01-20
用函数ClientToScreen和ScreenToClient可以将显示区域坐标转换为屏幕坐标,或者反过来,将屏幕坐标转换为显示区域坐标。
2016-01-20
Windows也能将视埠(设备)坐标转换为窗口(逻辑)坐标:
 
Windows提供了两个函数来让您将设备点转换为逻辑点以及将逻辑点转换为设备点。下面的函数将设备点转换为逻辑点:
DPtoLP (hdc, pPoints, iNumber) ;
其中,pPoints是一个指向POINT结构数组的指针,而iNumber是要转换的点的个数。您会发现这个函数对于将GetClientRect(它总是使用设备单位)取得的显示区域大小转换为逻辑坐标很有用: GetClientRect (hwnd, &rect) ;
DPtoLP (hdc, (PPOINT) &rect, 2) ;
下面的函数将逻辑点转换为设备点:
LPtoDP (hdc, pPoints, iNumber) ;
多看笔记 来自多看阅读 for Android
duokanbookid:w2g0639e6gg4g5g2c15e3g83de09c0gb

《6》的笔记

6

2016-01-23
窗口消息处理程序通过拦截WM_SETFOCUS和WM_KILLFOCUS消息来判定它的窗口何时拥有输入焦点。WM_SETFOCUS指示窗口正在得到输入焦点,WM_KILLFOCUS表示窗口正在失去输入焦点。我将在本章的后面详细说明这些消息。
2016-01-23
在处理按键消息时,您可能需要知道是否按下了位移键(Shift、Ctrl和Alt)或开关键(Caps Lock、Num Lock和Scroll Lock)。通过呼叫GetKeyState函数,您就能获得此信息。例如:
iState = GetKeyState (VK_SHIFT) ;
如果按下了Shift,则iState值为负(即设定了最高位置位)。如果Caps Lock键打开,则从 iState = GetKeyState (VK_CAPITAL) ;
传回的值低位被设为1。此位与键盘上的小灯保持一致。
2016-01-23
。如果您的窗口消息处理程序需要晓得目前窗口是否处理Unicode消息,则它可以呼叫:
fUnicode = IsWindowUnicode (hwnd) ;
如果hwnd的窗口消息处理程序获得Unicode消息,那么变量fUnicode将为TRUE,这表示窗口是用RegisterClassW注册的窗口类别。
2016-01-23
Windows支持三类字体-点阵字体、向量字体和(从Windows 3.1开始的)TrueType字体。
事实上向量字体已经过时了。
2016-01-23
插入符号函数
主要有五个插入符号函数:
CreateCaret 建立与窗口有关的插入符号  
SetCaretPos 在窗口中设定插入符号的位置  
ShowCaret 显示插入符号  
HideCaret 隐藏插入符号  
DestroyCaret 撤消插入符号  
另外还有取得插入符号目前位置(GetCaretPos)和取得以及设定插入符号闪烁时间(GetCaretBlinkTime和SetCaretBlinkTime)的函数。
多看笔记 来自多看阅读 for Android
duokanbookid:gc26cc89fb8599829d692838e5502b29

《7》的笔记

7

2016-01-24
理论上,您可以用我们的老朋友GetSystemMetrics函数来确认鼠标是否存在:
fMouse = GetSystemMetrics (SM_MOUSEPRESENT) ;
如果已经安装了鼠标,fMouse将传回TRUE(非0);如果没有安装,则传回0。然而,在Windows 98中,不论鼠标是否安装,此函数都将传回TRUE 。在Microsoft Windows NT中,它可以正常工作。要确定所安装鼠标其上按键的个数,可使用
cButtons = GetSystemMetrics (SM_CMOUSEBUTTONS) ;
如果没有安装鼠标,那么函数将传回0。然而,在Windows 98下,如果没有安装鼠标,此函数将传回2。
2016-01-24
您可以在「控制台」中设定鼠标的其它参数,例如双击速度。从Windows应用程序,通过使用SystemParametersInfo函数可以设定或获得此项信息。
2016-01-24
在定义窗口类别结构时指定特定窗口的内定光标,例如:
wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ;
2016-01-24
Windows只把键盘消息发送给拥有输入焦点的窗口。鼠标消息与此不同:只要鼠标跨越窗口或者在某窗口中按下鼠标按键,那么窗口消息处理程序就会收到鼠标消息,而不管该窗口是否活动或者是否拥有输入焦点。Windows为鼠标定义了21种消息,不过,其中有11个消息和显示区域无关(下面称之为「非显示区域」消息),Windows程序经常忽略这些消息。
当鼠标移过窗口的显示区域时,窗口消息处理程序收到WM_MOUSEMOVE消息。当在窗口的显示区域中按下或者释放一个鼠标按键时,窗口消息处理程序会接收到下面这些消息:表7-1
键按下释放按下(双键)
左WM_LBUTTONDOWNWM_LBUTTONUPWM_LBUTTONDBLCLK中WM_MBUTTONDOWNWM_MBUTTONUPWM_MBUTTONDBLCLK
右WM_RBUTTONDOWNWM_RBUTTONUPWM_RBUTTONDBLCLK
只有对三键鼠标,窗口消息处理程序才会收到MBUTTON消息;只有对双键或者三键鼠标,才会接收到RBUTTON消息。只有当定义的窗口类别能接收DBLCLK(双击)消息,窗口消息处理程序才能接收到这些消息(请参见本章中「双击鼠标按键」一节)。对于所有这些消息来说,其lParam值均含有鼠标的位置:低字组为x坐标,高字组为y坐标,这两个坐标是相对于窗口显示区域左上角的位置。您可以用LOWORD和HIWORD宏来提取这些值:
x = LOWORD (lParam) ;
y = HIWORD (lParam) ;
wParam的值指示鼠标按键以及Shift和Ctrl键的状态。您可以使用表头文件WINUSER.H中定义的位屏蔽来测试wParam。MK前缀代表「鼠标按键」。MK_LBUTTON按下左键
MK_MBUTTON按下中键MK_RBUTTON按下右键
MK_SHIFT按下Shift键MK_CONTROL按下Ctrl键
例如,如果收到了WM_LBUTTONDOWN消息,而且值
wparam & MK_SHIFT
是TRUE(非0),您就知道当左键按下时也按下了Shift键。
2016-01-25
即使没有安装鼠标,Windows仍然可以显示一个鼠标光标。Windows为这个光标保存了一个「显示计数」。如果安装了鼠标,显示计数会被初始化为0;否则,显示计数会被初始化为-1。只有在显示计数非负时才显示鼠标光标。要增加显示计数,您可以呼叫:
ShowCursor (TRUE) ;
要减少显示计数,可以呼叫: ShowCursor (FALSE) ;
您在使用ShowCursor之前,不需要确定是否安装了鼠标。如果您想显示鼠标光标,而不管鼠标存在与否,那么只需呼叫ShowCursor来增加显示计数。增加一次显示计数之后,如果没有安装鼠标则减少它以隐藏光标,如果安装了鼠标,则保留其显示。
即使没有安装鼠标,Windows也保留了鼠标目前的位置。如果没有安装鼠标,而您又显示鼠标光标,光标就可能出现在显示器的任意位置,直到您确实移动了它。要获得光标的位置,可以呼叫: GetCursorPos (&pt) ;
其中pt是POINT结构。函数使用鼠标的x和y坐标来填入POINT字段。要设定光标位置,可以使用:
SetCursorPos (x, y) ;
在这两种情况下,x和y都是屏幕坐标,而不是显示区域坐标(这是很明显的,因为这些函数没有要求hwnd参数)。
2016-01-25
可以通过下面的呼叫来获得一个特定子窗口的子窗口ID:
idChild = GetWindowLong (hwndChild, GWL_ID) ;
下面的函数也有同样的功能: idChild = GetDlgCtrlID (hwndChild) ;
正如函数名称所表示的,它主要用于对话框和控制窗口。如果您知道父窗口的句柄和子窗口ID,此函数也可以获得子窗口的句柄:
hwndChild = GetDlgItem (hwndParent, idChild) ;
2016-01-25
一个窗口消息处理程序通常只在鼠标光标位于窗口的显示区域,或非显示区域上时才接收鼠标消息。一个程序也可能需要在鼠标位于窗口外时接收鼠标消息。如果是这样,程序可以自行「拦截」鼠标
2016-01-25
拦截鼠标要比放置一个老鼠夹子容易一些,您只要呼叫:
SetCapture (hwnd) ;
在这个函数呼叫之后,Windows将所有鼠标消息发给窗口句柄为hwnd的窗口消息处理程序。之后收到鼠标消息都是以显示区域消息的型态出现,即使鼠标正在窗口的非显示区域。lParam参数将指示鼠标在显示区域坐标中的位置。不过,当鼠标位于显示区域的左边或者上方时,这些x和y坐标可以是负的。当您想释放鼠标时,呼叫: ReleaseCapture () ;
2016-01-25
转动滑轮会导致Windows在有输入焦点的窗口(不是鼠标光标下面的窗口)产生WM_MOUSEWHEEL消息。与平常一样,lParam将获得鼠标的位置,当然坐标是相对于屏幕左上角的,而不是显示区域的。另外,wParam的低字组包含一系列的旗标,用于表示鼠标按键、Shift与Ctrl键的状态。
新的信息保存在wParam的高字组。其中有一个「delta」值,该值目前可以是120或-120,这取决于滑轮的向前转动(也就是说,向鼠标的前面,即带有按钮与电缆的一端)还是向后转动。值120或-120表示文件将分别向上或向下卷动三行。这里的构想是,以后版本的鼠标滑轮能有比现在的鼠标产生更精确的移动速度信息,并且用delta值,例如40和-40,来产生WM_MOUSEWHEEL消息。这些值能使文件只向上或向下卷动一行。 为使程序能在一般化环境执行,SYSMETS将在WM_CREATE和WM_SETTINGCHANGE消息处理时,以SPI_GETWHEELSCROLLLINES作为参数来呼叫SystemParametersInfo。此值说明WHEEL_DELTA的delta值将滚动多少行,WHEEL_DELTA在WINUSER.H中定义。WHEEL_DELTA等于120,并且,在内定情况下SystemParametersInfo传回3,因此与卷动一行相联系的delta值就是40。SYSMETS将此值保存在iDeltaPerLine。
在WM_MOUSEWHEEL消息处理期间,SYSMETS将delta值给静态变量iAccumDelta。然后,如果iAccumDelta大于或等于iDeltaPerLine(或者是小于或等于-iDeltaPerLin),SYSMETS用SB_LINEUP或SB_LINEDOWN值产生WM_VSCROLL消息。对于每一个WM_VSCROLL消息,iAccumDelta由iDeltaPerLine增加(或减少)。此代码允许delta值大于、小于或等于滚动一行所需要的delta值。
多看笔记 来自多看阅读 for Android
duokanbookid:j02858bc4cedgccd63e8f81gf3040d26

《8》的笔记

8

2016-01-25
您可以通过呼叫SetTimer函数为您的Windows程序分配一个定时器。SetTimer有一个时间间隔范围为1毫秒到4,294,967,295毫秒(将近50天)的整数型态参数,这个值指示Windows每隔多久时间给您的程序发送WM_TIMER消息。例如,如果间隔为1000毫秒,那么Windows将每秒给程序发送一个WM_TIMER消息。
当您的程序用完定时器时,它呼叫KillTimer函数来停止定时器消息。在处理WM_TIMER消息时,您可以通过呼叫KillTimer函数来编写一个「限用一次」的定时器。KillTimer呼叫清除消息队列中尚未被处理的WM_TIMER消息,从而使程序在呼叫KillTimer之后就不会再接收到WM_TIMER消息。
2016-01-25
Windows定时器是PC硬件和ROM BIOS架构下之定时器一种相对简单的扩充。回到Windows以前的MS-DOS程序写作环境下,应用程序能够通过拦截者称为timer tick的BIOS中断来实作时钟或定时器。一些为MS-DOS编写的程序自己拦截这个硬件中断以实作时钟和定时器。这些中断每54.915毫秒产生一次,或者大约每秒18.2次。这是原始的IBM PC的微处理器时脉值4.772720 MHz被218所除而得出的结果。
2016-01-25
在Windows 98中,定时器与其下的PC定时器一样具有55毫秒的分辨率。在Microsoft Windows NT中,定时器的分辨率为10毫秒。
Windows应用程序不能以高于这些分辨率的频率(在Windows 98下,每秒18.2次,在Windows NT下,每秒大约100次)接收WM_TIMER消息。在SetTimer呼叫中指定的时间间隔总是截尾后tick数的整数倍。例如,1000毫秒的间隔除以54.925毫秒,得到18.207个tick,截尾后是18个tick,它实际上是989毫秒。对每个小于55毫秒的间隔,每个tick都会产生一个WM_TIMER消息。
2016-01-25
定时器消息不是异步的
因为定时器使用硬件定时器中断,程序写作者有时会误解,认为他们的程序会异步地被中断来处理WM_TIMER消息。
然而,WM_TIMER消息并不是异步的。WM_TIMER消息放在正常的消息队列之中,和其它消息排列在一起,因此,如果在SetTimer呼叫中指定间隔为1000毫秒,那么不能保证程序每1000毫秒或者989毫秒就会收到一个WM_TIMER消息。如果其它程序的执行事件超过一秒,在此期间内,您的程序将收不到任何WM_TIMER消息。您可以使用本章的程序来展示这一点。事实上,Windows对WM_TIMER消息的处理非常类似于对WM_PAINT消息的处理,这两个消息都是低优先级的,程序只有在消息队列中没有其它消息时才接收它们。 WM_TIMER还在另一方面和WM_PAINT相似:Windows不能持续向消息队列中放入多个WM_TIMER消息,而是将多余的WM_TIMER消息组合成一个消息。因此,应用程序不会一次收到多个这样的消息,尽管可能在短时间内得到两个WM_TIMER消息。应用程序不能确定这种处理方式所导致的WM_TIMER消息「遗漏」的数目。
2016-01-25
定时器的使用:三种方法
如果您需要在整个程序执行期间都使用定时器,那么您将得从WinMain函数中或者在处理WM_CREATE消息时呼叫SetTimer,并在退出WinMain或响应WM_DESTROY消息时呼叫KillTimer。根据呼叫SetTimer时使用的参数,可以下列三种方法之一使用定时器。
方法一
这是最方便的一种方法,它让Windows把WM_TIMER消息发送到应用程序的正常窗口消息处理程序中,SetTimer呼叫如下所示: SetTimer (hwnd, 1, uiMsecInterval, NULL) ;
第一个参数是其窗口消息处理程序将接收WM_TIMER消息的窗口句柄。第二个参数是定时器ID,它是一个非0数值,在整个例子中假定为1。第三个参数是一个32位无正负号整数,以毫秒为单位指定一个时间间隔,一个60,000的值将使Windows每分钟发送一次WM_TIMER消息。
您可以通过呼叫 KillTimer (hwnd, 1) ;
在任何时刻停止WM_TIMER消息(即使正在处理WM_TIMER消息)。此函数的第二个参数是SetTimer呼叫中所用的同一个定时器ID。在终止程序之前,您应该响应WM_DESTROY消息停止任何活动的定时器。
2016-01-25
如果您想将一个已经存在的定时器设定为不同的时间间隔,您可以简单地用不同的时间值再次呼叫SetTimer。
2016-01-25
方法二
设定定时器的第一种方法是把WM_TIMER消息发送到通常的窗口消息处理程序,而第二种方法是让Windows直接将定时器消息发送给您程序的另一个函数。
接收这些定时器消息的函数被称为「callback」函数,这是一个在您的程序之中但是由Windows呼叫的函数。您先告诉Windows此函数的地址,然后Windows呼叫此函数。这看起来也很熟悉,因为程序的窗口消息处理程序实际上也是一种callback函数。当注册窗口类别时,要将函数的地址告诉Windows,当发送消息给程序时,Windows会呼叫此函数。
2016-01-26
方法三
设定定时器的第三种方法类似于第二种方法,只是传递给SetTimer的hwnd参数被设定为NULL,并且第二个参数(通常为定时器ID)被忽略了,最后,此函数传回定时器ID:
iTimerID = SetTimer (NULL, 0, wMsecInterval, TimerProc) ;
如果没有可用的定时器,那么从SetTimer传回的iTimerID值将为NULL。 KillTimer的第一个参数(通常是窗口句柄)也必须为NULL,定时器ID必须是SetTimer的传回值:
KillTimer (NULL, iTimerID) ;
传递给TimerProc定时器函数的hwnd参数也必须是NULL。这种设定定时器的方法很少被使用。如果在您的程序在不同时刻有一系列的SetTimer呼叫,而又不希望追踪您已经用过了那些定时器ID,那么使用此方法是很方便的。
2016-01-26
尽管像DIGCLOCK这样显示时间是非常简单的,但是要显示复杂的日期和时间还是要依赖Windows的国际化支持。格式化日期和时间的最简单的方法是呼叫GetDateFormat和GetTimeFormat函数。
2016-01-26
呼叫GetLocaleInfo两次,第一次使用LOCALE_ITIME标识符(确定使用的是12小时还是24小时格式),然后使用LOCALE_ITLZERO标识符(在小时显示中禁止前面显示0)。
2016-01-26
要指出时间是A.M.或P.M.,应用程序可以使用带有LOCALE_S1159和LOCALE_S2359标识符的GetLocaleInfo函数。这些标识符使程序获得适合于使用者国家/地区和语言的字符串。
多看笔记 来自多看阅读 for Android
duokanbookid:m60e8g620d7b2c89e42b658b9fd5fc2b

《9》的笔记

9

2016-01-26
GetDialogBaseUnits函数来获得内定字体字符的宽度和高度。这是对话框用来获得文字尺寸的函数。此函数传回一个32位的值,其中低字组表示宽度,高字组表示高度。由于GetDialogBaseUnits传回的值与从GetTextMetrics获得的值大致上相同,但GetDialogBaseUnits有时使用起来会更方便些,而且能够与对话框控件更好地保持一致。
2016-01-28
每个子窗口控件都具有一个在其兄弟中唯一的窗口句柄和ID值。对于句柄和ID这两者,知道其中的一个您就可以获得另一个。如果您知道子窗口控件的窗口句柄,那么您可以用下面的叙述来获得ID:
id = GetWindowLong (hwndChild, GWL_ID) ;
2016-01-28
如果您想建立与窗口滚动条尺寸相同的滚动条控件,那么可以使用GetSystemMetrics取得水平滚动条的高度:
GetSystemMetrics (SM_CYHSCROLL) ;
或者垂直滚动条的宽度: GetSystemMetrics (SM_CXVSCROLL) ;
多看笔记 来自多看阅读 for Android
duokanbookid:j20e71e7c3g92g8g6943gc77f67d7699

《10》的笔记

10

2016-01-29
LoadIcon和LoadCursor,它们出现在范例程序,定义窗口类别结构的内容设定叙述中。它们从Windows中加载二进制图标和光标映象,并传回该图标或光标的句柄。
2016-01-29
DrawIcon (hdc, x, y, hIcon) ;
显示图标,其中x和y是被显示图示其左上角的坐标。
2016-01-29
程序可以通过呼叫LoadIcon函数取得此图示的句柄:
hIcon = LoadIcon (hInstance, MAKEINTRESOURCE (IDI_ICON)) ;
请注意ICONDEMO在两个地方呼叫这个函数,一次在定义窗口类别时,另一次在窗口消息处理程序中取得图标的句柄用于绘制。LoadIcon传回HICON型态的值,它是图示的句柄。
2016-01-29
可以在对象类别定义中设定自订光标,叙述为:
wndclass.hCursor = LoadCursor (hInstance, MAKEINTRESOURCE (IDC_CURSOR)) ;
如果光标用文字名称定义,则为: wndclass.hCursor = LoadCursor (hInstance, szCursor) ;
2016-01-29
如果使用了预先定义的子窗口控件,就可以使用以下方法改变窗口类别的hCursor字段:
SetClassLong (hwndChild, GCL_HCURSOR,
LoadCursor (hInstance, TEXT ("childcursor")) ;
如果您将显示区域划分为较小的逻辑区域而不使用子窗口,就可以使用SetCursor来改变鼠标光标: SetCursor (hCursor) ;
在处理WM_MOUSEMOVE消息处理期间,您应该呼叫SetCursor;否则,当光标移动时,Windows将使用窗口类别中定义的光标来重画光标。文件指出,如果没有改变光标,则SetCursor速度将会很快。
2016-01-29
字符串资源主要是为了让程序转换成其它语言时更为方便。
2016-01-29
在程序的初始化(比如,在处理WM_CREATE消息时)期间,您可以获得资源的句柄:
hResource = LoadResource (hInstance,
FindResource (hInstance, TEXT ("BINTYPE"),
MAKEINTRESOURCE (IDR_BINTYPE1))) ;
变量hResource定义为HGLOBAL型态,它是指向内存区块的句柄。不管它的名称是什么,LoadResource不会立即将资源加载内存。把LoadResource和FindResource函数如上例般合在一起使用,在实质上就类似于LoadIcon和LoadCursor函数的做法。事实上,LoadIcon和LoadCursor函数就用到了LoadResource和FindResource函数。
2016-01-29
当您需要存取文字时,呼叫LockResource:
pData = LockResource (hResource) ;
LockResource将资源加载内存(如果还没有加载的话),然后它会传回一个指向资源的指标。当结束对资源的使用时,您可以从内存中释放它: FreeResource (hResource) ;
当您的程序终止时,也会释放资源,即使您没有呼叫FreeResource.。
2016-01-29
IDS_APPNAME和IDS_CAPTIONPOEPOEM字符串由LoadString加载内存:
LoadString (hInstance, IDS_APPNAME, szAppName, sizeof (szAppName) /
sizeof (TCHAR)) ;
LoadString (hInstance, IDS_CAPTION, szCaption, sizeof (szCaption) /
sizeof (TCHAR)) ;
2016-01-29
当您为菜单中的项目键入文字时,可以键入一个「&」符号,指出后面一个字符在Windows显示菜单时要加底线。这种底线字符是在您使用Alt键选择菜单项时Windows要寻找的比对字符。如果在文字中不包括「&」符号,就不显示任何底线,Windows会将菜单项文字的第一个字母用于Alt键查找。
2016-01-29
在弹出式菜单的项目上,可以在字符串中使用制表符\t。紧接着\t的文字被放置在距离弹出式菜单的第一列右边新的一列上。
2016-01-29
Windows应用程序可以使用LoadMenu函数将菜单资源加载内存中,如同LoadIcon和LoadCursor函数一样。LoadMenu传回一个菜单句柄。如果您在资源描述档中为菜单使用了名称,叙述如下:
hMenu = LoadMenu (hInstance, TEXT ("MyMenu")) ;
如果使用了数值,那么LoadMenu呼叫采用如下的形式: hMenu = LoadMenu (hInstance, MAKEINTRESOURCE (ID_MENU)) ;
然后,您可以将这个菜单句柄作为CreateWindow的第九个参数:
hwnd = CreateWindow ( TEXT ("MyClass"), TEXT ("Window Caption"),
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT,
NULL, hMenu, hInstance, NULL) ;
在这种情况下,CreateWindow呼叫中指定的菜单可以覆盖窗口类别中指定的任何菜单。如果CreateWindow的第九个参数是NULL,那么您可以把窗口类别中的菜单看作是这种窗口类别的窗口内定使用的菜单。这样,您可以为依据同一窗口类别建立的几个窗口使用不同的菜单。
2016-01-29
您也可以在窗口类别中指定NULL菜单,并且在CreateWindow呼叫中也指定NULL菜单,然后在窗口被建立后再给窗口指定一个菜单:
SetMenu (hwnd, hMenu) ;
2016-01-29
改变菜单
我们已经看到了如何使用AppendMenu函数为程序定义菜单以及将菜单项加入到系统菜单中。在Windows 3.0之前,您不得不被迫使用ChangeMenu函数来完成这种工作。ChangeMenu函数有很多功能,至少在当时,整个Windows中它是最复杂的函数之一。现在,许多函数都比ChangeMenu函数还要复杂,并且ChangeMenu的功能被分解为五个新的函数:
AppendMenu在菜单尾部添加一个新的菜单项目  
DeleteMenu删除菜单中一个现有的菜单项并清除该项目  
InsertMenu在菜单中插入一个新项目  
ModifyMenu修改一个现有的菜单项目  
RemoveMenu从菜单中移走某一项目  
如果菜单项是一个弹出式菜单,那么DeleteMenu和RemoveMenu之间的区别就很重要。DeleteMenu清除弹出式菜单,但RemoveMenu不清除它。
2016-01-29
当您改变顶层菜单项时,直到Windows重画菜单列时才显示所做的改变。您可以通过下列呼叫来强迫执行菜单更新:
DrawMenuBar (hwnd) ;
注意,DrawMenuBar的参数是窗口句柄而不是菜单句柄。 您可以使用下列命令来获得弹出式菜单的句柄:
hMenuPopup = GetSubMenu (hMenu, iPosition) ;
其中iPosition是hMenu指示的顶层菜单中弹出式菜单项的索引(开始为0)。然后您可以在其它函数中使用弹出式菜单句柄(例如在AppendMenu函数中)。 您可以使用下列命令获得顶层菜单或者弹出式菜单中目前的项数:
iCount = GetMenuItemCount (hMenu) ;
您可以取得弹出式菜单项的菜单ID: id = GetMenuItemID (hMenuPopup, iPosition) ;
其中iPosition是菜单项在弹出式菜单中的位置(以0开始)。
在MENUDEMO中您已经看到如何选中、或者取消选中弹出式菜单中的某一项: CheckMenuItem (hMenu, id, iCheck) ;
在MENUDEMO中,hMenu是顶层菜单的句柄,id是菜单ID,而iCheck的值是MF_CHECKED或MF_UNCHECKED。如果hMenu是弹出式菜单句柄,那么参数id是位置索引而不是菜单ID。如果使用索引会更方便的话,那么您可以在第三个参数中包含MF_BYPOSITION,例如:
CheckMenuItem (hMenu, iPosition, MF_CHECKED | MF_BYPOSITION) ;
除了第三个参数是MF_ENABLED、MF_DISABLED或MF_GRAYED外,EnableMenuItem函数与CheckMenuItem函数所完成的工作类似。如果您在具有弹出式菜单的顶层菜单项上使用EnableMenuItem,那么必须在第三个参数中使用MF_BYPOSITION标识符,因为菜单项没有菜单ID。我们将在本章后面所示的POPPAD2程序中看到EnableMenuItem的一个例子。 HiliteMenuItem也类似于CheckMenuItem和EnableMenuItem,但是它使用的是MF_HILITE和MF_UNHILITE。当您在菜单项之间移动时,Windows使用反白显示方式加亮显示菜单项。您通常不需要使用HiliteMenuItem。
2016-01-29
快捷键可以是虚拟键或ASCII字符与Shift、Ctrl或Alt键的组合。可以通过在字母前键入『^』来指定带有Ctrl键的ASCII字符。也可以从下拉式清单方块中选取虚拟键。
2016-02-11
快捷键表的加载
在您的程序中,您使用LoadAccelerators函数把快捷键表加载内存,并获得该表的句柄。 LoadAccelerators叙述非常类似于LoadIcon、LoadCursor和LoadMenu叙述。
首先,把快捷键表的句柄定义为型态HANDLE: HANDLE hAccel ;
然后加载加速键表:
hAccel = LoadAccelerators (hInstance, TEXT ("MyAccelerators")) ;
正如图标、光标和菜单一样,您可以使用一个数值代替快捷键表的名称,然后在LoadAccelerators叙述中和MAKEINTRESOURCE宏一起使用该数值,或者把它放在双引号内,前面冠以字符「#」。
多看笔记 来自多看阅读 for Android
duokanbookid:s4d9b3172e70c3012311c01g4580e848

《12》的笔记

12

2016-02-23
Windows支持不同的预先定义剪贴簿格式,这些格式在WINUSER.H定义成以CF为前缀的标识符。
2016-02-23
CF_TEXT以NULL结尾的ANSI字符集字符串。它在每行末尾包含一个carriage return和linefeed字符,这是最简单的剪贴簿数据格式。传送到剪贴簿的数据存放在整体内存块中,并且是利用内存块句柄进行传送的(我将简短地讨论此项概念)。这个内存块专供剪贴簿使用,建立它的程序不应该继续使用它。
  CF_OEMTEXT含有文字数据(与CF_TEXT类似)的内存块。但是它使用的是OEM字符集。通常Windows程序不必关心这一点;它只有与在窗口中执行MS-DOS程序一起使用剪贴簿时才会使用。
  CF_UNICODETEXT含有Unicode文字的内存块。与CF_TEXT类似,它在每一行的末尾包含一个carriage return和linefeed字符,以及一个NULL字符(两个0字节)以表示数据结束。CF_UNICODETEXT只支援Windows NT。
  CF_LOCALE一个国家地区标识符的句柄。表示剪贴簿文字使用的国别地区设定。
  下面是两种附加的剪贴簿格式,它们在概念上与CF_TEXT格式相似(也就是说,它们都是文字数据),但是它们不需要以NULL结尾,因为格式已经定义了数据的结尾。现在已经很少使用这些格式了:
CF_SYLK包含Microsoft 「符号连结」数据格式的整体内存块。这种格式用在Microsoft的Multiplan、Chart和Excel程序之间交换数据,它是一种ASCII码格式,每一行都用carriage return和linefeed结尾。  
CF_DIF包含数据交换格式(DIF)之数据的整体内存块。这种格式是由Software Arts公司提出的,用于把数据送到VisiCalc电子表格程序中。这也是一种ASCII码格式,每一行都使用carriage return和linefeed结尾。  
下面三种剪贴簿格式与位图有关。所谓位图就是数据位的矩形数组,其中的数据位与输出设备的图素相对应。 第十四和 第十五章将详细讨论位图以及这些位图剪贴簿的格式: CF_BITMAP与设备相关的位图格式。位图是通过位图句柄传送给剪贴簿的。同样,在把这个位图传送给剪贴簿之后,程序不应该再继续使用这个位图。
  CF_DIB定义一个设备无关位图(在第十五章中描述)的内存块。这种内存块是以位图信息结构开始的,后面跟着可用的颜色表和位图数据位。
  CF_PALETTE调色盘句柄。它通常与CF_DIB配合使用,以定义与设备相关的位图所使用的颜色调色盘。
  在剪贴簿中,还有可能以工业标准的TIFF格式储存的位图数据:
CF_TIFF含有标号图像文件格式(TIFF)数据的整体内存块。这种格式由Microsoft、Aldus公司和Hewlett-Packard公司以及一些硬件厂商推荐使用。这一格式可从Hewlett-Packard的网站上获得。  
下面是两个metafile格式,我将在第十八章详细讨论。一个metafile就是一个以二进制格式储存的画图命令集: CF_METAFILEPICT以旧的metafile格式存放的「图片」。
  CF_ENHMETAFILE增强型metafile(32位Windows支持的)句柄。
  最后介绍几个混合型的剪贴簿格式:
CF_PENDATA与Windows的笔式输入扩充功能联合使用。  
CF_WAVE声音(波形)文件。  
CF_RIFF使用资源交换文件格式(Resource Interchange File Format)的多媒体数据。  
CF_HDROP与拖放服务相关的文件列表。  
内存配置
程序向剪贴簿传输一些数据的时候,必须配置一个内存块,并且将这块内存交给剪贴簿处理。在本书早期的程序中需要配置内存时,我们只需使用标准C执行时期链接库所支持的malloc函数。但是,由于在Windows中执行的应用程序之间必须要共享剪贴簿所储存的内存块,这时malloc函数就有些不适任这项任务了。 实际上,我们必须把早期Windows所开发的内存配置函数再拿出来使用,那时的操作系统在16位的实际模式内存结构中执行。现在的Windows仍然支持这些函数,您还可以使用它们,但不是必须使用这些函数就是了。
要用Windows API来配置一个内存块,可以呼叫: hGlobal = GlobalAlloc (uiFlags, dwSize) ;
此函数有两个参数:一系列可能的旗标和内存块的字节大小。函数传回一个HGLOBAL型态的句柄,称为「整体内存块句柄」或「整体句柄」。传回值为NULL表示不能配置足够的内存。
虽然GlobalAlloc的两个参数略有不同,但它们都是32位的无正负号整数。如果将第一个参数设定为0,那么您就可以更有效地使用旗标GMEM_FIXED。在这种情况下,GlobalAlloc传回的整体句柄实际是指向所配置内存块的指针。 如果不喜欢将内存块中的每一位都初始化为0,那么您也能够使用旗标GMEM,_ZEROINIT。在Windows表头文件中,简洁的GPTR旗标定义为GMEM_FIXED和GMEM_ZEROINIT旗标的组合:
#define GPTR (GMEM_FIXED | GMEM_ZEROINIT)
下面是一个重新配置函数: hGlobal = GlobalReAlloc (hGlobal, dwSize, uiFlags) ;
如果内存块扩大了,您可以用GMEM_ZEROINIT旗标将新的字节设为0。
下面是获得内存块大小的函数: dwSize = GlobalSize (hGlobal) ;
释放内存块的函数:
GlobalFree (hGlobal) ;
2016-02-23
将文字传送到剪贴簿
让我们想象把一个ANSI字符串传送到剪贴簿上,并且我们已经有了指向这个字符串的指针(pString)。现在希望传送这个字符串的iLength字符,这些字符可能以NULL结尾,也可能不以NULL结尾。
首先,通过使用GlobalAlloc来配置一个足以储存字符串的内存块,其中还包括一个终止字符NULL: hGlobal = GlobalAlloc (GHND | GMEM_SHARE, iLength + 1) ;
如果未能配置到内存块,hGlobal的值将为NULL 。如果配置成功,则锁定这块内存,并得到指向它的一个指标:
pGlobal = GlobalLock (hGlobal) ;
将字符串复制到内存块中: for (i = 0 ; i < wLength ; i++)
*pGlobal++ = *pString++ ;
由于GlobalAlloc的GHND旗标已使整个内存块在配置期间被清除为零,所以不需要增加结尾的NULL 。以下叙述为内存块解锁:
GlobalUnlock (hGlobal) ;
现在就有了表示以NULL结尾的文字所在内存块的内存句柄。为了把它送到剪贴簿中,打开剪贴簿并把它清空: OpenClipboard (hwnd) ;
EmptyClipboard () ;
利用CF_TEXT标识符把内存句柄交给剪贴簿,关闭剪贴簿:
SetClipboardData (CF_TEXT, hGlobal) ;
CloseClipboard () ;
2016-02-23
从剪贴簿上取得文字
从剪贴簿上取得文字只比把文字传送到剪贴簿上稍微复杂一些。您必须首先确定剪贴簿是否含有CF_TEXT格式的数据,最简单的方法是呼叫
bAvailable = IsClipboardFormatAvailable (CF_TEXT) ;
如果剪贴簿上含有CF_TEXT数据,这个函数将传回TRUE(非零)。我们在第十章的POPPAD2程序中已使用了这个函数,用它来确定「Edit」菜单中「Paste」项是被启用还是被停用的。IsClipboardFormatAvailable是少数几个不需先打开剪贴簿就可以使用的剪贴簿函数之一。但是,如果您之后想再打开剪贴簿以取得这个文字,就应该再做一次检查(使用同样的函数或其它方法),以便确定CF_TEXT数据是否仍然留在剪贴簿中。 为了传送出文字,首先打开剪贴簿:
OpenClipboard (hwnd) ;
会得到代表文字的内存块代号: hGlobal = GetClipboardData (CF_TEXT) ;
如果剪贴簿不包含CF_TEXT格式的数据,此句柄就为NULL。这是确定剪贴簿是否含有文字的另一种方法。如果GetClipboardData传回NULL,则关闭剪贴簿,不做其它任何工作。
从GetClipboardData得到的句柄并不属于使用者程序-它属于剪贴簿。仅在GetClipboardData和CloseClipboard呼叫之间这个句柄才有效。您不能释放这个句柄或更改它所引用的数据。如果需要继续存取这些数据,必须制作这个内存块的副本。 这里有一种将数据复制到使用者程序中的方法。首先,配置一块与剪贴簿数据块大小相同的内存块,并配置一个指向该块的指标:
pText = (char *) malloc (GlobalSize (hGlobal)) ;
再次呼叫hGlobal ,而hGlobal是从GetClipboardData呼叫传回的整体句柄。现在锁定句柄,获得一个指向剪贴簿块的指标: pGlobal = GlobalLock (hGlobal) ;
现在就可以复制数据了:
strcpy (pText, pGlobal) ;
或者,您可以使用一些简单的C程序代码: while (*pText++ = *pGlobal++) ;
在关闭剪贴簿之前先解锁内存块:
GlobalUnlock (hGlobal) ;
CloseClipboard () ;
现在您有了一个叫做pText的指针,以后程序的使用者就可以用它来复制文字了。
2016-02-23
剪贴簿和Unicode
迄今为止,我只讨论了用剪贴簿处理ANSI文字(每个字符对应一个字节)。我们用CF_TEXT标识符时就是这种格式。您可能对CF_OEMTEXT和CF_UNICODETEXT还不熟悉吧。
我有一些好消息:在处理您所想要的文字格式时,您只需呼叫SetClipboardData和GetClipboardData,Windows将处理剪贴簿中所有的文字转换。例如,在Windows NT中,如果一个程序用SetClipboardData来处理CF_TEXT剪贴簿数据型态,程序也能用CF_OEMTEXT呼叫GetClipboardData。同样地,剪贴簿也能将CF_OEMTEXT数据转换为CF_TEXT。 在Windows NT中,转换发生在CF_UNICODETEXT、CF_TEXT和CF_OEMTEXT之间。程序应该使用对程序本身而言最方便的一种文字格式来呼叫SetClipboardData 。同样地,程序应该用程序需要的文字格式来呼叫GetClipboardData。我们已经知道,本书附上的程序在编写时可以带有或不带UNICODE标识符。如果您的程序也依此编写,那么在定义了UNICODE标识符之后,程序将执行带有CF_UNICODETEXT参数的SetClipboardData以及GetClipboardData呼叫,而不是CF_TEXT。
2016-02-23
复杂的剪贴簿用法
我们已经看到,在将数据准备好之后,从剪贴簿传输数据时需要四个呼叫:
OpenClipboard (hwnd) ;
EmptyClipboard () ;
SetClipboardData (iFormat, hGlobal) ;
CloseClipboard () ;
存取这些数据需要三个呼叫 OpenClipboard (hwnd) ;
hGlobal = GetClipboardData (iFormat) ;
其它行程序
CloseClipboard () ;
在GetClipboardData和CloseClipboard呼叫之间,可以复制剪贴簿数据或以其它方式来使用它。很多应用程序都需要采用这种方法,但也可以用更复杂的方式来使用剪贴簿。
2016-02-23
自订数据格式
到目前为止,我们仅处理了Windows定义的标准剪贴簿资料格式。但是,您可能想用剪贴簿来储存「自订数据格式」。许多文书处理程序使用这种技术来储存包含着字体和格式化信息的文字。
初看之下,这个概念似乎是没有意义的。如果剪贴簿的作用是在应用程序之间传送数据,那么,为什么剪贴簿中要含有只有一个应用程序才能理解的数据呢?答案很简单:剪贴簿允许在同一个程序的内部(或者可能在一个程序中的不同执行实体之间)传送数据。很明显地,这些执行实体能理解它们自己的自订数据格式。 有几种使用自订数据格式的方法。最简单的方法用到一种表面上是标准剪贴簿格式(文字、位图或metafile)的数据,可是该数据实际上只对您的程序有意义。这种情况下,在SetClipboardData和GetClipboardData呼叫中可使用下列wFormat值:CF_DSPTEXT、CF_DSPBITMAP、CF_DSPMETAFILEPICT或CF_DSPENHMETAFILE(字母DSP代表「显示器」)。这些格式允许Windows按文字、位图或metafile来浏览或显示资料。但是,另一个使用常规的CF_TEXT、CF_BITMAP、CF_DIB、CF_METAFILEPICT或CF_ENHMETAFILE格式呼叫GetClipboardData的程序将不能取得这个数据。
如果用其中一种格式把数据放入剪贴簿中,则必须使用同样的格式读出数据。但是,如何知道数据是来自程序的另一个执行实体,还是来自使用其中某种数据格式的另一个程序呢?这里有一种方法,可以透过下列呼叫首先获得剪贴簿所有者: hwndClipOwner = GetClipboardOwner () ;
然后可以得到此窗口句柄的窗口类别名称:
TCHAR szClassName [32] ;
//其它行程序
GetClassName (hwndClipOwner, szClassName, 32) ;
如果类别名称与程序名称相同,那么数据是由程序的另一个执行实体传送到剪贴簿中的。
2016-02-23
使用自订数据格式的第二种方法涉及到CF_OWNERDISPLAY旗标。SetClipboardData的整体内存句柄是NULL:
SetClipboardData (CF_OWNERDISPLAY, NULL) ;
这是某些文书处理程序在Windows的剪贴簿浏览器的显示区域中显示格式化文字时所采用的方法。很明显地,剪贴簿浏览器不知道如何显示这种格式化文字。当一个文书处理程序指定CF_OWNERDISPLAY格式时,它也就承担起在剪贴簿浏览器的显示区域中绘图的责任。 由于整体内存句柄为NULL,所以用CF_OWNERDISPLAY格式(剪贴簿所有者)呼叫SetClipboardData的程序必须处理由Windows发往剪贴簿所有者的延迟提出消息、以及5条附加消息。这5个消息是由剪贴簿浏览器发送到剪贴簿所有者的:
WM_ASKCBFORMATNAME剪贴簿浏览器把这个消息发送到剪贴簿所有者,以得到数据格式名称。lParam参数是指向缓冲区的指标,wParam是这个缓冲区能容纳的最大字符数目。剪贴簿所有者必须把剪贴簿数据格式的名字复制到这个缓冲区中。  
WM_SIZECLIPBOARD这个消息通知剪贴簿所有者,剪贴簿浏览器的显示区域大小己发生了变化。wParam参数是剪贴簿浏览器的句柄,lParam是指向包含新尺寸的RECT结构的指针。如果RECT结构中都是0,则剪贴簿浏览器退出或最小化。尽管Windows的剪贴簿浏览器只允许它自己的一个执行实体执行,但其它剪贴簿浏览器也能把这个消息发送给剪贴簿所有者。应付多个剪贴簿浏览器并非不可能(假定wParam标识特定的浏览器),但剪贴簿所有者处理起来也不容易。  
WM_PAINTCLIPBOARD这个消息通知剪贴簿所有者修改剪贴簿浏览器的显示区域。同时,wParam是剪贴簿浏览器窗口的句柄,lParam是指向PAINTSTRUCT结构的整体指针。剪贴簿所有者可以从此结构的hdc栏中得到剪贴簿浏览器设备内容的句柄。  
WM_HSCROLLCLIPBOARD和WM_VSCROLLCLIPBOARD这两个消息通知剪贴簿所有者,使用者已经卷动了剪贴簿浏览器的卷动列。wParam参数是剪贴簿浏览器窗口的句柄,lParam的低字组是卷动请求,并且,如果低字组是SB_THUMBPOSITION,那么lParam的高字组就是滑块位置。  
处理这些消息比较麻烦,看来并不值得这样做。但是,这种处理对使用者来说是有益的。当从文书处理程序把文字复制到剪贴簿时,使用者在剪贴簿浏览器的显示区域中看见文字还保持着格式时心里会舒坦些
2016-02-23
使用私有剪贴簿数据格式的第三种方法是注册自己的剪贴簿格式名。您向Windows提供格式名,Windows给程序提供一个序号,它可以用作SetClipboardData和GetClipboardData的格式参数。一般来说,采用这种方法的程序也要以一种标准格式把数据复制到剪贴簿。这种方法允许剪贴簿浏览器在它的显示区域中显示数据(没有与CF_OWNERDISPLAY相关的冲突),并且允许其它程序从剪贴簿上复制数据。
例如,假定我们已经编写了一个以位图格式、metafile格式和自己的已注册的剪贴簿格式把数据复制到剪贴簿中的向量绘图程序。剪贴簿浏览器将显示metafile或者位图,其它从剪贴簿上读取位图和metafile的程序将获得这几种格式。但是,当我们的向量绘图程序需要从剪贴簿上读数据时,它会按照自己已注册的格式复制数据,这是因为这种格式可能包含着比位图文件或者metafile更多的信息。 程序透过下面的呼叫来注册一个新的剪贴簿格式:
iFormat = RegisterClipboardFormat (szFormatName) ;
iFormat的值介于0xC000和0xFFFF之间。剪贴簿浏览器(或一个通过呼叫EnumClipboardFormats取得目前所有剪贴簿数据格式的程序)可以取得这种数据格式的ASCII名称,这是通过下面呼叫实作的: GetClipboardFormatName (iFormat, psBuffer, iMaxCount) ;
Windows将多达iMaxCount个字符复制到psBuffer中。
使用这种方法把数据复制到剪贴簿中的程序写作者,可能需要公开数据格式名称和实际的数据格式。如果这个程序流行起来,那么其它程序就会以这种格式从剪贴簿中复制数据。
2016-02-23
实作剪贴簿浏览器
监视剪贴簿内容变化的程序称为「剪贴簿浏览器」。您可以在Windows中得到一个剪贴簿浏览器,但是您也可以编写自己的剪贴簿浏览器程序。剪贴簿浏览器通过传递到浏览器窗口消息处理程序的消息来监视剪贴簿内容的变化。
剪贴簿浏览器链
任意数量的剪贴簿浏览器应用程序都可以同时在Windows下执行,它们都可以监视剪贴簿内容的变化。但是,从Windows的角度来看,只存在一个剪贴簿浏览器,我们称之为「目前剪贴簿浏览器」。Windows只保留一个识别目前剪贴簿浏览器的窗口句柄,并且当剪贴簿的内容发生变化时只把消息发送到那个窗口中。 剪贴簿浏览器应用程序有必要加入「剪贴簿浏览器链」,以便执行的所有剪贴簿浏览器都可以收到Windows发送给目前剪贴簿浏览器的消息。当一个程序将自己注册为一个剪贴簿浏览器时,它就成为目前的剪贴簿浏览器。Windows把先前的目前浏览器窗口句柄交给这个程序,并且此程序将储存这个句柄。当此程序收到一个剪贴簿浏览器消息时,它把这个消息发送给剪贴簿链中下一个程序的窗口消息处理程序。
剪贴簿浏览器的函数和消息
程序透过呼叫SetClipboardViewer函数可以成为剪贴簿浏览器链的一部分。如果程序的主要作用是作为剪贴簿浏览器,那么这个程序在WM_CREATE消息处理期间可以呼叫这个函数,该函数传回前一个目前剪贴簿浏览器的窗口句柄。程序应该把这个句柄储存在静态变量中: static HWND hwndNextViewer ;
//其它行程序
case WM_CREATE :
//其它行程序
hwndNextViewer = SetClipboardViewer (hwnd) ;
如果在Windows的一次执行期间,您的程序成为剪贴簿浏览器的第一个程序,那么hwndNextViewer将为NULL。
不管剪贴簿中的内容怎样变化,Windows都将把WM_DRAWCLIPBOARD消息发送给目前的剪贴簿浏览器(最近注册为剪贴簿浏览器的窗口)。剪贴簿浏览器链中的每个程序都应该用SendMessage把这个消息发送到下一个剪贴簿浏览器。浏览器链中的最后一个程序(第一个将自己注册为剪贴簿浏览器的窗口)所储存的hwndNextViewer为NULL。如果hwndNextViewer为NULL,那么程序只简单地将控件权还给系统而已,而不向其它程序发送任何消息(不要把WM_DRAWCLIPBOARD消息和WM_PAINTCLIPBOARD消息混淆了。WM_PAINTCLIPBOARD是由剪贴簿浏览器发送给使用CF_OWNERDISPLAY剪贴簿数据格式的程序,而WM_ DRAWCLIPBOARD消息是由Windows发往目前剪贴簿浏览器的)。 处理WM_DRAWCLIPBOARD消息的最简单方法是将消息发送给下一个剪贴簿浏览器(除非hwndNextViewer为NULL),并使窗口的显示区域无效:
case WM_DRAWCLIPBOARD :
if ( hwndNextViewer)
SendMessage (hwndNextViewer, message, wParam, lParam) ;
InvalidateRect (hwnd, NULL, TRUE) ;
return 0 ;
在处理WM_PAINT消息处理期间,通过使用常规的OpenClipboard、GetClipboardData和CloseClipboard呼叫可以读取剪贴簿的内容。
2016-02-23
当某个程序想从剪贴簿浏览器链中删除它自己时,它必须呼叫ChangeClipboardChain。这个函数接收脱离浏览器链的程序之窗口句柄,和下一个剪贴簿浏览器的窗口句柄:
ChangeClipboardChain (hwnd, hwndNextViewer) ;
当程序呼叫ChangeClipboardChain时,Windows发送WM_CHANGECBCHAIN消息给目前的剪贴簿浏览器。wParam参数是从链中移除它自己的那个浏览器窗口句柄(ChangeClipboardChain的第一个参数),lParam是从链中移除自己后的下一个剪贴簿浏览器的窗口句柄(ChangeClipboardChain的第二个参数)。 当程序接收到WM_CHANGECBCHAIN消息时,必须检查wParam是否等于已经储存的hwndNextViewer的值。如果是这样,程序必须设定hwndNextViewer为lParam。这项工作保证将来的WM_DRAWCLIPBOARD消息不会发送给从剪贴簿浏览器链中删除了自己的窗口。如果wParam不等于hwndNextViewer ,并且hwndNextViewer不为NULL,则把消息送到下一个剪贴簿浏览器。
case WM_CHANGECBCHAIN :
if ((HWND) wParam == hwndNextViewer)
hwndNextViewer = (HWND) lParam ;
else if (hwndNextViewer)
SendMessage (hwndNextViewer, message, wParam, lParam) ;
return 0 ;
不一定要使用else if叙述,它只用于保证hwndNextViewer为非NULL的值。hwndNextViewer的值为NULL时,执行这段程序代码的程序就是链中最后一个浏览器,而这是不可能的。 当程序快结束时,如果它仍然在剪贴簿浏览器链中,则必须从链中删除它。您可以在处理WM_DESTROY消息时呼叫ChangeClipboardChain来完成这项工作。
case WM_DESTROY :
ChangeClipboardChain (hwnd, hwndNextViewer) ;
PostQuitMessage (0) ;
return 0 ;
Windows还有一个允许程序获得第一个剪贴簿浏览器窗口句柄的函数: hwndViewer = GetClipboardViewer () ;
一般来说不需要这个函数。如果没有目前的剪贴簿浏览器,则传回值为NULL。
下面是一个说明剪贴簿浏览器链如何工作的例子。当Windows刚启动时,目前剪贴簿浏览器是NULL: 剪贴簿浏览器:NULL
一个具有hwnd1窗口句柄的程序呼叫SetClipboardViewer。这个函数传回的NULL成为这个程序中的hwndNextViewer值: 目前剪贴簿浏览器:hwnd1
hwnd1的下一个浏览器:NULL 第二个具有hwnd2窗口句柄的程序呼叫SetClipboardViewer ,并传回hwnd1:
目前的剪贴簿浏览器:hwnd2 hwnd2的下一个浏览器:hwnd1
hwnd1的下一个浏览器:NULL 每三个程序(hwnd3)和第四个程序(hwnd4) 也呼叫SetClipboardViewer ,并且传回hwnd2和hwnd3:
目前的剪贴簿浏览器:hwnd4 hwnd4的下一个浏览器:hwnd3
hwnd3的下一个浏览器:hwnd2 hwnd2的下一个浏览器:hwnd1
hwnd1的下一个浏览器:NULL 当剪贴簿的内容发生变化时,Windows发送一个WM_DRAWCLIPBOARD消息给hwnd4,hwnd4发送消息给hwnd3,hwnd3发送消息给hwnd2,hwnd2发送消息给hwnd1,hwnd1传回。
2016-02-23
现在hwnd2决定通过下列呼叫从链中删除自己:
ChangeClipboardChain (hwnd2, hwnd1) ; Windows将wParam等于hwnd2、lParam等于hwnd1的WM_CHANGECBCHAIN消息发送给hwnd4。由于hwnd4的下一个测览器是hwnd3,所以hwnd4把这个消息传给hwnd3。现在hwnd3注意到wParam等于它的下一个测览器(hwnd2),所以将下一个浏览器设定为lParam (hwnd1)并且传回。这样工作就完成了。现在剪贴簿浏览器链如下:
目前剪贴簿浏览器:hwnd4 hwnd4的下一个浏览器:hwnd3
hwnd3的下一个浏览器:hwnd1 hwnd1的下一个浏览器:NULL
2016-02-23
一个简单的剪贴簿浏览器
剪贴簿浏览器不一定要像Windows所提供的那样完善,例如,剪贴簿浏览器可以只显示一种剪贴簿数据格式。程序12-2中所示的CLIPVIEW程序是一种只能显示CF_TEXT格式的剪贴簿浏览器。
程序12-2 CLIPVIEW
CLIPVIEW.C
/*-------------------------------------------------------------------------
CLIPVIEW.C --Simple Clipboard Viewer
(c) Charles Petzold, 1998
--------------------------------------------------------------------------*/
#include <windows.h>
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;
int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
{
static TCHAR szAppName[] = TEXT ("ClipView") ;
HWND hwnd ;
MSG msg ;
WNDCLASS wndclass ;

wndclass.style = CS_HREDRAW | CS_VREDRAW ;
wndclass.lpfnWndProc = WndProc ;
wndclass.cbClsExtra = 0 ;
wndclass.cbWndExtra = 0 ;
wndclass.hInstance = hInstance ;
wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ;
wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ;
wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;
wndclass.lpszMenuName = NULL ;
wndclass.lpszClassName = szAppName ;

if (!RegisterClass (&wndclass))
{
MessageBox ( NULL, TEXT ("This program requires Windows NT!"),
szAppName, MB_ICONERROR) ;
return 0 ;
}

hwnd = CreateWindow (szAppName,
TEXT ("Simple Clipboard Viewer (Text Only)"),
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT,
NULL, NULL, hInstance, NULL) ;

ShowWindow (hwnd, iCmdShow) ;
UpdateWindow (hwnd) ;

while (GetMessage (&msg, NULL, 0, 0))
{
TranslateMessage (&msg) ;
DispatchMessage (&msg) ;
}
return msg.wParam ;
}
LRESULT CALLBACK WndProc ( HWND hwnd, UINT message, WPARAM wParam,LPARAM lParam)
{
static HWND hwndNextViewer ;
HGLOBAL hGlobal ;
HDC hdc ;
PTSTR pGlobal ;
PAINTSTRUCT ps ;
RECT rect ;
2016-02-23
switch (message)
{
case WM_CREATE:
hwndNextViewer = SetClipboardViewer (hwnd) ;
return 0 ;

case WM_CHANGECBCHAIN:
if ((HWND) wParam == hwndNextViewer)
hwndNextViewer = (HWND) lParam ;

else if(hwndNextViewer)
SendMessage (hwndNextViewer, message, wParam, lParam) ;

return 0 ;
case WM_DRAWCLIPBOARD:
if (hwndNextViewer)
SendMessage (hwndNextViewer, message, wParam, lParam) ;

InvalidateRect (hwnd, NULL, TRUE) ;
return 0 ;

case WM_PAINT:
hdc = BeginPaint (hwnd, &ps) ;
GetClientRect (hwnd, &rect) ;
OpenClipboard (hwnd) ;

#ifdef UNICODE
hGlobal = GetClipboardData (CF_UNICODETEXT) ;
#else
hGlobal = GetClipboardData (CF_TEXT) ;
#endif
if (hGlobal != NULL)
{
pGlobal = (PTSTR) GlobalLock (hGlobal) ;
DrawText (hdc, pGlobal, -1, &rect, DT_EXPANDTABS) ;
GlobalUnlock (hGlobal) ;
}

CloseClipboard () ;
EndPaint (hwnd, &ps) ;
return 0 ;

case WM_DESTROY:
ChangeClipboardChain (hwnd, hwndNextViewer) ;
PostQuitMessage (0) ;
return 0 ;
}
return DefWindowProc (hwnd, message, wParam, lParam) ;
}
CLIPVIEW依上面所讨论的方法来处理WM_CREATE、WM_CHANGECBCHAIN、WM_DRAWCLIPBOARD和WM_DESTROY消息。WM_PAINT消息处理打开剪贴簿,并用CF_TEXT格式呼叫GetClipboardData。如果函数传回一个整体内存句柄,那么CLIPVIEW将锁定它,并用DrawText在显示区域显示文字。
处理标准格式(如Windows提供的那个剪贴簿一样)以外的数据格式的剪贴簿浏览器还需要完成一些其它工作,比如显示剪贴簿中目前所有数据格式的名称。使用者可以通过呼叫EnumClipboardFormats并使用GetClipboardFormatName得到非标准数据格式名称来完成这项工作。使用CF_OWNERDISPLAY数据格式的剪贴簿浏览器必须把下面四个消息送往剪贴簿数据的拥有者以显示该资料: WM_PAINTCLIPBOARD
WM_SIZECLIPBOARDWM_VSCROLLCLIPBOARD WM_HSCROLLCLIPBOARD
如果您想编写这样的剪贴簿浏览器,那么必须使用GetClipboardOwner获得剪贴簿所有者的窗口句柄,并当您需要修改剪贴簿的显示区域时,将这些消息发送给该窗口。
多看笔记 来自多看阅读 for Android
duokanbookid:ke57d4dcg67cgf2c0cd3017ge53b8cbb

《13》的笔记

13

2016-03-06
当应用程序要使用打印机时,它首先使用CreateDC或PrintDlg来取得指向打印机设备内容的句柄,于是使得打印机设备驱动程序动态链接库模块被加载到内存(如果还没有加载内存的话)并自己进行初始化。然后,程序呼叫StartDoc函数,通知说一个新文件开始了。StartDoc函数是由GDI模块来处理的,GDI模块呼叫打印机设备驱动程序中的Control函数告诉设备驱动程序准备进行打印。
2016-03-06
您也可以使用该函数来取得打印机设备内容句柄。然而,对打印机设备内容,CreateDC的一般语法为:
hdc = CreateDC (NULL, szDeviceName, NULL, pInitializationData) ;
pInitializationData参数一般被设为NULL。szDeviceName参数指向一个字符串,以告诉Windows打印机设备的名称。在设定设备名称之前,您必须知道有哪些打印机可用。
2016-03-06
如果函数成功取得了设备内容句柄,它就通过呼叫GetDeviceCaps来确定页面的水平和垂直大小(以图素为单位)。
xPage = GetDeviceCaps (hdcPrn, HORZRES) ;
yPage = GetDeviceCaps (hdcPrn, VERTRES) ;
这不是纸的全部大小,只是纸的可打印区域。
2016-03-06
EnableWindow (hwnd, FALSE) ;
它可以禁止键盘和鼠标的输入进入消息队列。因此在打印程序中,使用者不能对程序做任何工作。当打印完成时,应重新允许窗口接受输入:
EnableWindow (hwnd, TRUE) ;
多看笔记 来自多看阅读 for Android
duokanbookid:o64bbed6dbf66dg3d231b79gdg3gddb7

《14》的笔记

14

2016-03-07
位图是一个二维的位数组,它与图像的图素一一对应。当现实世界的图像被扫描成位图以后,图像被分割成网格,并以图素作为取样单位。在位图中的每个图素值指明了一个单位网格内图像的平均颜色。单色位图每个图素只需要一位,灰色或彩色位图中每个图素需要多个位。
位图代表了Windows程序内储存图像信息的两种方法之一。储存图像信息的另一种形式是metafile
2016-03-07
位图入门
位图和metafile在计算机图形处理世界中都占有一席之地。位图经常用来表示来自真实世界的复杂图像,例如数字化的照片或者视讯图像。Metafile更适合于描述由人或者机器产生的图像,比如建筑蓝图。位图和metafile都能存于内存或作为文件存于磁盘上,并且都能通过剪贴簿在Windows应用程序之间传输。
位图和metafile的区别在于位映像图像和向量图像之间的差别。位映像图像用离散的图素来处理输出设备;而向量图像用笛卡尔坐标系统来处理输出设备,其线条和填充对象能被个别拖移。现在大多数的图像输出设备是位映像设备,这包括视讯显示、点阵打印机、激光打印机和喷墨打印机。而笔式绘图机则是向量输出设备。
2016-03-07
位图有两个主要的缺点。第一个问题是容易受设备依赖性的影响。最明显的就是对颜色的依赖性,在单色设备上显示彩色位图的效果总是不能令人满意的。另一个问题是位图经常暗示了特定的显示分辨率和图像纵横比。尽管位图能被拉伸和缩小,但是这样的处理通常包括复制或删除图素的某些行和列,这样会破坏图像的大小。而metafile在放大缩小后仍然能保持图形样貌不受破坏。
位图的第二个缺点是需要很大的储存空间。例如,描述完整的640×480图素,16色的视频图形数组(VGA:Video Graphics Array)屏幕的一幅位图需要大于150 KB的空间;一幅1024×768,并且每个图素为24位颜色的图像则需要大于2 MB的空间。Metafile需要通常比位图来得少的空间。位图的储存空间由图像的大小及其包含的颜色决定,而metafile的储存空间则由图像的复杂程度和它所包含的GDI指令数决定。 然而,位图优于metafile之处在于速度。将位图复制给视讯显示器通常比复制基本图形文件的速度要快。最近几年,压缩技术允许压缩位图的文件大小,以使它能有效地通过电话线传输并广泛地用于Internet的网页上。
2016-03-07
DDB是Windows图形设备接口的图形对象之一(其中还包括绘图笔、画刷、字体、metafile和调色盘)。这些图形对象储存在GDI模块内部,由应用程序软件以句柄数字的方式引用。您可以将DDB句柄储存在一个HBITMAP(「handle to a bitmap:位图句柄」)型态的变量中,例如:
HBITMAP hBitmap ;
然后通过呼叫DDB建立的一个函数来获得句柄,例如:CreateBitmap。这些函数配置并初始化GDI内存中的一些内存来储存关于位图的信息,以及实际位图位的信息。应用程序不能直接存取这段内存。位图与设备内容无关。当程序使用完位图以后,就要清除这段内存: DeleteObject (hBitmap) ;
如果程序执行时您使用了DDB,那么程序终止时,您可以完成上面的操作。
CreateBitmap函数用法如下: hBitmap = CreateBitmap (cx, cy, cPlanes, cBitsPixel, bits) ;
前两个参数是位图的宽度和高度(以图素为单位),第三个参数是颜色面的数目,第四个参数是每图素的位数,第五个参数是指向一个以特定颜色格式存放的位数组的指针,数组内存放有用来初始化该DDB的图像。如果您不想用一张现有的图像来初始化DDB,可以将最后一个参数设为NULL。以后您还是可以设定该DDB内图素的内容。
2016-03-07
对于第二种情况,您可以用CreateCompatibleBitmap来简化问题:
hBitmap = CreateCompatibleBitmap (hdc, cx, cy) ;
此函数建立了一个与设备兼容的位图,此设备的设备内容句柄由第一个参数给出。CreateCompatibleBitmap用设备内容句柄来获得GetDeviceCaps信息,然后将此信息传递给CreateBitmap。除了与实际的设备内容有相同的内存组织之外,DDB与设备内容没有其它联系。 CreateDiscardableBitmap函数与CreateCompatibleBitmap的参数相同,并且功能上相同。在早期的Windows版本中,CreateDiscardableBitmap建立的位图可以在内存减少时由Windows将其从内存中清除,然后程序再重建位图数据。
第三个位图建立函数是CreateBitmapIndirect: hBitmap CreateBitmapIndirect (&bitmap) ;
其中bitmap是BITMAP型态的结构。BITMAP结构定义如下:
typedef struct _tagBITMAP
{
LONG bmType ; // set to 0
LONG bmWidth ; // width in pixels
LONG bmHeight ; // height in pixels
LONG bmWidthBytes ; // width of row in bytes
WORD bmPlanes ; // number of color planes
WORD bmBitsPixel ; // number of bits per pixel
LPVOIDbmBits ; // pointer to pixel bits
}
BITMAP, * PBITMAP ;
在呼叫CreateBitmapIndirect函数时,您不需要设定bmWidthBytes字段。Windows将为您计算,您也可以将bmBits字段设定为NULL,或者设定为初始化位图时用的图素位地址。
2016-03-07
载入位图资源
除了各种各样的位图建立函数以外,获得GDI位图对象句柄的另一个方法就是呼叫LoadBitmap函数。使用此函数,您不必担心位图格式。在程序中,您只需简单地按资源来建立位图,这与建立图标或者鼠标光标的方法类似。LoadBitmap函数的语法与LoadIcon和LoadCursor相同:
hBitmap = LoadBitmap (hInstance, szBitmapName) ;
如果想加载系统位图,那么将第一个参数设为NULL。这些不同的位图是Windows视觉接口(例如关闭方块和勾选标记)的一小部分,它们的标识符以字母OBM开始。如果位图与整数标识符而不是与名称有联系,那么第二个参数就可以使用MAKEINTRESOURCE宏。由LoadBitmap加载的所有位图最终应用DeleteObject清除。 如果位图资源是单色的,那么从LoadBitmap传回的句柄将指向一个单色的位图对象。如果位图资源不是单色,那么从LoadBitmap传回的句柄将指向一个GDI位图对象,该对象与执行程序的视讯显示器有相同的色彩组织。因此,位图始终与视讯显示器兼容,并且总是选进与视讯显示器兼容的内存设备内容中。采用LoadBitmap呼叫后,就不用担心任何色彩转换的问题了。在下一章中,我们就知道LoadBitmap的具体运作方式了。
2016-03-07
用CreateBitmapIndirect来建立位图需要下面两条叙述:
bitmap.bmBits = (PSTR) bits ;
hBitmap = CreateBitmapIndirect (&bitmap) ;
另一种方法是: hBitmap = CreateBitmapIndirect (&bitmap) ;
SetBitmapBits (hBitmap, sizeof bits, bits) ;
您也可以用一道叙述来建立位图:
hBitmap = CreateBitmap (20, 5, 1, 1, bits) ;
在程序14-4显示的BRICKS2程序利用此技术直接建立了砖块位图,而没有使用资源。
多看笔记 来自多看阅读 for Android
duokanbookid:p029gb97bf152bdbfd9g307ef7630234

《16》的笔记

16

DIBHELP.C (第一部分)
2016-03-08
使用调色盘管理器的第一步就是呼叫CreatePalette函数来建立逻辑调色盘。逻辑调色盘包含程序所需要的全部颜色-即236种颜色。
2016-03-08
GetDeviceCaps (hdc, SIZEPALETTE)
将传回在显示卡上调色盘表的总尺寸。这与同时显示的颜色总数相同。因为调色盘管理器只用于每图素8位的视讯显示模式,所以此值将是256。
函数呼叫 GetDeviceCaps (hdc, NUMRESERVED)
传回在调色盘表中的颜色数,该表是设备驱动程序为系统保留的,此值是20。不呼叫调色盘管理器,这些只是Windows应用程序在256色显示模式下使用的纯色。要使用其余的236种颜色,程序必须使用调色盘管理器函数。
一个附加项目也可用: GetDeviceCaps (hdc, COLORRES)
此值告诉您加载到硬件调色盘表的RGB颜色值分辨率(以位计)。这些是进入数字模拟转换器的位。某些视讯显示卡只使用6位ADC,所以该值是18。其余使用8位的ADC,所以值是24。
2016-03-08
程序可以通过呼叫下面的函数来获得系统调色盘中的某些或全部的RGB项目:
GetSystemPaletteEntries (hdc, uStart, uNum, &pe) ;
只有显示卡模式支持调色盘操作时,该函数才能执行。第二个和第三个参数是无正负号整数,显示第一个调色盘项目的索引和调色盘项目数。最后一个参数是指向PALETTEENTRY型态的指针。 您可以在几种情况下使用该函数。程序可以定义PALETTEENTRY结构如下:
PALETTEENTRY pe ;
然后可按下面的方法多次呼叫GetSystemPaletteEntries: GetSystemPaletteEntries (hdc, i, 1, &pe) ;
2016-03-09
CreateAllPurposePalette函数似乎是用247个项目来建立逻辑调色盘,它超出了系统调色盘中允许程序正常存取的236个项目。的确如此,不过这样做很方便。这些项目中有15个被复制或者映射到20种标准的保留颜色中。
CreateAllPurposePalette从建立31种灰阶开始,即0x00、0x09、0x11、0x1A、0x22、0x2B、0x33、0x3C、0x44、0x4D、0x55、0x5E、0x66、0x6F、0x77、0x80、0x88、0x91、0x99、0xA2、0xAA、0xB3、0xBB、0xC4、0xCC、0xD5、0xDD、0xE6、0xEE、0xF9和0xFF的红色、绿色和蓝色值。注意,第一个、最后一个和中间的项目都在标准的20种保留颜色中。下一个函数用红色、绿色和蓝色值的所有组合建立了颜色0x00、0x33、0x66、0x99、0xCC和0xFF。这样就共有216种颜色,但是其中8种颜色复制了标准的20种保留颜色,而另外4个复制了前面计算的灰阶。如果将PALETTEENTRY结构的peFlags字段设为0,则Windows将不把复制的项目放进系统调色盘。
2016-03-09
中间色调色盘
Windows API包括一个通用调色盘,程序可以通过呼叫CreateHalftonePalette来获得该调色盘
DIBHELP.C (第三部分)
2016-03-09
文件载入和储存
DIBBLE可以在响应IDM_FILE_LOAD和IDM_FILE_SAVE的WM_COMMAND消息处理过程中加载DIB文件并储存这些文件。在处理这些消息处理期间,DIBBLE通过分别呼叫GetOpenFileName和GetSaveFileName来启动公用文件对话框。
对于「File」、「Save」菜单命令,DIBBLE只需要呼叫DibFileSave。对于「File」、「Open」菜单命令,DIBBLE必须首先删除前面的HDIB、调色盘和位图对象。它透过发送一个WM_USER_DELETEDIB消息来完成这件事,此消息通过呼叫DibDelete和DeleteObject来处理。然后DIBBLE呼叫DIBHELP中的DibFileLoad函数,发送WM_USER_SETSCROLLS和WM_USER_CREATEPAL消息来重新设定滚动条并建立调色盘。WM_USER_CREATEPAL消息也位于程序从DIB区块建立的新的DDB位置。
2016-03-09
DIBBLE中的「Edit」菜单中除了常见的「Cut」、「Copy」、「Paste」和「Delete」选项之外,还包括两个附加项-「Flip」和「Rotate」。「Flip」选项使位图绕水平轴翻转-即上下颠倒翻转。「Rotate」选项使位图顺时针旋转90度。这两个函数都需要透过将它们从一个DIB复制到另一个来存取所有的DIB图素(因为这两个函数不需要建立新的调色盘,所以不删除和重新建立调色盘)。
「Flip」菜单选项使用DibFlipHorizontal函数,此函数也位于DIBBLE.C文件。此函数呼叫DibCopy来获得DIB精确的副本。然后,进入将原DIB中的图素复制到新DIB的循环,但是复制这些图素是为了上下翻转图像。注意,此函数呼叫DibGetPixel和DibSetPixel。这些是DIBHELP.C中的通用(但不像我们所希望的那么快)函数。
2016-03-09
「Popularity」算法
「Popularity」算法是256色调色盘问题相当明显的解决方法。您要做的就是走遍位图中的所有图素,并找出256种最普通的RGB颜色值。这些就是您在调色盘中使用的值。DIBPAL的DibPalPopularity函数中实作了这种算法。
不过,如果每种颜色都使用整个24位,而且假设需要用整数来计算所有的颜色,那么数组将占据64MB内存。另外,您可以发现位图中实际上没有(或很少)重复的24位图素值,这样就没有所谓常见的颜色了。 要解决这个问题,您可以只使用每个红色、绿色和蓝色值中最重要的n位-例如,6位而不是8位。因为大多数的彩色扫描仪和视讯显示卡都只有6位的分辨率,所以这样规定更有意义。这将数组减少到大小更合理的256KB或1MB。只使用5位能将可用的颜色总数减少到32,768。通常,使用5位要比6位的性能更好。对此,您可以用DIBBLE和一些图像颜色来自己检验。
2016-03-09
「Median Cut」算法
DIBPAL.C中的DibPalMedianCut函数执行Paul Heckbert的Median Cut算法。此算法在概念上相当简单,但在程序代码中实作要比Popularity算法更困难,它适合递归函数。
画出RGB颜色立方体。图像中的每个图素都是此立方体中的一个点。一些点可能代表图像中的多个图素。找出包括图像中所有图素的立体方块,找出此方块的最大尺寸,并将方块分成两个,每个方块都包括相同数量的图素。对于这两个方块,执行相同的操作。现在您就有4个方块,将这4个方块分成8个,然后再分成16个、32个、64个、128个和256个。 现在您有256个方块,每个方块都包括相同数量的图素。取每个方块中图素RGB颜色值的平均值,并将结果用于调色盘。
实际上,这些方块通常包含图素的数量并不相同。例如,通常包括单个点的方块会有更多的图素。这发生在黑色和白色上。有时,一些方块里头根本没有图素。如果这样,您就可以省下更多的方块,但是我决定不这样做。 另一种最佳化调色盘的技术称为「octree quantization」,此技术由Jeff Prosise提出,并于1996年8月发表在《Microsoft Systems Journal》上(包含在MSDN的CD中)。
多看笔记 来自多看阅读 for Android
duokanbookid:td00d34577e5233ffdcg3bcc780c686c

《17》的笔记

17

2016-03-09
TrueType使程序写作者和使用者以灵活的方式处理文字的能力大幅增强。TrueType是轮廓字体技术,由Apple Computer公司和Microsoft公司开发,并被许多字体制造商支持。由于TrueType字体能够连续缩放,并能应用于视讯显示器和打印机,现在能够在Windows下实作真的WYSIWYG(what you see is what you get:所见即所得)。TrueType也便于制作「奇妙」字体,例如旋转的字母、内部填充图案的字母或将它们用于剪裁区域,在本章我将展示它们。
2016-03-09
文字输出函数
我已经在许多范例程序中使用过最常用的文字输出函数:
TextOut (hdc, xStart, yStart, pString, iCount) ;
参数xStart和yStart是逻辑坐标上字符串的起始点。通常,这是Windows开始绘制的第一个字母的左上角。TextOut需要指向字符串的指针和字符串的长度,这个函数不能识别以NULL终止的字符串。 TextOut函数的xStart和yStart参数的含义可由SetTextAlign函数改变。TA_LEFT、TA_RIGHT和TA_CENTER旗标影响使用xStart在水平方向上定位字符串的方式。默认值是TA_LEFT。如果在SetTextAlign函数中指定了TA_RIGHT,则后面的TextOut呼叫会将字符串的最后一个字符定位于xStart,如果指定了TA_CENTER,则字符串的中心位于xStart。
类似地,TA_TOP、TA_BOTTOM和TA_BASELINE旗标影响字符串的垂直位置。TA_TOP是默认值,它意味着字符串的字母顶端位于yStart,使用TA_BOTTOM意味着字符串位于yStart之上。可以使用TA_BASELINE定位字符串,使基准线位于yStart。基准线是如小写字母p、q、y等字母下部的线。 如果您使用TA_UPDATECP旗标呼叫SetTextAlign,Windows就会忽略TextOut的xStart和yStart参数,而使用由MoveToEx、LineTo或更改目前位置的另一个函数设定的位置。TA_UPDATECP旗标也使TextOut函数将目前位置更新为字符串的结尾(TA_LEFT)或字符串的开头(TA_RIGHT)。这在使用多个TextOut呼叫显示一行文字时非常有用。当水平位置是TA_CENTER时,在TextOut呼叫后,目前位置不变。
2016-03-09
显示几列文字时,对每一列都需要呼叫一个TextOut,其替代函数是TabbedTextOut函数:
TabbedTextOut ( hdc, xStart, yStart, pString, iCount,
iNumTabs, piTabStops, xTabOrigin) ;
如果文字字符串中含有嵌入的制表符(‘\t’或0x09),则TabbedTextOut会根据传递给它的整数数组将制表符扩展为空格。 TabbedTextOut的前五个参数与TextOut相同,第六个参数是跳位间隔数,第七个是以图素为单位的跳位间隔数组。例如,如果平均字符宽度是8个图素,而您希望每5个字符加一个跳位间隔,则这个数组将包含40、80、120,按递增顺序依此类推。
如果第六个和第七个参数是0或NULL,则跳位间隔按每八个平均字符宽度设定。如果第六个参数是1,则第七个参数指向一个整数,表示跳位间隔重复增大的倍数(例如,如果第六个参数是1,并且第七个参数指向值为30的变量,则跳位间隔设定在30、60、90…图素处)。最后一个参数给出了从跳位间隔开始测量的逻辑x坐标,它与字符串的起始位置可能相同也可能不同。
2016-03-09
另一个进阶的文字输出函数是ExtTextOut(前缀Ext表示它是扩展的):
ExtTextOut (hdc, xStart, yStart, iOptions, &rect,
pString, iCount, pxDistance) ;
第五个参数是指向矩形结构的指针,在iOptions设定为ETO_CLIPPED时,该结构为剪裁矩形,在iOptions设定为ETO_OPAQUE时,该结构为用目前背景色填充的背景矩形。这两种选择您可以都采用,也可以都不采用。 最后一个参数是整数数组,它指定了字符串中连续字符的间隔。程序可以使用它使字符间距变窄或变宽,因为有时需要在较窄的列中调整单个文字。该参数可以设定为NULL来使用内定的字符间距。
用于写文字的高级函数是DrawText,我们第一次遇到它是在第三章讨论HELLOWIN程序时,它不指定坐标的起始位置,而是通过RECT结构型态定义希望显示文字的区域: DrawText (hdc, pString, iCount, &rect, iFormat) ;
2016-03-09
和其它文字输出函数一样,DrawText需要指向字符串的指针和字符串的长度。然而,如果在DrawText中使用以NULL结尾的字符串,就可以将iCount设定为-1,Windows会自动计算字符串的长度。
当iFormat设定为0时,Windows会将文字解释为一系列由carriage return字符(‘\r’或0x0D)或linefeed字符(‘\n’或0x0A)分隔的行。文字从矩形的左上角开始,carriage return字符或linefeed字符被解释为换行字符,因此Windows会结束目前行而开始新的一行。新的一行从矩形的左侧开始,在上一行的下面空开一个字符的高度(没有外部间隔)。包含字母的任何文字都应该显示在所剪裁矩形底部的右边或下边。 您可以使用iFormat参数更改DrawText的内定操作,iFormat由一个或多个旗标组成。DT_LEFT旗标(默认值)指定了左对齐的行,DT_RIGHT指定了向右对齐的行,而DT_CENTER指定了位于矩形左边和右边中间的行。因为DT_LEFT的值是0,所以如果只需要左对齐,就不需要包含标识符。
如果您不希望将carriage return字符或linefeed字符解释为换行字符,则可以包括标识符DT_SINGLELINE。然后,Windows会把carriage return字符和linefeed字符解释为可显示的字符,而不是控制字符。在使用DT_SINGLELINE时,还可以将行指定为位于矩形的顶端(DT_TOP)、底端(DT_BOTTOM)或者中间(DT_VCETER,V表示垂直)。 在显示多行文字时,Windows通常只在carriage return字符或linefeed字符处换行。然而,如果行的长度超出了矩形的宽度,则可以使用DT_WORDBREAK旗标,它使Windows在行内字的末尾换行。对于单行或多行文字的显示,Windows会把超出矩形的文字部分截去,可以使用DT_NOCLIP跳过这个操作,这个旗标还加快了函数的速度。当Windows确定多行文字的行距时,它通常使用不带外部间距的字符高度,如果您想在行距中加入外部间距,就可以使用旗标DT_EXTERNALLEADING。
如果文字中包含制表符(‘\t’或0x09),则您需要包括旗标DT_EXPANDTABS。在内定情况下,跳位间隔设定于每八个字符的位置。通过使用旗标DT_TABSTOP,您可以指定不同的跳位间隔,在这种情况下,iFormat的高字节包含了每个新跳位间隔的字符位置数值。不过我建议您避免使用DT_TABSTOP,因为iFormat的高字节也用于其它旗标。 DT_TABSTOP旗标存在的问题,可以由新的函数DrawTextEx来解决,它含有一个额外的参数:
DrawTextEx (hdc, pString, iCount, &rect, iFormat, &drawtextparams) ;
最后一个参数是指向DRAWTEXTPARAMS结构的指针,它的定义如下: typedef struct tagDRAWTEXTPARAMS
{
UINT cbSize ; // size of structure
int iTabLength ; // size of each tab stop
int iLeftMargin ; // left margin
int iRightMargin ; // right margin
UINT uiLengthDrawn ; // receives number of characters processed
} DRAWTEXTPARAMS, * LPDRAWTEXTPARAMS ;
中间的三个字段是以平均字符的增量为单位的。
2016-03-11
在内定的设备内容下,文字颜色是黑色,但您可以用下面的叙述进行更改:
SetTextColor (hdc, rgbColor) ;
使用画笔的颜色和画刷的颜色,Windows把rgbColor的值转换为纯色,您可以通过呼叫GetTextColor取得目前文字的颜色。
2016-03-11
Windows在矩形的背景区域中显示文字,它可能根据背景模式的设定进行着色,也可能不这样做。您可以使用
SetBkMode (hdc, iMode) ;
更改背景模式,其中iMode的值为OPAQUE或TRANSPARENT。内定的背景模式为OPAQUE,它表示Windows使用背景颜色来填充矩形的背景。您可以使用 SetBkColor (hdc, rgbColor) ;
来改变背景颜色。rgbColor的值是转换为纯色的值。内定背景色是白色。
如果两行文字靠得太近,其中一个的背景矩形就会遮盖另一个的文字。由于这种原因,我通常希望内定的背景模式是TRANSPARENT。在背景模式为TRANSPARENT的情况下,Windows会忽略背景色,也不对矩形背景区域着色。Windows也使用背景模式和背景色对点和虚线之间的空隙及阴影刷中阴影间的区域着色,
2016-03-11
许多Windows程序将WHITE_BRUSH指定为Windows用于擦出窗口背景的画刷,画刷在窗口类别结构中指定。然而,您可能希望您程序的窗口背景与使用者在「控制台」中设定的系统颜色保持一致,在这种情况下,可以在WNDCLASS结构中指定背景颜色的这种方式:
wndclass.hbrBackground = COLOR_WINDOW + 1 ;
当您想要在显示区域书写文字时,可以使用目前系统颜色设定文字色和背景色: SetTextColor (hdc, GetSysColor (COLOR_WINDOWTEXT)) ;
SetBkColor (hdc, GetSysColor (COLOR_WINDOW)) ;
完成这些以后,就可以使您的程序随系统颜色的更改而变化:
case WM_SYSCOLORCHANGE :
InvalidateRect (hwnd, NULL, TRUE) ;
break ;
另一个影响文字的设备内容属性是字符间距。它的默认值是0,表示Windows不在字符之间添加任何空间,但您可以使用以下函数插入空间: SetTextCharacterExtra (hdc, iExtra) ;
参数iExtra是逻辑单位,Windows将其转换为最接近的图素,它可以是0。如果您将iExtra取为负值(希望将字符紧紧压在一起),Windows会接受这个数值的绝对值─也就是说,您不能使iExtra的值小于0。您可以通过呼叫GetTextCharacterExtra取得目前的字符间距,Windows在传回该值前会将图素间距转换为逻辑单位。
使用备用字体
当您呼叫TextOut、TabbedTextOut、ExtTextOut、DrawText或DrawTextEx书写文字时,Windows使用设备内容中目前选择的字体。字体定义了特定的字样和大小。以不同字体显示文字的最简单方法是使用Windows提供的备用字体,然而,它的范围是很有限的。 您可以呼叫下面的函数取得某种备用字体的句柄:
hFont = GetStockObject (iFont) ;
其中,iFont是几个标识符之一。然后,您就可以将该字体选入设备内容: SelectObject (hdc, hFont) ;
这些您也可以只用一步完成:
SelectObject (hdc, GetStockObject (iFont)) ;
在内定的设备内容中选择的字体称为系统字体,能够由GetStockObject的SYSTEM_FONT参数识别。这是调和的ANSI字符集字体。在GetStockObject中指定SYSTEM_FIXED_FONT(我在本书的前面几个程序中应用过),可以获得等宽字体的句柄,这一字体与Windows 3.0以前的系统字体兼容。在您希望所有的字体都具有相同宽度时,这是很方便的。 备用字体OEM_FIXED_FONT也称为终端机字体,是Windows在MS-DOS命令提示窗口中使用的字体,它包括与原始IBM-PC扩展字符集兼容的字符集。Windows在窗口标题列、菜单和对话框的文字中使用DEFULT_GUI_FONT。
当您将新字体选入设备内容时,必须使用GetTextMetrics计算字符的高度和平均宽度。如果选择了调和字体,那么一定要注意,字符的平均宽度只是个平均值,某些字符会比它宽或比它窄。在本章的后面,您会了解到确定由不同宽度字符所组成的字符串总宽度的方法。 尽管GetStockObject确实提供了存取不同字体的最简单方式,但是您还不能充分控件Windows所提供的字体。不久,您会看到指定字体字样和大小的方法。
2016-03-11
字体型态
Windows支持两大类字体,即所谓的「GDI字体」和「设备字体」。GDI字体储存在硬盘的文件中,而设备字体是输出设备本来就有的。例如,通常打印机都具有内建的设备字体集。
GDI字体有三种样式:点阵字体,笔划字体和TrueType字体。 点阵字体的每个字符都以位图图素图案的形式储存,每种点阵字体都有特定的纵横比和字符大小。Windows通过简单地复制图素的行或列就可以由GDI点阵字体产生更大的字符。然而,只能以整数倍放大字体,并且不能超过一定的限度。由于这种原因,GDI点阵字体又称为「不可缩放的」字体。它们不能随意地放大或缩小。点阵字体的主要优点是显示性能(显示速度很快)和可读性(因为是手工设计的,所以尽可能清晰)。
字体是通过字体名称识别的,点阵字体的字体名称为: System (用于SYSTEM_FONT)
FixedSys (用于SYSTEM_FIXED_FONT) Terminal (用于OEM_FIXED_FONT)
Courier MS Serif
MS Sans Serif(用于DEFAULT_GUI_FONT)
2016-03-11
Small Fonts
每个点阵字体只有几种大小(不超过6种)。Courier字体是定宽字体,外形与用打字机打出的字体相似。「Serif」指字体字母笔划在结束时拐个小弯。「sans serif」字体不是serif类的字体。在Windows的早期版本中,MS(Microsoft)Serif和MS Sans Serif字体被称为Tms Rmn(指它与Times Roman相似)和Helv(与Helvetica相似)。Small Fonts是专为显示小字设计的。 在Windows3.1以前,除了GDI字体外,Windows所提供的字体只有笔划字体。笔划字体是以「连结点」的方式定义的一系列线段,笔划字体可以连续地缩放,这意味着同样的字体可以用于具有任何分辨率的图形输出设备,并且字体可以放大或缩小到任意尺寸。不过,它的性能不好,小字体的可读性也很糟,而大字体由于笔划是单根直线而显得很单薄。笔划字体有时也称为绘图机字体,因为它们特别适合于绘图机,但是不适合于别的场合。笔划字体的字样有:Modern、Roman和Script。
对于GDI点阵字体和GDI笔划字体,Windows都可以「合成」粗体、斜体、加底线和加删除线,而不需要为每种属性另外储存字体。例如,对于斜体,Windows只需要将字符的上部向右移动就可以了。
2016-03-11
TrueType 字体
TrueType字体的单个字符是通过填充的直线和曲线的轮廓来定义的。Windows可以通过改变定义轮廓的坐标对TrueType字体进行缩放。
当程序开始使用特定大小的TrueType字体时,Windows「点阵化」字体。这就是说Windows使用TrueType字体文件中包括的「提示」对每个字符的连结直线和曲线的坐标进行缩放。这些提示可以补偿误差,避免合成的字符变得很难看(例如,在某些字体中,大写H的两竖应该一样宽,但盲目地缩放字体可能会导致其中一竖的图素比另一竖宽。有了提示就可以避免这些现象发生)。然后,每个字符的合成轮廓用于建立字符的位图,这些位图储存在内存以备将来使用。 最初,Windows使用了13种TrueType字体,它们的字体名称如下:
Courier New Courier New Bold
Courier New Italic Courier New Bold Italic
Times New Roman Times New Roman Bold
Times New Roman Italic Times New Roman Bold Italic
Arial Arial Bold
Arial Italic Arial Bold Italic
Symbol
2016-03-11
TEXTMETRIC结构的另一个字段是tmExternalLeading,词「间隔(leading)」来自排字工人在金属字块间插入的铅,它用于在两行文字之间产生空白。tmInternalLeading值与为发音符号保留的空间有关,tmExternalLeading表示字符的连续行之间所留的附加空间。程序写作者可以使用或忽略外部的间隔值。
2016-03-11
字体的点值可由tmHeight减tmInternalLeading得到。
2016-03-11
用LOGPIXELSX或LOGPIXELSY参数呼叫GetDeviceCaps来取得该设备分辨率。
2016-03-11
逻辑字体的建立和选择
您可以透过呼叫CreateFont或CreateFontIndirect来建立逻辑字体。CreateFontIndirect函数接受一个指向LOGFONT结构的指针,该结构有14个字段。CreateFont函数接受14个参数,它们与LOGFONT结构的14个字段形式相同。它们是仅有的两个建立逻辑字体的函数(我提到这一点,是因为Windows中有许多用于其它字体操作的函数)。因为很难记住14个字段,所以很少使用CreateFont。因此,我主要讨论CreateFontIndirect。
有三种基本的方式用于定义LOGFONT结构中的字段,以便呼叫CreateFontIndirect: 您可以简单地将LOGFONT结构的字段设定为所需的字体特征。在这种情况下,在呼叫SelectObject时,Windows使用「字体映像」算法从设备上有效的字体中选择与这些特征最匹配的字体。由于这依赖于视讯显示器和打印机上的有效字体,所以其结果可能与您的要求有相当大的差别。
2016-03-11
GetTextFace函数使程序能够确定目前选入设备内容的字体名称:
GetTextFace (hdc, sizeof (szFaceName) / sizeof (TCHAR), szFaceName) ;
详细的字体信息可以从GetTextMetrics中得到: GetTextMetrics (hdc, &textmetric) ;
其中,textmetric是TEXTMETRIC型态的变量,它具有20个字段。
2016-03-11
LOGFONT结构的前两个字段是逻辑单位,因此它们依赖于映像方式的目前设定:
lfHeight这是以逻辑单位表示的希望的字符高度。您可以将lfHeight设定0,以使用内定大小,或者根据字段代表的含义将其设定为正数或负数。如果将lfHeight设定为正数,就表示您希望该值表示含有内部间隔(不是外部间隔)的高度。实际上,所要求的字体行距为lfHeight。如果将lfHeight设定为负值,则Windows会将其绝对值作为与点值一致的字体高度。这是一个很重要的区别:如果想要特定点值的字体,可将点值转换为逻辑单位,并将lfHeight字段设定为该值的负数。如果lfHeight是正值,则TEXTMETRIC结构的tmHeight字段近似为该值(有时有微小的偏差,可能由于舍入误差所引起)。如果lfHeight是负值,则它粗略地与不包括tmInternalLeading字段的TEXTMETRIC结构的tmHeight字段相匹配。
2016-03-11
lfWidth是逻辑单位的字符期望宽度。在多数情况下,可以将此值设定为0,让Windows仅根据高度选择字体。使用非零值对点阵字体并不会起太大作用,但对于TrueType字体,您能轻松地用它来获得比正常字符更宽或更窄的字体。这个字段对应于TEXTMETRIC结构的tmAveCharWidth字段。要正确使用lfWidth字段,首先把带有lfWidth字段的LOGFONT结构设定为0,建立逻辑字体,将它选入设备内容,然后呼叫GetTextMetrics。得到tmAveCharWidth字段,可按比例调节其值的大小,然后使用所调节的lfWidth的tmAveCharWidth值建立第二种字体。
2016-03-11
lfEscapement这是从水平方向上逆时针测量的十分之几的角度。它指定在书写文字时字符串的连续字符放置的方式。
2016-03-11
GetTextFace得到的有效字体名称。
除了数值化的纵横比以外,Windows复制到TEXTMETRIC结构的所有大小值都以逻辑单位表示。TEXTMETRIC结构的字段如下: tmHeight逻辑单位的字符高度。它近似等于LOGFONT结构中指定的lfHeight字段的值,如果该值为正,它就代表行距,而非点值。如果LOGFONT结构的lfHeight字段为负,则tmHeight字段减tmInternalLeading字段应近似等于lfHeight字段的绝对值。
  tmAscent逻辑单位的基准线以上的字符垂直大小。
  tmDescent逻辑单位的基准线以下的字符垂直大小。
  tmInternalLeading包含在tmHeight值内的垂直大小,通常被一些大写字母上注音符号占据。同样,可以用tmHeight值减tmInternalLeading值来计算字体的点值。
  tmExternalLeading tmHeight以外的行距附加量,字体的设计者推荐用于隔开文字的连续行。
  tmAveCharWidth字体中小写字母的平均宽度。
  tmMaxCharWidth逻辑单位的字符最大宽度。对于定宽字体,这个值与tmAveCharWidth相同。
  tmWeight字体重量,范围从0到999。实际上,这个字段为400时是标准字体,700时是粗体。
  tmOverhangWindows在合成斜体或粗体时添加到点阵字体字符的额外宽度量(逻辑单位)。当点阵字体斜体化时,tmAveCharWidth值保持不变,因为斜体化的字符串与相同的正常字符串的总宽度相等。要为字体加粗,Windows必须稍微增加每个字符的宽度。对于粗体,tmAveCharWidth值小于tmOverhang值,等于没有加粗的相同字体的tmAveCharWidth值。
  tmDigitizedAspectX和tmDigitizedAspectY字体合适的纵横比。它们与使用LOGPIXELSX和LOGPIXELSY标识符从GetDeviceCaps得到的值相同。
  tmFirstChar字体中第一个字符的字符代码。
  tmLastChar字体中最后一个字符的字符代码。如果TEXTMETRIC结构通过呼叫GetTextMetricsW(函数的宽字符版本)获得,那么这个值可能大于255。
  tmDefaultCharWindows用于显示不在字体中的字符的字符代码,通常是矩形。
  tmBreakChar在调整文字时,Windows和您的程序用于确定单字断开的字符。如果您不用一些奇怪的东西(例如EBCDIC字体),它就是32-空格符。
  tmItalic对于斜体字为非零值。
  tmUnderlined对于底线字体为非零值。
  tmStruckOut对于删除线字体为非零值。
  tmPitchAndFamily低四位是表示字体某些特征的旗标,
2016-03-11
简单文字格式
对文字的最有用的一个函数是GetTextExtentPoint32(这个函数的名称显示了Windows早期版本的一些变化)。该函数根据设备内容中选入的目前字体得出字符串的宽度和高度:
GetTextExtentPoint32 (hdc, pString, iCount, &size) ;
逻辑单位的文字宽度和高度在SIZE结构的cx和cy字段中传回。我使用一行文字的例子,假定您把一种字体选入设备内容,现在要写入文字: TCHAR * szText [] = TEXT ("Hello, how are you?") ;
您希望文字从垂直坐标yStart开始,页边距由坐标xLeft和xRight设定。您的任务就是计算文字开始处的水平坐标的xStart值。
如果文字以定宽字体显示,那么这项任务就相当容易,但通常不是这样的。首先您得到字符串的文字宽度: GetTextExtentPoint32 (hdc, szText, lstrlen (szText), &size) ;
如果size.cx比(xRight-xLeft)大,这一行就太长了,不能放在页边距内。我们假定它能放进去。
要向左对齐文字,只要把xStart设定为与xLeft相等,然后写入文字: TextOut (hdc, xStart, yStart, szText, lstrlen (szText)) ;
这很容易。现在可以把size.cy加到yStart中写下一行文字了。
要向右对齐文字,用以下公式计算xStart: xStart = xRight - size.cx ;
居中文字用以下公式:
xStart = (xLeft + xRight - size.cx) / 2 ;
现在开始艰巨的任务-在左右页边距内分散对齐文字。页边距之间的距离是(xRight-xLeft)。如不调整,文字宽度就是size.cx。两者之差 xRight - xLeft - size.cx
必须在字符串的三个空格字符处平均配置。这听起来很讨厌,但还不是太糟。可以呼叫
SetTextJustification (hdc, xRight - xLeft - size.cx, 3)
来完成。第二个参数是字符串内空格字符中需要分配的空间量。第三个参数是空格字符的数量,这里为3。现在把xStart设定与xLeft相等,用TextOut写入文字: TextOut (hdc, xStart, yStart, szText, lstrlen (szText)) ;
文字会在xLeft和xRight页边距之间分散对齐。
无论何时呼叫SetTextJustification,如果空间量不能在空格字符中平均分配,它就会累积一个错误值。这将影响后面的GetTextExtentPoint32呼叫。每次开始新的一行,都必须通过呼叫 SetTextJustification (hdc, 0, 0) ;
来清除错误值。
2016-03-11
GDI绘图路径
绘图路径是储存在GDI内的直线和曲线的集合。绘图路径是在Windows的32位版本中发表的。绘图路径看上去类似于区域,我们确实可以将绘图路径转换为区域,并使用绘图路径进行剪裁。但随后我们会发现两者的不同。
要定义绘图路径,可先简单呼叫 BeginPath (hdc) ;
进行该呼叫之后,所画的任何线(例如,直线、弧及贝塞尔曲线)将作为绘图路径储存在GDI内部,不被显示到设备内容上。绘图路径经常由连结起来的线组成。要制作连结线,应使用LineTo、PolylineTo和BezierTo函数,这些函数都以目前位置为起点划线。如果使用MoveToEx改变了目前位置,或呼叫其它的画线函数,或者呼叫了会导致目前位置改变的窗口/视端口函数,您就在整个绘图路径中建立了一个新的子绘图路径。因此,绘图路径包含一或多个子绘图路径,每一个子绘图路径是一系列连结的线段。
绘图路径中的每个子绘图路径可以是敞开的或封闭的。封闭子绘图路径之第一条连结线的第一个点与最后一条连结线的最后一点相同,并且子绘图路径通过呼叫CloseFigure结束。如果必要的话,CloseFigure将用一条直线封闭子绘图路径。随后的画线函数将开始一个新的子绘图路径。最后,通过下面的呼叫结束绘图路径定义: EndPath (hdc) ;
这时,接着呼叫下列五个函数之一:
StrokePath (hdc) ;
FillPath (hdc) ;
StrokeAndFillPath (hdc) ;
hRgn = PathToRegion (hdc) ;
SelectClipPath (hdc, iCombine) ;
这些函数中的每一个都会在绘图路径定义完成后,将其清除。 StrokePath使用目前画笔绘制绘图路径。
2016-03-11
CreatePen的一个参数表示颜色;ExtCreatePen的相应参数不只表示颜色,它还使用画刷给PS_GEOMETRIC画笔内部着色。该画刷甚至能透过位图定义。
在绘制宽线段时,我们可能要关注线段端点的外观。在连结直线或曲线时,可能还要关注线段间连结点的外观。画笔由CreatePen建立时,这些端点及连结点通常是圆形的;使用ExtCreatePen建立画笔时我们可以选择。(实际上 ,在Windows 98中,只有在使用画笔实作绘图路径时我们可以选择;在Windows NT中要更加灵活)。宽线段的端点可以使用ExtCreatePen中的下列画笔样式定义: PS_ENDCAP_ROUND
PS_ENDCAP_SQUARE
PS_ENDCAP_FLAT
「square」样式与「flat」样式的不同点是:前者将线伸展到一半宽。与端点类似,绘图路径中线段间的连结点可通过如下样式设定:
PS_JOIN_ROUND
PS_JOIN_BEVEL
PS_JOIN_MITER
「bevel」样式将连结点切断;「miter」样式将连结点变为箭头
多看笔记 来自多看阅读 for Android
duokanbookid:tdc91c913d74174e57502cc2fb347g27

《20》的笔记

20

2016-03-13
假设线程共享几个变量或者数据结构。通常,这么多个变量或者结构的字段在它们之间必须是一致的。操作系统可以在更新这些变量的程序中间中断一个线程,那么使用这些变量的线程得到的将是不一致的数据。
结果是冲突发生了,并且通常不难想象这样的错误将对程序造成怎样的破坏。我们所需要的是类似于红绿灯的程序写作技术,以帮助我们对线程交通进行协调和同步,这就是临界区域。大体上,一个临界区域就是一块不可中断的程序代码。 有四个函数用于临界区域。要使用这些函数,您必须定义一个临界区域对象,这是一个型态为CRITICAL_SECTION的整体变量。例如:
CRITICAL_SECTION cs ;
这个CRITICAL_SECTION数据型态是一个结构,但是其中的字段只能由Windows内部使用。这个临界区域对象必须先被程序中的某个线程初始化,通过呼叫: InitializeCriticalSection (&cs) ;
这样就建立了一个名为cs的临界区域对象。该函数的在线辅助说明包含下面的警告:「临界区域对象不能被移动或者复制,程序也不能修改该对象,但必须在逻辑上把它视为不透明的。」这句话,可以被解释为:「不要干扰它,甚至不要看它。」
当临界区域对象被初始化之后,线程可以通过下面的呼叫进入临界区域: EnterCriticalSection (&cs) ;
在这时,线程被认为「拥有」临界区域对象。两个线程不可以同时拥有同一个临界区域对象,因此,如果一个线程进入了临界区域,那么下一个使用同一临界区域对象呼叫EnterCriticalSection的线程将在函数呼叫中被暂停。只有当第一个线程通过下面的呼叫离开临界区域时,函数才会传回控制权:
LeaveCriticalSection (&cs) ;
这时,在EnterCriticalSection呼叫中被停住的那个线程将拥有临界区域,其函数呼叫也将传回,允许线程继续执行。 当临界区域不再被程序所需要时,可以通过呼叫
DeleteCriticalSection (&cs) ;
将其删除,该函数释放所有被配置来维护此临界区域对象的系统资源。 这种临界区域技术涉及「互斥」(此术语在我们继续讨论线程同步时将再次出现)。在任何时刻,只有一个线程能拥有一个临界区域。因此,一个线程可以进入一个临界区域,设定一个结构的字段,然后退出临界区域。另一个使用该结构的线程在存取结构中的字段之前也要先进入该临界区域,然后再退出临界区域。
注意,您可以定义多个临界区域对象,比如cs1和cs2。例如,如果一个程序有四个线程,而前两个线程共享一些数据,那么它们可以使用一个临界区域对象,而另外两个线程共享一些其它的数据,那么它们可以使用另一个临界区域对象。 您在主线程中使用临界区域时应该小心。如果从属线程在它自己的临界区域中花费了一段很长的时间,那么它可能会将主线程的执行阻碍很长一段时间。从属执行绪可能只是使用临界区域复制该结构的字段到自己的区域变量中。
临界区域的一个限制是它们只能用于在同一程序内的线程之间的协调。但是在某些情况下,您需要协调两个不同程序对同一资源的共享(如共享内存等)。在此其况下不能使用临界区域,但是可以使用一种被称为「互斥对象(mutex object)」的技术。「mutex」是个合成字,代表「mutual exclusion(互斥)」,它在这里精确地表达了我们的目的。我们想防止一个程序的线程在更新数据或者使用共享内存与其它资源时被中断。
2016-03-13
事件信号
多线程通常是用于那些必须执行长时间处理的程序。我们可以将一个「大作业」定义为一个可能会违反1/10秒规则的程序。显然大作业包括文书处理程序中的拼写检查、数据库程序中的文件排序或者索引、电子表格的重新计算、打印,甚至包括复杂的绘图。当然,迄今为止我们知道,遵循1/10秒规则的最好方法是将大作业放到另一个线程去执行。这些额外的执行绪不会建立窗口,因此它们不受1/10秒规则的限制。
2016-03-13
窗口消息处理程序还拥有一个静态结构(型态为PARAMS,也定义在程序的顶部),该结构是在窗口消息处理程序和其它线程之间的共享数据。结构只有两个字段-hwnd(程序窗口的句柄)和bContinue,这是一个布尔变量,用于指示线程是否继续计算或者停止。
2016-03-13
然后窗口过程调用_beginthread函数。线程函数Thread以呼叫GetCurrentTime开始,GetCurrentTime取得以毫秒计的Windows启动以来已经执行了的时间。
2016-03-13
KillThread函数只有在正常终止线程比较困难时才应该使用,原因是线程可以配置资源,如内存等。如果当线程终止时没有释放所配置的内存,那么内存将仍然是被配置了的。线程不是程序:所配置的资源在一个程序的所有线程之间是共享的,因此当线程终止时,资源不会被自动释放。好的程序结构要求一个线程释放由它配置的所有资源。
2016-03-13
事件对象
BIGJOB1在每次需要执行暴力测试计算时,就建立一个执行绪。执行绪在完成计算之后自动终止。
另一种可用的方法是在程序的整个生命周期内保持线程的执行,但是只在必要时才启动它。这是一个应用事件对象的理想情况。 事件对象可以是「有信号的」(也称为「被设立的」)或「没信号的」(也称为「被重置的」)。您可以通过下面呼叫来建立事件对象:
hEvent = CreateEvent (&sa, fManual, fInitial, pszName) ;
第一个参数(指向一个SECURITY_ATTRIBUTES结构的指针)和最后一个参数(一个事件对象的名字)只有在事件对象被多个程序共享时才有意义。在同一程序中,这些参数通常被设定为NULL。如果您希望事件对象被初始化为有信号的,那么将fInitial参数设定为TRUE。而如果希望事件对象被初始化为无信号的,则将fInitial参数设定为FALSE。稍后,我将简短地描述fManual参数。 要设立一个现存的事件对象,呼叫
SetEvent (hEvent) ;
要重置一个事件对象,呼叫 ResetEvent (hEvent) ;
一个程序通常呼叫:
WaitForSingleObject (hEvent, dwTimeOut) ;
并且将第二个参数设定为INFINITE。如果事件对象目前是被设立的,那么函数将立即传回,否则,函数将暂停线程直到事件对象被设立。如果您将第二个参数设定为一个以毫秒计的超时时间值,这样函数也可能在事件对象被设立之前传回。 如果最初的CreateEvent呼叫的fManual参数被设定为FALSE,那么事件对象将在WaitForSingleObject函数传回时自动重置。这种功能特性通常使得事件对象没有必要使用ResetEvent函数。
2016-03-13
线程区域储存空间 (TLS)
多线程程序中的整体变量(以及任何被配置的内存)被程序中的所有线程共享。在一个函数中的局部静态变量也被使用函数的所有线程共享。一个函数中的局部动态变量是唯一于各个线程的,因为它们被储存在堆栈上,而每个线程有它自己的堆栈。
对各个线程唯一的持续性储存空间有存在的必要。例如,我在本章前面提到过的C中的strtok函数要求这种型态的储存空间。不幸的是,C语言不支持这类储存空间。但是Windows中提供了四个函数,它们实作了一种技术来做到这一点,并且Microsoft对C的扩充语法也支持它,这就叫做线程区域储存空间。 下面是API工作的方法:
首先,定义一个包含需要唯一于线程的所有数据的结构,例如: typedef struct
{
int a ;
int b ;
}
DATA, * PDATA ;
主线程呼叫TlsAlloc获得一个索引值:
dwTlsIndex = TlsAlloc () ;
这个值可以储存在一个整体变量中或者通过参数结构传递给线程函数。 线程函数首先为该数据结构配置内存,并使用上面所获得的索引值呼叫TlsSetValue:
TlsSetValue (dwTlsIndex, GlobalAlloc (GPTR, sizeof (DATA)) ;
该函数将一个指标和某个线程及某个线程索引相关联。现在,任何需要使用这个指标的函数(包括最初的线程函数本身)都可以包含如下所示的程序代码: PDATA pdata ;
...
pdata = (PDATA) TlsGetValue (dwTlsIndex) ;
现在函数可以设定或者使用pdata->a和pdata->b了。在线程函数终止以前,它释放配置的内存:
GlobalFree (TlsGetValue (dwTlsIndex)) ;
当使用该数据的所有线程都终止之时,主线程将释放索引: TlsFree (dwTlsIndex) ;
这个程序刚开始可能令人有些迷惑,因此如果能看一看如何实作线程区域储存空间可能会有帮助(我不知道Windows实际上是如何实作的,但下面的方案是可能的)。首先,TlsAlloc可能只是配置一块内存(长度为0)并传回一个索引值,即指向这块内存的一个指针。每次使用该索引呼叫TlsSetValue时,通过重新配置将内存块增大8个字节。在这8个字节中储存的是呼叫函数的线程ID(通过GetCurrentThreadId来获得)以及传递给TlsSetValue函数的指标。TlsSetValue简单地使用线程ID来搜寻操作系统管理的线程区域储存空间地址表,然后传回指标。TlsFree将释放内存块。所以您看,这可能是一件容易得可以由您自己来实作的事情。不过,既然已经有工具为您做好了这些工作,那也不错。
Microsoft对C的扩充功能使这件工作更加容易。只要在要对每个线程都保留不同内容的变量前加上__declspec (thread)就好了。对于任何函数的外部静态变量,则为: __declspec (thread) int iGlobal = 1 ;
对于函数内部的静态变量,则为:
__declspec (thread) static int iLocal = 2 ;
多看笔记 来自多看阅读 for Android
duokanbookid:u6691824814f8c3d19c1668e9b2e3ee3

《21》的笔记

21

2016-03-13
动态链接库(也称为DLL)是Microsoft Windows最重要的组成要素之一。大多数与Windows相关的磁盘文件如果不是程序模块,就是动态链接程序。迄今为止,我们都是在开发Windows应用程序
2016-03-13
动态链接库的基本知识
正如前面所看到的,Windows应用程序是一个可执行文件,它通常建立一个或几个窗口,并使用消息循环接收使用者输入。通常,动态链接库并不能直接执行,也不接收消息。它们是一些独立的文件,其中包含能被程序或其它DLL呼叫来完成一定作业的函数。只有在其它模块呼叫动态链接库中的函数时,它才发挥作用。
所谓「动态链接」,是指Windows把一个模块中的函数呼叫连结到动态链接库模块中的实际函数上的程序。在程序开发中,您将各种目标模块(.OBJ)、执行时期链接库(.LIB)文件,以及经常是已编译的资源(.RES)文件连结在一起,以便建立Windows的.EXE文件,这时的连结是「静态连结」。动态链接与此不同,它发生在执行时期。 KERNEL32.DLL、USER32.DLL和GDI32.DLL、各种驱动程序文件如KEYBOARD.DRV、SYSTEM.DRV和MOUSE.DRV和视讯及打印机驱动程序都是动态链接库。这些动态链接库能被所有Windows应用程序使用。
2016-03-13
有些动态链接库(如字体文件等)被称为「纯资源」。它们只包含数据(通常是资源的形式)而不包含程序代码。由此可见,动态链接库的目的之一就是提供能被许多不同的应用程序所使用的函数和资源。在一般的操作系统中,只有操作系统本身才包含其它应用程序能够呼叫来完成某一作业的例程。在Windows中,一个模块呼叫另一个模块函数的程序被推广了。结果使得编写一个动态链接库,也就是在扩充Windows。当然,也可认为动态链接库(包括构成Windows的那些动态链接库例程)是对使用者程序的扩充。
尽管一个动态链接库模块可能有其它扩展名(如.EXE或.FON),但标准扩展名是.DLL。只有带.DLL扩展名的动态链接库才能被Windows自动加载。如果文件有其它扩展名,则程序必须另外使用LoadLibrary或者LoadLibraryEx函数加载该模块。
2016-03-13
链接库:一词多义
动态链接库有着令人困惑的印象,部分原因是由于「链接库」这个词被放在几种不同的用语之后。除了动态链接库之外,我们也用它来称呼「目的码链接库」或「引用链接库」。
目的码链接库是带.LIB扩展名的文件。在使用连结程序进行静态连结时,它的程序代码就会加到程序的.EXE文件中。例如,在Microsoft Visual C++中,连同程序连结的一般C执行目的码链接库被称为LIBC.LIB。 引用链接库是目的码链接库文件的一种特殊形式。像目的码链接库一样,引用链接库有.LIB扩展名,并且被连结器用来确定程序代码中的函数呼叫来源。但引用链接库不含程序代码,而是为连结程序提供信息,以便在.EXE文件中建立动态链接时要用到的复位位表。包含在Microsoft编译器中的KERNEL32.LIB、USER32.LIB和GDI32.LIB文件是Windows函数的引用链接库。如果一个程序呼叫Rectangle函数,Rectangle将告诉LINK,该函数在GDI32.DLL动态链接库中。该信息被记录在.EXE文件中,使得程序执行时,Windows能够和GDI32.DLL动态链接库进行动态连结。
目的码链接库和引用链接库只用在程序开发期间使用,而动态链接库在执行期间使用。当一个使用动态链接库的程序执行时,该动态链接库必须在磁盘上。当Windows要执行一个使用了动态链接库的程序而需要加载该链接库时,动态链接库文件必须储存在含有该.EXE程序的目录下、目前的目录下、Windows系统目录下、Windows目录下,或者是在通过MS-DOS环境中的PATH可以存取到的目录下(Windows会按顺序搜索这些目录)。
2016-03-13
#ifdef __cplusplus
#define EXPORT extern "C" __declspec (dllexport)
#else
#define EXPORT __declspec (dllexport)
#endif
2016-03-13
动态链接库模块不接收消息,但是,动态链接库模块可呼叫GetMessage和PeekMessage。实际上,从消息队列中得到的消息是发给呼叫链接库函数的程序的。一般来说,链接库是替呼叫它的程序工作的,这是一项对链接库所呼叫的大多数Windows函数都适用的规则。
2016-03-13
动态链接库可以从链接库文件或者从呼叫链接库的程序文件中加载资源(如图标、字符串和位图)。加载资源的函数需要执行实体句柄。如果链接库使用它自己的执行实体句柄(初始化期间传给链接库的),则链接库能从它自己的文件中获得资源。为了从呼叫程序的.EXE文件中得到资源,程序链接库函数需要呼叫该函数的程序的执行实体句柄。
在链接库中登录窗口类别和建立窗口需要一点技巧。窗口类别结构和CreateWindow呼叫都需要执行实体句柄。尽管在建立窗口类别和窗口时可使用动态链接库模块的执行实体句柄,但在链接库建立窗口时,窗口消息仍会发送到呼叫链接库中程序的消息队列。如果使用者必须在链接库中建立窗口类别和窗口,最好的方法可能是使用呼叫程序的执行实体句柄。 因为模态对话框的消息是在程序的消息循环之外接收到的,因此使用者可以在链接库中呼叫DialogBox来建立模态对话框。执行实体句柄可以是链接库句柄,并且DialogBox的hwndParent参数可以为NULL。
2016-03-13
不用输入引用信息的动态链接
除了在第一次把使用者程序加载内存时,由Windows执行动态链接外,程序执行时也可以把程序同动态链接库模块连结到一起。例如,您通常会这样呼叫Rectangle函数:
Rectangle (hdc, xLeft, yTop, xRight, yBottom) ;
因为程序和GDI32.LIB引用链接库连结,该链接库提供了Rectangle的地址,因此这种方法有效。 您也可以用更迂回的方法呼叫Rectangle。首先用typedef为Rectangle定义一个函数型态:
typedef BOOL (WINAPI * PFNRECT) (HDC, int, int, int, int) ;
然后定义两个变量: HANDLE hLibrary ;
PFNRECT pfnRectangle ;
现在将hLibrary设定为链接库句柄,将lpfnRectangle设定为Rectangle函数的地址:
hLibrary = LoadLibrary (TEXT ("GDI32.DLL"))
pfnRectangle = (PFNPRECT) GetProcAddress (hLibrary, TEXT ("Rectangle"))
如果找不到链接库文件或者发生其它一些错误,LoadLibrary函数传回NULL。现在您可以呼叫函数然后释放链接库: pfnRectangle (hdc, xLeft, yTop, xRight, yBottom) ;
FreeLibrary (hLibrary) ;
尽管这项执行时期动态链接的技术并没有为Rectangle函数增加多大好处,但它肯定是有用的,如果直到执行时还不知道程序动态链接库模块的名称,这时就需要使用它。
多看笔记 来自多看阅读 for Android
duokanbookid:w6g02f2607963cg73e989ebbdfg226f2

《22》的笔记

22

2016-03-13
多媒体硬件
或许最常用的多媒体硬件就是波形声音设备,也就是平常所说的声卡。波形声音设备将麦克风的输入或其它声音输入转换为数字取样,并将其储存到内存或者储存到以.WAV为扩展名的磁盘文件中。波形声音设备还将波形转换回模拟声音,以便通过PC扩音器来播放。
声卡通常还包含MIDI设备。MIDI是符合工业标准的乐器数字化接口(Musical Instrument Digital Interface)。这类硬件播放音符以响应短的二进制命令消息。MIDI硬件通常还可以通过电缆连结到如音乐键盘等的MIDI输入设备上。通常,外部的MIDI合成器也能够添加到声卡上。 现在,大多数PC上的CD-ROM驱动器都具备播放普通音乐CD的能力。这就是平常所说的「CD声音」。来自波形声音设备、MIDI设备以及CD声音设备的输出,一般在使用者的控制下用「音量控制」程序混合在一起。
另外几种普遍的多媒体「设备」不需要额外的硬件。Windows视讯设备(也称作AVI视讯设备)播放扩展名为.AVI(audio-video interleave:声音视频插格)的电影或动画文件。「ActiveMovie控件」可以播放其它型态的电影,包括QuickTime和MPEG。PC上的显示卡需要特定的硬件来协助播放这些电影。 还有个别PC使用者使用某种Pioneer雷射影碟机或者Sony VISCA系列录放机。这些设备都有串行端口接口,因此可由PC软件来控制。某些显示卡具有一种称为「窗口影像(video in a window)」的功能,此功能允许一个外部的视讯信号与其它应用程序一起出现在Windows的屏幕上。这也可认为是一种多媒体设备。
2016-03-13
API概述
在Windows中,API支持的多媒体功能主要分成两个集合。它们通常称为「低阶」和「高阶」界面。
低阶接口是一系列函数,这些函数以简短的说明性前缀开头,而且在/Platform SDK/Graphics and Multimedia Services/Multimedia Reference/Multimedia Functions(与高阶函数一起)中列出。 低阶的波形声音输入输出函数的前缀是waveIn和waveOut。我们将在本章看到这些函数。另外,本章还讨论用midiOut函数来控制MIDI输出设备。这些API还包括midiIn和midiStream函数。
本章还使用前缀为time的函数,这些函数允许设定一个高分辨率的定时器例程,其定时器的时间间隔速率最低能够到1毫秒。此程序主要用于播放MIDI音乐。其它几组函数包括声音压缩、视讯压缩以及动画和视讯序列,
2016-03-13
您还会注意到多媒体函数列表中七个带有前缀mci的函数,它们允许存取媒体控制接口(MCI:Media Control Interface)。这是一个高阶的开放接口,用于控制多媒体PC中所有的多媒体硬件。MCI包括所有多媒体硬件都共有的许多命令,因为多媒体的许多方面都以磁带录音机这类设备播放/记录方式为模型。您为输入或输出而「打开」一台设备,进而可以「录音」(对于输入)或者「播放」(对于输出),并且结束后可以「关闭」设备。
MCI本身分为两种形式。一种形式下,可以向MCI发送消息,这类似于Windows消息。这些消息包括位编码标记和C数据结构。另一种形式下,可以向MCI发送文字字符串。这个程序主要用于描述命令语言,此语言具有灵活的字符串处理函数,但支持呼叫Windows API的函数不多。字符串命令版的MCI还有利于交互研究和学习MCI,我们马上就举一个例子。MCI中的设备名称包括CD声音(cdaudio)、波形音响(waveaudio)、MIDI编曲器(sequencer)、影碟机(videodisc)、vcr、overlay(窗口中的模拟视频)、dat(digital audio tape:数字式录频磁带)以及数字视频(digitalvideo)。MCI设备分为「简单型」和「混合型」。简单型设备(如CD声音)不使用文件。混合型设备(如波形音响)则使用文件。使用波形音响时,这些文件的扩展名是.WAV。
2016-03-13
存取多媒体硬件的另一种方法包括DirectX API,它超出了本书的范围。
另外两个高阶多媒体函数也值得一提:MessageBeep和PlaySound,它们在第三章有示范。MessageBeep播放「控制台」的「声音」中指定的声音。PlaySound可播放磁盘上、内存中或者作为资源加载的.WAV文件。本章的后面还会用到PlaySound函数。
2016-03-13
open cdaudio
然后按Enter键。其中open是MCI命令,cdaudio是MCI认定的CD-ROM驱动器的设备名称(假定您的系统中只有一个CD-ROM驱动器。要获得多个CD-ROM驱动器名称需使用sysinfo命令)。
多看笔记 来自多看阅读 for Android
duokanbookid:g753edgff170046ceef73f7564d0d1b9

《23》的笔记

23

2016-03-13
分别是Windows Sockets (Winsock) API和Windows Internet(WinInet)API支持的文件传输协议(FTP:File Transfer Protocol)的部分。
2016-03-13
Sockets和TCP/IP
Socket通常(但不专用于)与主宰Internet通信的传输控件协议/因特网协议(TCP/IP:Transmission Control Protocol/Internet Protocol)牵连在一起。因特网协定(IP:Internet Protocol),作为TCP/IP的组成部分之一,用来将数据打包成「数据封包(datagram)」,该资料封包包含用于标识数据来源和目的地的表头信息。而传输控制协议(TCP:Transmission Control Protocol)则提供了可靠的传输和检查IP数据封包正确性的方法。
在TCP/IP下,通讯端点由IP地址和端口号定义。IP地址包括4个字节,用于确定Internet上的服务器。IP地址通常按「由点连结的四个小于255的数字」的格式显示,例如「209.86.105.231」。埠号确定了特定的服务或服务器提供的服务。其中一些埠号已经标准化,以提供众所周知的服务。 当Socket与TCP/IP合用时,Socket就是TCP/IP的通讯端点。因此,Socket指定了IP地址和端口号。
2016-03-13
网络时间服务
下面给出的范例程序与提供时间协议(Time Protocol)的Internet服务器相连结。此程序将获得目前准确的日期和时间,并用此信息设定您的PC时钟。
在美国,国家标准和技术协会(National Institute of Standards and Technology)(以前称为国家标准局(National Bureau of Standards))负责维护准确时间,该时间与世界各地的机构相联系。准确时间可用于无线电广播、电话号码、计算机拨号电话号码以及Internet,关于这些的所有文件都位于网站 http://www.bldrdoc.gov/timefreq(网域名称「bldrdoc」指的是Boulder、Colorado、NIST Time的位置和Frequency Division)。 我们只对NIST Network Time Service感兴趣,其详细的文件位于 http://www.bldrdoc.gov/timefreq/service/nts.htm。此网页列出了十个提供NIST时间服务的服务器。例如,第一个名称为time-a.timefreq.bldrdoc.gov,其IP地址为132.163.135.130。
(我曾经编写过一个使用非Internet NIST计算机拨接服务的程序,并发表于《PC Magazine》,您也可以在Ziff-Davis的网站 http://www.zdnet.com/pcmag/pctech/content/16/20/ut1620.001.html中找到。此程序对于想学习如何使用Windows Telephony API的人很有帮助。) 在Internet上有三个不同的时间服务,每一个都由Request for Comment(RFC)描述为Internet标准。日期协议(Daytime Protocol)(RFC-867)提供了一个ASCII字符串用于指出准确的日期和时间。该ASCII字符串的准确格式并不标准,但人们可以理解其中的含义。时间协议(RFC-868)提供了一个32位的数字,用来表示从1900年1月1日至今的秒数。该时间是UTC(不考虑字母顺序,它表示世界时间坐标(Coordinated Universal Time)),它类似于所谓的格林威治标准时间(Greenwich Mean Time)或者GMT-英国格林威治时间。第三个协议称为网络时间协议(Network Time Protocol)(RFC-1305),该协议很复杂。
对于我们的目的,即包括分析Socket和不断更新PC时钟,时间协议RFC-868已经够用了。RFC-868只是一个两页的简短文件,主要是说用TCP获得准确时间的程序应该有如下步骤: 连结到提供此服务的服务器埠37。
接收32位的时间。 关闭连结。
现在我们已经知道了编写存取时间服务的Socket应用程序的每个细节。
2016-03-13
NETTIME程序
Windows Sockets API,通常也称为WinSock,与Berkeley Sockets API兼容,因此,可以想象UNIX Socket程序代码可以顺利地拿到Windows上使用。Windows下更进一步的支持由对Berkeley Socket扩充的功能提供,其函数的形式是以WSA(「WinSock API」)为前缀。相关的概述和参考位于/Platform SDK/Networking and Distributed Services/Windows Sockets Version 2。
2016-03-13
WinInet 和 FTP
WinInet(「Windows Internet」)API是一个高阶函数集,帮助程序写作者使用三个常见的Internet协议,这三个协议是:用于World Wide Web全球信息网的超文字传输协议(HTTP:Hypertext Transfer Protocol)、文件传输协议(FTP:File Transfer Protocol)和另一个称为Gopher的文件传输协议。WinInet函数的语法与常用的Windows文件函数的语法类似,这使得使用这些协议就像使用本地磁盘驱动器上的文件一样容易。WinInet API的文件位于/Platform SDK/Internet, Intranet, Extranet Services/Internet Tools and Technologies/WinInet API。
下面的范例程序将展示如何使用WinInet API的FTP部分。许多有网站的公司也都有「匿名FTP」服务器,这样使用者可以在不输入使用者名称和密码的情况下下载文件。例如,如果您在Internet Explorer的地址栏输入ftp://ftp.microsoft.com,那么您就可以浏览FTP服务器上的目录并下载文件。如果进入ftp://ftp.cpetzold.com/cpetzold.com/ProgWin/UpdDemo,那么您将在我的匿名FTP服务器上发现与待会要提到的范例程序一块使用的文件列表。 虽然现今FTP服务对大多数的Web使用者来说并不是那么方便使用,但它仍然相当有用。例如,应用程序能利用FTP从匿名FTP服务器上取得数据,这些取得数据的运作程序几乎完全在台面下处理,而不需要使用者操心。这就是我们将讨论的UPDDEMO(「update demonstration:更新范例」)程序的构想。
2016-03-13
FTP API概况
使用WinInet的程序必须在所有呼叫WinInet函数的源文件中包括表头文件WININET.H。程序还必须连结WININET.LIB。在Microsoft Visual C++中,您可以在「Project Settings」对话框的「Link」页面卷标中指定。执行时,程序将和WININET.DLL动态链接库连结。
在下面的论述中,我不会详细讨论函数的语法,因为某些函数有很多选项,这让它变得相当复杂。要掌握WinInet,您可以将UPDDEMO原始码当成食谱来看待。这时最重要的是了解有关的各个步骤以及FTP函数的范围。 要使用Windows Internet API,首先要呼叫InternetOpen。然后,使用WinInet支持的任何一种协议。InternetOpen给您一个Internet作业句柄,并储存到HINTERNET型态的变量中。用完WinInet API以后,应该通过呼叫InternetCloseHandle来关闭句柄。
要使用FTP,您接下来就要呼叫InternetConnect。此函数需要使用由InternetOpen建立Internet作业句柄,并且传回FTP作业的句柄。您可将此句柄作为名称开头为Ftp的所有函数的第一个参数。InternetConnect函数的参数指出要使用的FTP,还提供了服务器名称,例如,ftp.cpetzold.com。此函数还需要使用者名称和密码。如果存取匿名FTP服务器,这些参数可以设定为NULL。如果应用程序呼叫InternetConnect时PC并没有连结到Internet,Windows 98将显示「拨号联机」对话框。当使用FTP的应用程序结束时,呼叫InternetCloseHandle来关闭句柄。 这时可以开始呼叫有Ftp前缀的函数。您将发现这些函数与标准的Windows文件I/O函数很相似。为了避免与其它协议重复,一些以Internet为前缀的函数也可以处理FTP。
下面四个函数用于处理目录: fSuccess = FtpCreateDirectory (hFtpSession, szDirectory) ;
fSuccess = FtpRemoveDirectory (hFtpSession, szDirectory) ;
fSuccess = FtpSetCurrentDirectory (hFtpSession, szDirectory) ;
fSuccess = FtpGetCurrentDirectory (hFtpSession, szDirectory, &dwCharacterCount) ;
注意,这些函数很像我们所熟悉的Windows提供用于处理本地文件系统的CreateDirectory、RemoveDirectory、SetCurrentDirectory和GetCurrentDirectory函数。
当然,存取匿名FTP的应用程序不能建立或删除目录。而且,程序也不能假定FTP目录具有和Windows文件系统相同的目录结构型态。特别是用相对路径名设定目录的程序,不能假定关于新的目录全名的一切。如果程序需要知道最后所在目录的整个名称,那么呼叫了SetCurrentDirectory之后必须再呼叫GetCurrentDirectory。GetCurrentDirectory的字符串参数至少包含MAX_PATH字符,并且最后一个参数应指向包含该值的变量。 下面两个函数让您删除或者重新命名文件(但不是在匿名FTP服务器上):
fSuccess = FtpDeleteFile (hFtpSession, szFileName) ;
fSuccess = FtpRenameFile (hFtpSession, szOldName, szNewName) ;
经由先呼叫FtpFindFirstFile,可以查找文件(或与含有万用字符的文件名样式相符的多个文件)。此函数很像FindFirstFile函数,甚至都使用了相同的WIN32_FIND_DATA结构。该文件为列举出来的文件传回了一个句柄。您可以将此句柄传递给InternetFindNextFile函数以获得额外的文件名称信息。最后通过呼叫InternetCloseHandle来关闭句柄。 要打开文件,可以呼叫FtpFileOpen。
2016-03-13
这个函数传回一个文件句柄,此句柄可以用于InternetReadFile、InternetReadFileEx、InternetWrite和InternetSetFilePointer呼叫。最后可以通过呼叫最常用的InternetCloseHandle函数来关闭句柄。
最后,下面两个高级函数特别有用:FtpGetFile呼叫将文件从FTP服务器复制到本地内存,它合并了FtpFileOpen、FileCreate、InternetReadFile、WriteFile、InternetCloseHandle和CloseHandle呼叫。FtpGetFile的另一个参数是一个旗标,如果本地已经存在同名文件,那么该旗标将导致函数呼叫失败。FtpPutFile与此函数类似,用于将文件从本地内存复制到FTP服务器。
2016-03-13
FtpThread函数透过使用下面的呼叫来完成实际的传输:InternetOpen、InternetConnect、FtpSetCurrentDirectory、FtpFindFirstFile、InternetFindNextFile、FtpGetFile和InternetCloseHandle(三次)。如同大多数程序代码,该线程函数如果略过错误检查、让使用者了解下一步的操作情况以及允许使用者随意取消整个显示的那些步骤,那么它将变得简洁许多。FtpThread函数透过用hwndStatus句柄呼叫SetWindowText来让使用者知道进展情况,这里指的是对话框中间的静态文字区。
2016-03-13
线程可以依照下面的三种方式之一来终止:
第一种,FtpThread可能遇到从WinInet函数传回的错误。如果是这样,它将清除并编排错误字符串的格式,然后将此字符串(连同对话框文字区句柄和「Cancel」按钮的句柄一起)传递给ButtonSwitch。ButtonSwitch是一个小函数,它显示了文字字符串,并将「Cancel」按钮转换成「OK」按钮-不只是按钮上的文字字符串的转换,还包括控件ID的转换。这样就允许使用者按下「OK」按钮来结束对话框。 第二种方式,FtpThread能在没有任何错误的情况下完成任务,其处理方法和遇到错误时的方法一样,只不过对话框中显示的字符串为「Internet Download Complete」。
第三种方式,使用者可以在程序中选择取消下载。这时,DlgProc将PARAMS结构的bContinue字段设定为FALSE。FtpThread频繁地检查该值,如果bContinue等于FALSE,那么函数将做好应该进行的收拾工作,并以NULL文字参数呼叫ButtonSwitch,此参数表示显示了字符串「Internet Session Cancelled」。同样,使用者必须按下「OK」按钮来关闭对话框。
多看笔记 来自多看阅读 for Android
duokanbookid:je498522cf9235g4f2ccb641cdbdc0e0

你可能感兴趣的:(c,程序设计)