第一部分内容不多:1.调用API失败之后如何查看发生了什么错误。2.安全的使用字符串函数,避免发生读写越界的情况。3.内核对象和内核句柄的基本概念,内核对象在进程中表现为句柄只能通过API操作句柄对象,无法直接访问。
1.对于系统API而言通常不会抛出异常,而采用放回值来报告函数调用失败,通常有如下几种返回值类型:
API函数发生时调用SetLastError把错误类型放到线程局部存储区中并返回一个无效值,要获取具体失败的原因,可以调用GetLastError函数取出错误码,再调用FormatMessage来解析出错误类型的具体信息。通常需要在失败发生的第一时间就调用GetLastError以防止被其他失败的调用修改如果在调试器中可以在watch窗口中输入$err,hr实时监控错误代码。同样用户也可以调用SetLastError函数来设置自己的错误码(需要大于255,小于的部分在windows.h中定义了)。一下代码展示了如果获取到错误码的说明:
case IDOK:
// Get the error code
DWORD dwError = GetDlgItemInt(hwnd, IDC_ERRORCODE, NULL, FALSE);
HLOCAL hlocal = NULL; // Buffer that gets the error message string
// Use the default system locale since we look for Windows messages.
// Note: this MAKELANGID combination has 0 as value
DWORD systemLocale = MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL);
// Get the error code's textual description
BOOL fOk = FormatMessage(
FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS |
FORMAT_MESSAGE_ALLOCATE_BUFFER,
NULL, dwError, systemLocale,
(PTSTR) &hlocal, 0, NULL);
if (!fOk) {
// Is it a network-related error?
HMODULE hDll = LoadLibraryEx(TEXT("netmsg.dll"), NULL,
DONT_RESOLVE_DLL_REFERENCES);
if (hDll != NULL) {
fOk = FormatMessage(
FORMAT_MESSAGE_FROM_HMODULE | FORMAT_MESSAGE_IGNORE_INSERTS |
FORMAT_MESSAGE_ALLOCATE_BUFFER,
hDll, dwError, systemLocale,
(PTSTR) &hlocal, 0, NULL);
FreeLibrary(hDll);
}
}
if (fOk && (hlocal != NULL)) {
SetDlgItemText(hwnd, IDC_ERRORTEXT, (PCTSTR) LocalLock(hlocal));
LocalFree(hlocal);
} else {
SetDlgItemText(hwnd, IDC_ERRORTEXT,
TEXT("No text found for this error number."));
}
break;
2.对于编码和字符操作而言,现在基本上都采用了unicode编码和缓冲区安全的字符操作函数,实在无需赘言MultiByteToWideChar和WideCharToMultiByte函数配合用于字符编码的转换也是十分方便,网上代码大把。
3.windows系统严格的将进程为用户态和内核态,在0到2G的内存空间中为用户态地址,大于2G为内核态地址,内核态的地址实际上是系统的地址是共用的。用户态要想操作内核态对象必须通过系统提供的API,内核态中的对象拥有者为系统,而在用户进程中表现为对象句柄,句柄只在当前进程中有意义可以视为内核对象的代号,在调用系统API时传入句柄以指明所操作的对象。故而内核对象可以在进程间共享。一般而言用户态获得(创建或者打开)内核对象需要指定一个SECURITY_ATTRIBUTES来说明访问的安全性(通常传入NULL使用默认安全性)。在使用完内核对象后需要调用CloseHandle来关闭内核对象,内核对象在高2GB内存中,与系统资源一样可以在进程间共享,所以所谓创建和关闭内核对象只不过是在内核对象的引用计数上增减1,只有引用计数为0时系统才会释放资源。内核对象有如下三种共享方式:
1.句柄继承,在创建进程对象时指定的SECURITY_ATTRIBUTES结构中令bInheritHandle为true,则指明了该对象可以被继承。调用CreatePress创建子进程时参数bInheritHandles设置为true,则会将所有可以被继承的句柄原封不动的复制到子进程中。这样我们可以在子进程中使用继承的句柄来访问内核对象。但是存在一个问题我们如何在子进程中知道哪个句柄对应与那个内核对象呢?这就需要我们在创建进程的过程中通过传递参数或者进程创建后通过进程间通信的方式来通知子进程了。
2.使用命名对象,在创建内核的想的时候给对象指定一个名称,如果在其他进程中同样创建一个名称相同的对象,则系统不会真正创建一个新的内核对象,而是创建一个句柄指向已经存在的对象,我们可以通过调用Create函数后马上调用GetLastError查看返回值是否为ERROR_ALREADY_EXISTS来检查是打开了一个新的对象还是创建了一个对象。若能够确保对象存在的情况下直接调用Open函数来打开内核对象也是可以的。由于对象名称可能会重复造成不必要的问题,windows的处理方式与c++处理类名重复的方式一样引入了命名空间的概念,先创建一个私有的命名空间再在空间中创建命名对象,只有bournd和sid一致时才可以访问这个私有空间中的对象。除了私有空间还有gobal,local,session三种公开的空间。
3.使用DuplicateHandle在进程间复制句柄,当两个进程不为父子关系时,通过调用DuplicateHandle函数将源进程中的一个句柄复制给目标进程。与句柄继承一样存在目标进程不知道哪个句柄代表了那个内核对象,需要进程间通信去通知目标句柄。
私有空间中的命名对象是最常用的共享方式:
void CheckInstances() {
// Create the boundary descriptor
g_hBoundary = CreateBoundaryDescriptor(g_szBoundary, 0);
// Create a SID corresponding to the Local Administrator group
BYTE localAdminSID[SECURITY_MAX_SID_SIZE];
PSID pLocalAdminSID = &localAdminSID;
DWORD cbSID = sizeof(localAdminSID);
if (!CreateWellKnownSid(
WinBuiltinAdministratorsSid, NULL, pLocalAdminSID, &cbSID)
) {
AddText(TEXT("AddSIDToBoundaryDescriptor failed: %u\r\n"),
GetLastError());
return;
}
// Associate the Local Admin SID to the boundary descriptor
// --> only applications running under an administrator user
// will be able to access the kernel objects in the same namespace
if (!AddSIDToBoundaryDescriptor(&g_hBoundary, pLocalAdminSID)) {
AddText(TEXT("AddSIDToBoundaryDescriptor failed: %u\r\n"),
GetLastError());
return;
}
// Create the namespace for Local Administrators only
SECURITY_ATTRIBUTES sa;
sa.nLength = sizeof(sa);
sa.bInheritHandle = FALSE;
if (!ConvertStringSecurityDescriptorToSecurityDescriptor(
TEXT("D:(A;;GA;;;BA)"),
SDDL_REVISION_1, &sa.lpSecurityDescriptor, NULL)) {
AddText(TEXT("Security Descriptor creation failed: %u\r\n"), GetLastError());
return;
}
g_hNamespace = CreatePrivateNamespace(&sa, g_hBoundary, g_szNamespace);
// Don't forget to release memory for the security descriptor
LocalFree(sa.lpSecurityDescriptor);
// Check the private namespace creation result
DWORD dwLastError = GetLastError();
if (g_hNamespace == NULL) {
// Nothing to do if access is denied
// --> this code must run under a Local Administrator account
if (dwLastError == ERROR_ACCESS_DENIED) {
AddText(TEXT("Access denied when creating the namespace.\r\n"));
AddText(TEXT(" You must be running as Administrator.\r\n\r\n"));
return;
} else {
if (dwLastError == ERROR_ALREADY_EXISTS) {
// If another instance has already created the namespace,
// we need to open it instead.
AddText(TEXT("CreatePrivateNamespace failed: %u\r\n"), dwLastError);
g_hNamespace = OpenPrivateNamespace(g_hBoundary, g_szNamespace);
if (g_hNamespace == NULL) {
AddText(TEXT(" and OpenPrivateNamespace failed: %u\r\n"),
dwLastError);
return;
} else {
g_bNamespaceOpened = TRUE;
AddText(TEXT(" but OpenPrivateNamespace succeeded\r\n\r\n"));
}
} else {
AddText(TEXT("Unexpected error occured: %u\r\n\r\n"),
dwLastError);
return;
}
}
}
// Try to create the mutex object with a name
// based on the private namespace
TCHAR szMutexName[64];
StringCchPrintf(szMutexName, _countof(szMutexName), TEXT("%s\\%s"),
g_szNamespace, TEXT("Singleton"));
g_hSingleton = CreateMutex(NULL, FALSE, szMutexName);
if (GetLastError() == ERROR_ALREADY_EXISTS) {
// There is already an instance of this Singleton object
AddText(TEXT("Another instance of Singleton is running:\r\n"));
AddText(TEXT("--> Impossible to access application features.\r\n"));
} else {
// First time the Singleton object is created
AddText(TEXT("First instance of Singleton:\r\n"));
AddText(TEXT("--> Access application features now.\r\n"));
}
}