Ensure Cleanup之C++ 样板类别(EnsureCleanup.h)
无疑的,一般程序设计上常见的错误即是在程序代码中忘记关闭或释放一个资源,这也是多数开发者必须面对的情形。为了保证资源在超过使用范围时会被适当地清除,我建立了CensureCleanup之C++ 样板类别(Template Class),如列表B-1所示。因为这个类别是一个样板类别,它可以帮助您清除一般的大部份物件类型。
我为大部份常见的Microsoft Windows之资料型别定义了CensureCleanup的特定实例。定义一个型别需要叁种资讯:物件型别、被用来清除物件的函数位址以及一个指示该物件无效的值(通常为NULL或0)。举例来说,为了清除一个核心物件,它的资料型别为HANDLE,用来清除该物件的函数为CloseHandle,以及一个确认该handle为无效的NULL值。同样的,要清除一个动态载入的DLL,它的资料型别为HANDLE,用来清除资源的函数为FreeLibrary,和一个用来确认为无效物件的NULL值。您可以在CEnsureCleanup.h文件后面看到所有用于清除之不同类别。
使用CensureCleanup类别并不会使事情变得更容易。无论何时,当您平常使用正常的资料类型时,所必须做的事情即是使用一个类别的实例。例如,您不须编写如下的程序代码:
HANDLE hfile = CreateFile(...); . . . CloseHandle(hfile);
您可以编写如下的样子:
CEnsureCloseFile hfile = CreateFile(...); . . .
使用C++ 类别资料型态,您不需再呼叫CloseHandle释放资源。当C++ 物件超出使用范围时,解构函数(Destructor)会被呼叫,而CloseHandle也会自动被呼叫!事实上,即使是从一个呼叫函数的中途返回或者是产生一个例外,此时物件都保证会被清除。
顺便一提,您毋须担心管理的部份。我检查了在使用这些类别时,由最佳化编译器所产生的组合语言,其产生的代码正如同您明确地呼叫清除函数的情形。这是因为CensureCleanup函数使用了所有的内嵌函数。我简直爱死了这些类别,而且也不敢相信自己在这么多年的程序设计生涯中竟没有使用到它们。现在我不管什么事几乎都会用到它们。
EnsureCleanup.h
/********************************************************************
模组:EnsureCleanup.h
通告:Copyright (c)2000 Jeffrey Richter
目的:当一个物件超过使用范围时,这些类别会确认物件被清除。请参阅附录B的内容。
********************************************************************/
#pragma once // 每次完成编译时,包含此标头档
///////////////////////////////////////////////////////////////////////////////
#include "../CmnHdr.h" // 请参阅附录A
///////////////////////////////////////////////////////////////////////////////
// 资料型别表示物件清除函数的位址
// 我使用了UINT_PTR以使此类别可在64位元之Windows中适当地执行
typedef VOID (WINAPI* PFNENSURECLEANUP)(UINT_PTR);
///////////////////////////////////////////////////////////////////////////////
// 每一个范本说明需要一个资料型别、清除函数的位址及一个指示无效的值
template<class TYPE, PFNENSURECLEANUP pfn, UINT_PTR tInvalid = NULL>
class CEnsureCleanup {
public:
// 预设的建构者假设为一个无效的值(没有东西需被清除)
CEnsureCleanup(){m_t = tInvalid;}
// 此建构者设定该值为一个特定值
CEnsureCleanup(TYPE t) : m_t((UINT_PTR) t) { }
// 执行清除的解构者
~CEnsureCleanup() { Cleanup(); }
// 如果要显示该值是否为一个有效的物件,使用Helper方法告知
BOOL IsValid() { return(m_t != tInvalid); }
BOOL IsInvalid() { return(!IsValid()); }
// 重新指定物件,强迫现行物件被清除
TYPE operator=(TYPE t){
Cleanup();
m_t = (UINT_PTR) t;
return(*this);
}
// 回传该值(支援32位元及64位元的Windows)
operator TYPE() {
// 如果TYPE是32位元值,转型至32位元的TYPE
// 如果TYPE是64位元值,转型至64位元的TYPE
return((sizeof(TYPE) == 4) ? (TYPE) PtrToUint(m_t) : (TYPE) m_t);
}
// 如果该值显示一个有效的物件,清除该物件
void Cleanup() {
if (IsValid()) {
// 在64位元Windows中,所有的参数皆为64位元
// 所以不需转型
pfn(m_t); // 关闭物件
m_t = tInvalid; // 我们不再显示一个有效的物件
}
}
private:
UINT_PTR m_t; // 表示物件的成员
};
///////////////////////////////////////////////////////////////////////////////
// 使它变得容易的巨集,为特定资料型别宣告范本类别的实例
#define MakeCleanupClass(className, tData, pfnCleanup) /
typedef CEnsureCleanup<tData, (PFNENSURECLEANUP) pfnCleanup> className;
#define MakeCleanupClassX(className, tData, pfnCleanup, tInvalid) /
typedef CEnsureCleanup< tData, (PFNENSURECLEANUP)pfnCleanup, /
(INT_PTR)tInvalid> className;
///////////////////////////////////////////////////////////////////////////////
// 一般资料型别的C++ 范本类别实例
MakeCleanupClass(CEnsureCloseHandle, HANDLE, CloseHandle);
MakeCleanupClassX(CEnsureCloseFile, HANDLE, CloseHandle,
INVALID_HANDLE_VALUE);
MakeCleanupClass(CEnsureLocalFree, HLOCAL, LocalFree);
MakeCleanupClass(CEnsureGlobalFree, HGLOBAL, GlobalFree);
MakeCleanupClass(CEnsureRegCloseKey, HKEY, RegCloseKey);
MakeCleanupClass(CEnsureCloseServiceHandle, SC_HANDLE, CloseServiceHandle);
MakeCleanupClass(CEnsureCloseWindowStation, HWINSTA, CloseWindowStation);
MakeCleanupClass(CEnsureCloseDesktop, HDESK, CloseDesktop);
MakeCleanupClass(CEnsureUnmapViewOfFile, PVOID, UnmapViewOfFile);
MakeCleanupClass(CEnsureFreeLibrary, HMODULE, FreeLibrary);
///////////////////////////////////////////////////////////////////////////////
// 释放一个保留区域的特殊类别
// 因为VirtualFree需要叁个参数,所以要求特殊的类别
class CEnsureReleaseRegion {
public:
CEnsureReleaseRegion(PVOID pv = NULL) : m_pv(pv) {}
~CEnsureReleaseRegion() {Cleanup(); }
PVOID operator=(PVOID pv) {
Cleanup();
m_pv = pv;
return(m_pv);
}
operator PVOID(){return(m_pv);}
void Cleanup() {
if (m_pv != NULL) {
VirtualFree(m_pv, 0, MEM_RELEASE);
m_pv = NULL;
}
}
private:
PVOID m_pv;
};
///////////////////////////////////////////////////////////////////////////////
// 为了从heap中释放一个区块的特殊类别
// 因为HeapFree需要叁个参数,所以要求特殊的类别
class CEnsureHeapFree {
public:
CEnsureHeapFree(PVOID pv = NULL, HANDLE hHeap = GetProcessHeap())
:m_pv(pv), m_hHeap(hHeap) {}
~CEnsureHeapFree() {Cleanup(); }
PVOID operator=(PVOID pv) {
Cleanup();
m_pv = pv;
return(m_pv);
}
operator PVOID() {return(m_pv); }
void Cleanup() {
if (m_pv != NULL) {
HeapFree(m_hHeap, 0, m_pv);
m_pv = NULL;
}
}
private:
HANDLE m_hHeap;
PVOID m_pv;
};
///////////////////////////////// End of File /////////////////////////////////
| 列表B-1 EnsureCleanup.h标头文件 |
Print Buffer之C++ 类别(PrintBuf.h)
许多书中的范例应用程序建构了资讯的字串。然后这个资讯会被放入一个静态控制项或唯读的编辑控制项中,不然就是被显示在一个讯息方块里。列示在列表B-2中的CprintBuf类别让建构这些字串的工作变得更简单。
CprintBuf物件会持续追踪一个字串缓冲器。每一次Print或PrintError方法被呼叫时,新的字串资讯就会被新增至字串后方。如您所见,预测字串所需的储存空间是很困难的,因为应用程序没有办法提前知道将被放入的字串为何。
为了有效地使用内存,CprintBuf的字串缓冲器经由保留一个较大的内存区块而被初始。因为它不会使用一个小于系统配置的大略值(迄今所有的Windows平台上皆为64 KB),所以我们将它预设为64 KB大小。在保留这个区域后,会提交一个储存分页给它。如果字串资料试图使用超过此储存范围,则会产生一个例外,而CprintBuf物件会提交更多储存空间给缓冲器,增加所需的内存。
此类别多载(overload)了PCTSTR的转型运算子,使得类别的实例可被直接传递给需要一个指向包含以零结尾字串的缓冲器函数。
PrintBuf.h
/********************************************************************
模组:PrintBuf.h
通告:Copyright (c)2000 Jeffrey Richter
目的:此类别包装了在自动地增加资料缓冲器时允许sprintf类的操作。
请参阅附录B
********************************************************************/
#pragma once // 每个编辑单元皆须包含此标头档
///////////////////////////////////////////////////////////////////////////////
#include "../CmnHdr.h" // 请参阅附录A
#include <StdIO.h> // 为了 _vstprintf而包含
///////////////////////////////////////////////////////////////////////////////
class CPrintBuf {
public:
CPrintBuf(SIZE_T nMaxSizeInBytes = 64 * 1024); // 预设为64 KB
virtual ~CPrintBuf();
BOOL Print(PCTSTR pszFmt, ...);
BOOL PrintError(DWORD dwError = GetLastError());
operator PCTSTR() {return(m_pszBuffer);}
void Clear();
private:
LONG Filter(EXCEPTION_POINTERS* pep);
private:
int m_nMaxSizeInBytes;
int m_nCurSize;
PTSTR m_pszBuffer;
};
///////////////////////////////////////////////////////////////////////////////
#ifdef PRINTBUF_IMPL
///////////////////////////////////////////////////////////////////////////////
CPrintBuf::CPrintBuf(SIZE_T nMaxSizeInBytes) {
// 此建构者设定成员的间隔值并保留一个nMaxSizeInBytes大小的位址区块,并且提交单一分页
m_nMaxSizeInBytes = nMaxSizeInBytes;
m_nCurSize = 0;
m_pszBuffer = (PTSTR)
VirtualAlloc(NULL, m_nMaxSizeInBytes, MEM_RESERVE, PAGE_READWRITE);
chASSERT(m_pszBuffer != NULL);
chVERIFY(VirtualAlloc(m_pszBuffer, 1, MEM_COMMIT, PAGE_READWRITE) != NULL);
}
///////////////////////////////////////////////////////////////////////////////
CPrintBuf::~CPrintBuf() {
VirtualFree(m_pszBuffer, 0, MEM_RELEASE);
}
///////////////////////////////////////////////////////////////////////////////
void CPrintBuf::Clear() {
VirtualFree(m_pszBuffer, m_nMaxSizeInBytes, MEM_DECOMMIT);
chVERIFY(VirtualAlloc(m_pszBuffer, 1, MEM_COMMIT, PAGE_READWRITE) != NULL);
m_nCurSize = 0;
}
///////////////////////////////////////////////////////////////////////////////
LONG CPrintBuf::Filter(EXCEPTION_POINTERS* pep) {
LONG lDisposition = EXCEPTION_EXECUTE_HANDLER;
EXCEPTION_RECORD* per = pep->ExceptionRecord;
__try {
// 检查产生的例外是否为一个在资料缓冲器区域的存取违规?
if (per->ExceptionCode != EXCEPTION_ACCESS_VIOLATION)
__leave;
if (!chINRANGE(m_pszBuffer, (PVOID) per->ExceptionInformation[1],
((PBYTE) m_pszBuffer) + m_nMaxSizeInBytes - 1)){
__leave;
}
// 试图提交内存到该区域
if (VirtualAlloc((PVOID) pep->ExceptionRecord->ExceptionInformation[1],
1, MEM_COMMIT, PAGE_READWRITE) == NULL) {
__leave;
}
lDisposition = EXCEPTION_CONTINUE_EXECUTION;
}
__finally {
}
return(lDisposition);
}
///////////////////////////////////////////////////////////////////////////////
int CPrintBuf::Print(PCTSTR pszFmt , ...) {
// 此函数增加文字至格式化的列印缓冲器
int nLength = -1; // Assume failure
va_list arglist;
va_start(arglist, pszFmt);
__try {
// 新增字串到缓冲器的结尾
nLength = _vstprintf(m_pszBuffer + m_nCurSize, pszFmt, arglist);
if (nLength > 0)
m_nCurSize += nLength;
}
__except (Filter(GetExceptionInformation())) {
chMB("CPrintBuf attempted to go over the maximum size.");
DebugBreak();
}
va_end(arglist);
return(nLength);
}
///////////////////////////////////////////////////////////////////////////////
BOOL CPrintBuf::PrintError(DWORD dwErr) {
// 新增最后的错误字串文字至缓冲器
PTSTR pszMsg = NULL;
BOOL fOk = (0 != FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, NULL,
dwErr, 0, (PTSTR) &pszMsg, 0, NULL));
fOk = fOk && (Print(TEXT("Error %d: %s"), dwErr, pszMsg) >= 0);
if (pszMsg != NULL)
LocalFree(pszMsg);
return(fOk);
}
///////////////////////////////////////////////////////////////////////////////
#endif // PRINTBUF_IMPL
///////////////////////////////// End of File /////////////////////////////////
| 列表B-2 PrintBuf.h标头文件 |
Auto Buffer之C++ 范本类别(AutoBuf.h)
许多Windows函数需要使用缓冲器,所以您必须配置它。一般的情形下,您没有办法在呼叫一个函数前得知正确的缓冲器大小,所以为了这些Windows函数,您必须先呼叫函数一次,取得所需的缓冲器大小,然后再配置缓冲器,最后再呼叫函数一秒钟以实际地将资料放入缓冲器内。当然这是相当不方便的。再加上呼叫函数期间可能会改变缓冲器大小的需求,因为Windows是一个先占式(Preemptive)的多线程作业系统。在一些情况中,您第二次对函数的呼叫可能会失败。虽然执行失败的可能性相当小,但是一个良好的应用程序编写应确实地将这部份列入考虑并使用好的设计风格及优美的处理方式。例如,以下为需要使用RegQueryValueEx取得一个适当登录值的程序代码:
ULONG uValSize = 1;
PTSTR pszValue = NULL;
LONG lErr;
do {
if (uValSize != 0) {
if (pszValue != NULL) {
HeapFree(GetProcessHeap(), 0, pszValue);
}
pszValue = (PTSTR)HeapAlloc(GetProcessHeap(), 0, uValSize);
if (pszValue == NULL) {
// 错误:未显示适当的处理方式
}
}
// 假设hkey已被初始
lErr = RegQueryValueEx(hkey, TEXT("SomeValueName"), NULL,
NULL, (PBYTE) pszValue, &uValSize);
}while (lErr == ERROR_MORE_DATA);
if (lErr != ERROR_SUCCESS){
// 错误:未显示适当的处理方式
}
接下来的程序代码片段执行了相同的功能,但是为了配置缓冲器而使用CautoBuf函数。一个附加的特色是,当物件超出使用范围时,缓冲器会自动地被释放。
CAutoBuf<PTSTR, sizeof(TCHAR)> pszValue;
pszValue = 1;
LONG lErr;
do {
lErr = RegQueryValueEx(hkey, TEXT("SomeValueName"), NULL, NULL,
pszValue, pszValue);
} while (lErr == ERROR_MORE_DATA);
if (lErr != ERROR_SUCCESS){
// 错误:未显示适当的处理方式
}
列于列表B-3的CautoBuf之C++ 类别使需要紧握缓冲器的函数得以正常工作。一个CautoBuf物件配置一个资料缓冲器,经由任何Windows函数告知会自动地决定其大小。另外,当物件超出使用范围时,CautoBuf物件会自动地释放它的资料缓冲器。
由于CautoBuf之C++ 类别是一个样版类别,所以它支援使用任何资料型别的缓冲器。要使用此类别,您可以简单地用您想要持有的资料型别宣告一个实例。以下是一个CautoBuf的范例,它应该包含一个QUERY_SERVICE_CONFIG结构(其长度可变):
CAutoBuf<QUERY_SERVICE_CONFIG> pServiceConfig;
现在可以开始将资料填入缓冲器,您必须使用以下的方法呼叫QueryServiceConfig函数:
BOOL fOk; fOk = QueryServiceConfig( hService, // hService (SC_HANDLE) pServiceConfig, // pServiceConfig (QUERY_SERVICE_CONFIG*) pServiceConfig, // cbBufferSize (DWORD) pServiceConfig); // pcbBytesNeeded (PDWORD)
您应该会注意到该函数传递了pServiceConfig物件给叁个参数,这是因为CautoBuf类别在物件上使用了叁个转型方法:一个回传资料缓冲器位址的转型方法,一个以位元组大小回传资料缓冲器大小的DWORD转型方法,以及一个回传将被用所需缓冲器大小填入的一个DWORD成员变数之PDWORD转型方法。
如果传递给cbBufferSize参数的值太小,QueryServiceConfig会回传FALSE,且随后对GetLastError的呼叫会回传ERROR_INSUFFICIENT_BUFFER值。在这个情形下,您需要做的事就是再呼叫QueryServiceConfig一次,此时缓冲器会自动调整至pcbBytesNeeded参数回传的大小。这一次QueryServiceConfig应该可以成功地填入缓冲器,并回传非零值。
然而,QueryServiceConfig仍然有可能执行失败,因为另一个线程可能会改变服务的状态资讯。所以良好的应用程序编写方式应该重覆地呼叫QueryServiceConfig,直到它执行成功为止。为了使这个工作变得容易些,我们在AutoBuf.h文件的结尾处加入了GROWUNTIL巨集。这个巨集在一个回圈中重覆地呼叫了所需的函数,直到该函数执行成功或产生除了ERROR_INSUFFICIENT_BUFFER或ERROR_MORE_DATA的错误为止。以下列出一个范例,说明如何使用此巨集:
BOOL fOk; GROWUNTIL(FALSE, fOk = QueryServiceConfig( hService, // hService (SC_HANDLE) pServiceConfig, // pServiceConfig (QUERY_SERVICE_CONFIG*) pServiceConfig, // cbBufferSize (DWORD) pServiceConfig)); // pcbBytesNeeded (DWORD*)
AutoBuf.h
/********************************************************************
模组:AutoBuf.h
通告:Copyright (c)2000 Jeffrey Richter
目的:此类别管理一个自动调整大小的资料缓冲器。
请参阅附录B
********************************************************************/
#pragma once // 编辑单元时需要包含此标头文件
///////////////////////////////////////////////////////////////////////////////
#include "../CmnHdr.h" // 请参阅附录A
///////////////////CautoBuf之C++ 样板类别说明///////////////////
/*
CautoBuf之C++ 样板类别实作安全的缓冲器型别,自动增加程序代码所需的大小。当物件被删除时
(通常是在您的程序代码超出框架范围以及从堆叠中被释放时),内存也会自动被释放。
使用范例:
// 用不明确的资料型别建立一个缓冲器,
// 此时缓冲器会以一个位元组的方式增加
CAutoBuf<PVOID> buf;
// 建立TCHARs型别的缓冲器,
// 此时缓冲器会以sizeof(TCHAR) 的方式增加
CAutoBuf<PTSTR, sizeof(TCHAR)>buf;
// 强迫此缓冲器为10位元组大
buf = 10;
*/
///////////////////////////////////////////////////////////////////////////////
// 此类别只用做CautoBuf样板类别的基础类别。
// 基础类别已经存在,使得样板类别实例分享一般程序代码的单一实例。
class CAutoBufBase {
public:
UINT Size() {return(* (PDWORD) PSize()); }
UINT Size(UINT uSize);
PUINT PSize() {
AdjustBuffer();
m_uNewSize = m_uCurrentSize;
return(&m_uNewSize);
}
void Free() {Reconstruct(); }
protected:
CAutoBufBase(PBYTE *ppbData, int nMult) {
m_nMult = nMult;
m_ppbBuffer = ppbData; // 取得保存缓冲器位址的类别,以允许
// 侦错器用已分类的型别工作
Reconstruct(TRUE);
}
virtual ~CAutoBufBase() { Free(); }
void Reconstruct(BOOL fFirstTime = FALSE);
PBYTE Buffer() {
AdjustBuffer();
return(*m_ppbBuffer);
}
private:
void AdjustBuffer();
private:
PBYTE* m_ppbBuffer; // 资料缓冲器的位址
Int m_nMult; // 为了增加缓冲器所使用的Multiplier(以位元组为单位)
UINT m_uNewSize; // 请求的缓冲器大小(在m_nMult单元中)
UINT m_uCurrentSize; // 实际的大小(在m_nMult单元中)
};
///////////////////////////////////////////////////////////////////////////////
template <class TYPE, int MULT = 1>
class CAutoBuf :private CAutoBufBase {
public:
CAutoBuf() : CAutoBufBase((PBYTE*) &m_pData, MULT) {}
void Free() { CAutoBufBase::Free(); }
public:
operator TYPE*() { return(Buffer()); }
UINT operator=(UINT uSize) { return(CAutoBufBase::Size(uSize)); }
operator UINT() { return(Size()); }
operator ULONG() { return(Size()); }
operator PUINT() { return(PSize()); }
operator PLONG() { return((PLONG)PSize()); }
operator PULONG() { return((PULONG)PSize()); }
operator PBYTE() { return((PBYTE)Buffer()); }
operator PVOID() { return((PVOID)Buffer()); }
TYPE& operator[](int nIndex) { return(*(Buffer() + nIndex)); }
private:
TYPE* Buffer() { return((TYPE*) CAutoBufBase::Buffer()); }
private:
TYPE* m_pData;
};
///////////////////////////////////////////////////////////////////////////////
#define GROWUNTIL(fail, func) /
do { /
if ((func) != (fail)) /
break; /
}while ((GetLastError() == ERROR_MORE_DATA) || /
(GetLastError() == ERROR_INSUFFICIENT_BUFFER));
///////////////////////////////////////////////////////////////////////////////
#ifdef AUTOBUF_IMPL
///////////////////////////////////////////////////////////////////////////////
void CAutoBufBase::Reconstruct(BOOL fFirstTime) {
if (!fFirstTime) {
if (*m_ppbBuffer != NULL)
HeapFree(GetProcessHeap(), 0, *m_ppbBuffer);
}
*m_ppbBuffer = NULL; // 取得没有指向资料缓冲器的类别
m_uNewSize = 0; // 缓冲器中没有资料
m_uCurrentSize = 0; // 缓冲器中没有资料
}
///////////////////////////////////////////////////////////////////////////////
UINT CAutoBufBase::Size(UINT uSize) {
// 设定缓冲器去要求m_nMult大小的位元组
if (uSize == 0) {
Reconstruct();
} else {
m_uNewSize = uSize;
AdjustBuffer();
}
return(m_uNewSize);
}
///////////////////////////////////////////////////////////////////////////////
void CAutoBufBase::AdjustBuffer(){
if (m_uCurrentSize < m_uNewSize) {
// 我们开始增加缓冲器大小
HANDLE hHeap = GetProcessHeap();
if (*m_ppbBuffer != NULL) {
// 我们已经重新设定缓冲器大小
PBYTE pNew = (PBYTE)
HeapReAlloc(hHeap, 0, *m_ppbBuffer, m_uNewSize * m_nMult);
if (pNew != NULL) {
m_uCurrentSize = m_uNewSize;
*m_ppbBuffer = pNew;
}
} else {
// 我们没有缓冲器,建立一个新的
*m_ppbBuffer = (PBYTE)HeapAlloc(hHeap, 0, m_uNewSize *m_nMult);
if (*m_ppbBuffer != NULL)
m_uCurrentSize = m_uNewSize;
}
}
}
///////////////////////////////////////////////////////////////////////////////
#endif // AUTOBUF_IMPL
///////////////////////////////// End of File /////////////////////////////////
| 列表B-3 AutoBuf.h标头文件 |
UI Layout C++ 类别(UILayout.h)
本书中许多范例应用程序允许使用者重新设定主视窗的大小。当然,重新设定主视窗大小会导致所有的子控制项被重新配置或重设大小。CUILayout C++ 类别列于列表B-4中,它封装了所有子控制项的重新配置与重设大小的逻辑。
每一个应用程序支援一个重设大小的主视窗,它们将会建立一个CUILayout物件。在该视窗的Dlg_OnInitDialog函数中,CUILayout物件的Initialize方法被呼叫去设定父视窗的handle和视窗的最小宽度与高度。然后CUILayout物件的AnchorControl或AnchorControls方法会被重覆地呼叫,以识别每一个子控制项视窗以及该控制项应如何依据父视窗而被固定。
当主视窗执行了它的Dlg_OnSize函数时,它只需去呼叫CUILayout物件的AdjustControls方法,传递主视窗之客户区(Client area)的宽度及高度给它即可。为了保持它与父视窗的的相对位置,AdjustControls方法将会自动地重新配置及重设所有子控制项大小。
当主视窗执行了Dlg_OnGetMinMaxInfo函数时,它需要去呼叫CUILayout物件的HandleMinMax方法。此方法设定了MINMAXINFO结构的成员,让使用者不会因为将视窗缩得太小而遮住了任何的子控制项。
UILayout.h
/********************************************************************
模组: UILayout.h
通告: Copyright (c)2000 Jeffrey Richter
目的: 当一个父视窗被重设大小后,此类别会管理子视窗的位置及大小
请参阅附录B
********************************************************************/
#pragma once // 编辑单元时,需包含此标头档
///////////////////////////////////////////////////////////////////////////////
#include "../CmnHdr.h" //请参阅附录A
///////////////////////////////////////////////////////////////////////////////
class CUILayout {
public:
enum ANCHORPOINT {
AP_TOPLEFT,
AP_TOPMIDDLE,
AP_TOPRIGHT,
AP_MIDDLERIGHT,
AP_BOTTOMRIGHT,
AP_BOTTOMMIDDLE,
AP_BOT TOMLEFT,
AP_MIDDLELEFT,
AP_CENTER
};
public:
void Initialize(HWND hwndParent, int nMinWidth = 0, int nMinHeight = 0);
BOOL AnchorControl(ANCHORPOINT apUpperLeft, ANCHORPOINT apLowerRight, int nID,BOOL
fRedraw = FALSE);
BOOL AnchorControls(ANCHORPOINT apUpperLeft, ANCHORPOINT apLowerRight, BOOL fRedraw, ...);
BOOL AdjustControls(int cx, int cy);
void HandleMinMax(PMINMAXINFO pMinMax)
{ pMinMax->ptMinTrackSize = m_ptMinParentDims; }
private:
struct CONTROL {
int m_nID;
BOOL m_fRedraw;
ANCHORPOINT m_apUpperLeft;
ANCHORPOINT m_apLowerRight;
POINT m_ptULDelta;
POINT m_ptLRDelta;
};
private:
void PixelFromAnchorPoint(ANCHORPOINT ap,
int cxParent, int cyParent, PPOINT ppt);
private:
CONTROL m_CtrlInfo[255]; // 允许一个对话方块样板中的最大控制项
int m_nNumControls;
HWND m_hwndParent;
POINT m_ptMinParentDims;
};
///////////////////////////////////////////////////////////////////////////////
#ifdef UILAYOUT_IMPL
///////////////////////////////////////////////////////////////////////////////
void CUILayout::Initialize(HWND hwndParent, int nMinWidth, int nMinHeight) {
m_hwndParent = hwndParent;
m_nNumControls = 0;
if ((nMinWidth == 0) || (nMinHeight == 0)) {
RECT rc;
GetWindowRect(m_hwndParent, &rc);
m_ptMinParentDims.x = rc.right - rc.left;
m_ptMinParentDims.y = rc.bottom - rc.top;
}
if (nMinWidth != 0)m_ptMinParentDims.x = nMinWidth;
if (nMinHeight != 0)m_ptMinParentDims.y = nMinHeight;
}
///////////////////////////////////////////////////////////////////////////////
BOOL CUILayout::AnchorControl(ANCHORPOINT apUpperLeft,
ANCHORPOINT apLowerRight, int nID, BOOL fRedraw) {
BOOL fOk = FALSE;
try {
{
HWND hwndControl = GetDlgItem(m_hwndParent, nID);
if (hwndControl == NULL)goto leave;
if (m_nNumControls >= chDIMOF(m_CtrlInfo)) goto leave;
m_CtrlInfo[m_nNumControls].m_nID = nID;
m_CtrlInfo[m_nNumControls].m_fRedraw = fRedraw;
m_CtrlInfo[m_nNumControls].m_apUpperLeft = apUpperLeft;
m_CtrlInfo[m_nNumControls].m_apLowerRight = apLowerRight;
RECT rcControl;
GetWindowRect(hwndControl, &rcControl); // 遮住控制项的coords
// 转换coords至相对父视窗的类型
MapWindowPoints(HWND_DESKTOP, m_hwndParent, (PPOINT) &rcControl, 2);
RECT rcParent;
GetClientRect(m_hwndParent, &rcParent);
POINT pt;
PixelFromAnchorPoint(apUpperLeft, rcParent.right, rcParent.bottom, &pt);
m_CtrlInfo[m_nNumControls].m_ptULDelta.x = pt.x - rcControl.left;
m_CtrlInfo[m_nNumControls].m_ptULDelta.y = pt.y - rcControl.top;
PixelFromAnchorPoint(apLowerRight, rcParent.right, rcParent.bottom, &pt);
m_CtrlInfo[m_nNumControls].m_ptLRDelta.x = pt.x - rcControl.right;
m_CtrlInfo[m_nNumControls].m_ptLRDelta.y = pt.y - rcControl.bottom;
m_nNumControls++;
fOk = TRUE;
}
leave:;
}
catch (...) {
}
chASSERT(fOk);
return(fOk);
}
///////////////////////////////////////////////////////////////////////////////
BOOL CUILayout::AnchorControls(ANCHORPOINT apUpperLeft,
ANCHORPOINT apLowerRight, BOOL fRedraw, ...) {
BOOL fOk = TRUE;
va_list arglist;
va_start(arglist,fRedraw);
int nID = va_arg(arglist,int);
while (fOk && (nID != -1)) {
fOk = fOk && AnchorControl(apUpperLeft, apLowerRight, nID, fRedraw);
nID = va_arg(arglist, int);
}
va_end(arglist);
return(fOk);
}
///////////////////////////////////////////////////////////////////////////////
BOOL CUILayout::AdjustControls(int cx, int cy) {
BOOL fOk = FALSE;
// 建立由已存在之所有控制项组成的区域
HRGN hrgnPaint = CreateRectRgn(0, 0, 0, 0);
for (int n = 0; n <m_nNumControls; n++) {
HWND hwndControl = GetDlgItem(m_hwndParent, m_CtrlInfo[n].m_nID);
RECT rcControl;
GetWindowRect(hwndControl, &rcControl); // Screen coords of control
// 转换coords至相对父视窗的类别
MapWindowPoints(HWND_DESKTOP, m_hwndParent, (PPOINT) &rcControl, 2);
HRGN hrgnTemp = CreateRectRgnIndirect(&rcControl);
CombineRgn(hrgnPaint, hrgnPaint, hrgnTemp, RGN_OR);
DeleteObject(hrgnTemp);
}
for (n = 0; n < m_nNumControls; n++) {
// 取得控制项的上/左边界,并使用父视窗的宽度/高度
RECT rcControl;
PixelFromAnchorPoint(m_CtrlInfo[n].m_apUpperLeft,
cx, cy, (PPOINT) &rcControl);
rcControl.left -= m_CtrlInfo[n].m_ptULDelta.x;
rcControl.top -= m_CtrlInfo[n].m_ptULDelta.y;
// 取得控制项的下/右边界,并使用父视窗的宽度/高度
PixelFromAnchorPoint(m_CtrlInfo[n].m_apLowerRight,
cx, cy, (PPOINT) &rcControl.right);
rcControl.right -= m_CtrlInfo[n].m_ptLRDelta.x;
rcControl.bottom -= m_CtrlInfo[n].m_ptLRDelta.y;
// 控制项的位置/大小
HWND hwndControl = GetDlgItem(m_hwndParent, m_CtrlInfo[n].m_nID);
MoveWindow(hwndControl, rcControl.left, rcControl.top,
rcControl.right - rcControl.left,
rcControl.bottom - rcControl.top, FALSE);
if (m_CtrlInfo[n].m_fRedraw) {
InvalidateRect(hwndControl, NULL, FALSE);
} else {
// 移除被控制项的新位置占用的区域
HRGN hrgnTemp = CreateRectRgnIndirect(&rcControl);
CombineRgn(hrgnPaint, hrgnPaint, hrgnTemp, RGN_DIFF);
DeleteObject(hrgnTemp);
// 使控制项自己重画
InvalidateRect(hwndControl, NULL, TRUE);
SendMessage(hwndControl, WM_NCPAINT, 1, 0);
UpdateWindow(hwndControl);
}
}
// 画出对话方块之客户区的新显示部份
HDC hdc = GetDC(m_hwndParent);
HBRUSH hbrColor = CreateSolidBrush(GetSysColor(COLOR_3DFACE));
FillRgn(hdc, hrgnPaint, hbrColor);
DeleteObject(hbrColor);
ReleaseDC(m_hwndParent, hdc);
DeleteObject(hrgnPaint);
return(fOk);
}
///////////////////////////////////////////////////////////////////////////////
void CUILayout::PixelFromAnchorPoint(ANCHORPOINT ap,
int cxParent, int cyParent, PPOINT ppt){
ppt->x = ppt->y = 0;
switch (ap) {
case AP_TOPMIDDLE:
case AP_CENTER:
case AP_BOTTOMMIDDLE:
ppt->x = cxParent / 2;
break;
case AP_TOPRIGHT:
case AP_MIDDLERIGHT:
case AP_BOTTOMRIGHT:
ppt->x = cxParent;
break;
}
switch|(ap){
case AP_MIDDLELEFT:
case AP_CENTER:
case AP_MIDDLERIGHT:
ppt->y = cyParent / 2;
break;
case AP_BOTTOMLEFT:
case AP_BOTTOMMIDDLE:
case AP_BOTTOMRIGHT:
ppt->y = cyParent;
break;
}
}
///////////////////////////////////////////////////////////////////////////////
#endif // UILAYOUT_IMPL
///////////////////////////////// End of File /////////////////////////////////
| 列表B-4 UILayout.h标头文件 |
I/O Completion Port C++ 类别(IOCP.h)
列表B-5中为简单的CIOCP C++ 类别,它只包装了Windows之I/O完成连接埠(I/O completion port)的核心物件函数。所有的方法皆为内嵌的,所以当您使用此类别时不会有效能损失的情形。此函数的包装允许您用一个更逻辑的介面来操作一个I/O完成连接埠物件。
IOCP.h
/********************************************************************
模组:IOCP.h
通告:Copyright (c)2000 Jeffrey Richter
目的:此类别包装了一个I/O Completion Port
请参阅附录B
********************************************************************/
#pragma once // 在编辑单元时必须包含此标头档
///////////////////////////////////////////////////////////////////////////////
#include "../CmnHdr.h" // 请参阅附录A
///////////////////////////////////////////////////////////////////////////////
class CIOCP {
public:
CIOCP(int nMaxConcurrency = -1) {
m_hIOCP = NULL;
if (nMaxConcurrency != -1)
(void) Create(nMaxConcurrency);
}
~CIOCP() {
if (m_hIOCP != NULL)
chVERIFY(CloseHandle(m_hIOCP));
}
BOOL Create(int nMaxConcurrency = 0) {
m_hIOCP = CreateIoCompletionPort(
INVALID_HANDLE_VALUE, NULL, 0, nMaxConcurrency);
chASSERT(m_hIOCP != NULL);
return(m_hIOCP != NULL);
}
BOOL AssociateDevice(HANDLE hDevice, ULONG_PTR CompKey) {
BOOL fOk = (CreateIoCompletionPort(hDevice, m_hIOCP, CompKey, 0)
== m_hIOCP);
chASSERT(fOk);
return(fOk);
}
BOOL AssociateSocket(SOCKET hSocket, ULONG_PTR CompKey) {
return(AssociateDevice((HANDLE)hSocket, CompKey));
}
BOOL PostStatus(ULONG_PTR CompKey, DWORD dwNumBytes = 0,
OVERLAPPED* po = NULL) {
BOOL fOk = PostQueuedCompletionStatus(m_hIOCP, dwNumBytes, CompKey, po);
chASSERT(fOk);
return(fOk);
}
BOOL GetStatus(ULONG_PTR* pCompKey, PDWORD pdwNumBytes,
OVERLAPPED** ppo, DWORD dwMilliseconds = INFINITE) {
return(GetQueuedCompletionStatus(m_hIOCP, pdwNumBytes,
pCompKey, ppo,dwMilliseconds));
}
private:
HANDLE m_hIOCP;
};
///////////////////////////////// End of File /////////////////////////////////
| 列表B-5 IOCP.h标头文件 |
Security Information C++ 类别(SecInfo.h)
CsecInfo类别列于列表B-6中,它是一个非常小的IsecurityInformation之COM介面包装,即那些为了编辑一个物件的安全性而用来呼叫EditSecurity至程序的一般对话方块。CsecInfo类别仅实作Iunknown介面所需的成员,所以您的程序代码不需担心在使用EditSecurity时与COM相关的部份。
如果您选择使用CsecInfo类别,那么您应该从它取得您所拥有的类别并实作基础类别的纯虚拟函数。
SecInfo.h
/********************************************************************
模组: SecInfo.h
通告: Copyright (c)2000 Jeffrey Richter
目的: 此类别包装了那些被用来呼叫EditSecurity函数中使用的IsecurityInformation介面。
请参阅附录B
********************************************************************/
#pragma once // 一旦编辑单元时需包含此标头档
///////////////////////////////////////////////////////////////////////////////
#include "../CmnHdr.h" // 请参阅附录A
#include <aclapi.h>
#include <aclui.h>
///////////////////////////////////////////////////////////////////////////////
class CSecInfo:public ISecurityInformation {
public:
CSecInfo() { m_nRef = 1; m_fMod = FALSE; }
BOOL IsModified() { return(m_fMod); }
protected:
virtual ~CSecInfo() {}
protected:
void Modified() { m_fMod = TRUE; }
static GUID m_guidNULL;
static SI_ACCESS m_siAccessAllRights[];
private:
ULONG m_nRef;
BOOL m_fMod;
public:
HRESULT WINAPI QueryInterface(REFIID riid, PVOID* ppvObj);
ULONG WINAPI AddRef();
ULONG WINAPI Release();
HRESULT UseStandardAccessRights(const GUID* pguidObjectType, DWORD dwFlags,
PSI_ACCESS* ppAccess, ULONG* pcAccesses, ULONG* piDefaultAccess);
protected:
HRESULT WINAPI GetObjectInformation(PSI_OBJECT_INFO pObjectInfo) = 0;
HRESULT WINAPI GetSecurity(SECURITY_INFORMATION RequestedInformation,
PSECURITY_DESCRIPTOR* ppSecurityDescriptor, BOOL fDefault) = 0;
HRESULT WINAPI SetSecurity(SECURITY_INFORMATION SecurityInformation,
PSECURITY_DESCRIPTOR pSecurityDescriptor) = 0;
HRESULT WINAPI GetAccessRights(const GUID* pguidObjectType,
DWORD dwFlags, // SI_EDIT_AUDITS, SI_EDIT_PROPERTIES
PSI_ACCESS *ppAccess, ULONG *pcAccesses, ULONG *piDefaultAccess) = 0;
HRESULT WINAPI MapGeneric(const GUID *pguidObjectType,
UCHAR *pAceFlags, ACCESS_MASK *pMask) = 0;
HRESULT WINAPI GetInheritTypes(PSI_INHERIT_TYPE* ppInheritTypes,
ULONG *pcInheritTypes);
HRESULT WINAPI PropertySheetPageCallback(HWND hwnd, UINT uMsg,
SI_PAGE_TYPE uPage);
PSECURITY_DESCRIPTOR
LocalAllocSDCopy(PSECURITY_DESCRIPTOR psd);
};
///////////////////////////////////////////////////////////////////////////////
#ifdef SECINFO_IMPL
///////////////////////////////////////////////////////////////////////////////
GUID CSecInfo::m_guidNULL = GUID_NULL;
#define RIGHT(code, text, fGeneral, fSpecific) /
{&m_guidNULL, code, L ##text, /
(0 |(fGeneral ?SI_ACCESS_GENERAL : 0) | /
(fSpecific ?SI_ACCESS_SPECIFIC : 0)) }
static SI_ACCESS CSecInfo::m_siAccessAllRights[] == {
RIGHT(DELETE, "DELETE", TRUE, FALSE),
RIGHT(READ_CONTROL, "READ_CONTROL", TRUE, FALSE),
RIGHT(WRITE_DAC, "WRITE_DAC", TRUE, FALSE),
RIGHT(WRITE_OWNER, "WRITE_OWNER", TRUE, FALSE),
RIGHT(SYNCHRONIZE, "SYNCHRONIZE", TRUE, FALSE),
RIGHT(STANDARD_RIGHTS_REQUIRED, "STANDARD_RIGHTS_REQUIRED", TRUE, FALSE),
RIGHT(STANDARD_RIGHTS_READ, "STANDARD_RIGHTS_READ", TRUE, FALSE),
RIGHT(STANDARD_RIGHTS_WRITE, "STANDARD_RIGHTS_WRITE", TRUE, FALSE),
RIGHT(STANDARD_RIGHTS_EXECUTE, "STANDARD_RIGHTS_EXECUTE", TRUE, FALSE),
RIGHT(STANDARD_RIGHTS_ALL, "STANDARD_RIGHTS_ALL", TRUE, FALSE),
RIGHT(SPECIFIC_RIGHTS_ALL, "SPECIFIC_RIGHTS_ALL", TRUE, FALSE),
RIGHT(ACCESS_SYSTEM_SECURITY, "ACCESS_SYSTEM_SECURITY", TRUE, FALSE),
RIGHT(MAXIMUM_ALLOWED, "MAXIMUM_ALLOWED", TRUE, FALSE),
};
///////////////////////////////////////////////////////////////////////////////
PSECURITY_DESCRIPTOR CSecInfo::LocalAllocSDCopy(PSECURITY_DESCRIPTOR pSD) {
DWORD dwSize = 0;
SECURITY_DESCRIPTOR_CONTROL sdc;
PSECURITY_DESCRIPTOR pSDNew = NULL;
DWORD dwVersion;
__try {
if (pSD == NULL) __leave;
if (!GetSecurityDescriptorControl(pSD, &sdc, &dwVersion)) __leave;
if ((sdc & SE_SELF_RELATIVE) != 0) {
dwSize = GetSecurityDescriptorLength(pSD);
if (dwSize == 0) __leave;
pSDNew = LocalAlloc(LPTR, dwSize);
if (pSDNew == NULL)__leave;
CopyMemory(pSDNew, pSD, dwSize);
} else {
if (MakeSelfRelativeSD(pSD, NULL, &dwSize)) __leave;
else if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) __leave;
pSDNew = LocalAlloc(LPTR, dwSize);
if (pSDNew == NULL) __leave;
if (!MakeSelfRelativeSD(pSD, pSDNew, &dwSize)) {
LocalFree(pSDNew);
pSDNew = NULL;
}
}
}
__ finally {
}
return(pSDNew);
}
///////////////////////////////////////////////////////////////////////////////
HRESULT CSecInfo::QueryInterface(REFIID riid, PVOID* ppvObj) {
HRESULT hr = E_NOINTERFACE;
if ((riid == IID_ISecurityInformation) || (riid == IID_IUnknown)) {
*ppvObj = this;
AddRef();
hr = S_OK;
}
return(hr);
}
///////////////////////////////////////////////////////////////////////////////
ULONG CSecInfo::AddRef() {
m_nRef++;
return(m_nRef);
}
///////////////////////////////////////////////////////////////////////////////
ULONG CSecInfo::Release() {
ULONG nRef =--m_nRef;
if (m_nRef == 0)
delete this;
return(nRef);
}
///////////////////////////////////////////////////////////////////////////////
HRESULT CSecInfo::UseStandardAccessRights(const GUID* pguidObjectType,
DWORD dwFlags, PSI_ACCESS* ppAccess, ULONG* pcAccesses,
ULONG* piDefaultAccess) {
*ppAccess = m_siAccessAllRights;
*pcAccesses = chDIMOF(m_siAccessAllRights);
*piDefaultAccess = 0;
return(S_OK);
}
///////////////////////////////////////////////////////////////////////////////
HRESULT CSecInfo::GetInheritTypes(PSI_INHERIT_TYPE*ppInheritTypes,
ULONG* pcInheritTypes) {
*ppInheritTypes = NULL;
*pcInheritTypes = 0;
return(S_OK);
}
///////////////////////////////////////////////////////////////////////////////
HRESULT CSecInfo::PropertySheetPageCallback(HWND hwnd, UINT uMsg,
SI_PAGE_TYPE uPage) {
return(S_OK);
}
///////////////////////////////////////////////////////////////////////////////
#pragma comment(lib, "ACLUI.lib") // 强迫连结此程序库
///////////////////////////////////////////////////////////////////////////////
#endif // SECINFO_IMPL
///////////////////////////////// End of File /////////////////////////////////
| 列表B-6 SecInfo.h标头文件
|
本文介绍了一套专为简化Windows程序开发而设计的C++辅助类库,包括确保资源清理、打印缓冲管理、自动缓冲调整等功能类,以及窗口布局管理和I/O完成端口等实用工具。
1322

被折叠的 条评论
为什么被折叠?



