SystemParametersInfo 關於VERIFY 出錯

SystemParametersInfo 關於VERIFY 出錯

 源代碼:

int i,j,numitems,maxlength;
 CString string,newstring;
 CSize t;
 CFont m_fontMenu;
 LOGFONT m_lf;
 
 ZeroMemory ((PVOID) &m_lf,sizeof (LOGFONT));
 NONCLIENTMETRICS nm;
 nm.cbSize = sizeof (NONCLIENTMETRICS) - sizeof(nm.iPaddedBorderWidth);
 VERIFY (SystemParametersInfo (SPI_GETNONCLIENTMETRICS,nm.cbSize,&nm,0)); 
 m_lf =  nm.lfMenuFont;
 m_fontMenu.CreateFontIndirect (&m_lf);

錯誤原因:

這段代碼本來沒錯,錯在平臺上.

nm.cbSize = sizeof (NONCLIENTMETRICS) - sizeof(nm.iPaddedBorderWidth);這個是高版本.通常所說的VISTA版本
nm.cbSize = sizeof (NONCLIENTMETRICS) ; 這個是低版本,通常所說的0x0501

看似是系統問題.其實這2個代碼都能在XP上運行,也都能在VS2008/VS2010上編譯,

低於這裏所說的系統與VS編譯器2個版本的,就會出問題.瞭解即可.

解決辦法:

(一)如果你想用nm.cbSize = sizeof (NONCLIENTMETRICS) ; 這個是低版本,通常所說的0x0501,就在stdafx.h中

#pragma once

#ifndef _SECURE_ATL
#define _SECURE_ATL 1
#endif

#ifndef VC_EXTRALEAN
#define VC_EXTRALEAN            // 從 Windows 頭中排除極少使用的資料
#endif

這個語句的後面加上以下2句

#ifndef WINVER    // 允許使用特定於 Windows XP 或更高版本的功能。
#define WINVER 0x0501  // 將此值更改爲相應的值,以適用於 Windows 的其他版本。
#endif

#ifndef _WIN32_WINNT  // 允許使用特定於 Windows XP 或更高版本的功能。
#define _WIN32_WINNT 0x0501 // 將此值更改爲相應的值,以適用於 Windows 的其他版本。
#endif  

(2)如果你想用nm.cbSize = sizeof (NONCLIENTMETRICS) - sizeof(nm.iPaddedBorderWidth);這個是高版本.通常所說的VISTA版本
就什麼也不加,如果你的stdaftx.h裏有加的話,就把刪除掉就好了.採用默認的(系統與VS編譯器版本上面已提過了)

以下是隨便寫的一個獲取當前系統版本的接口,可以創建一個MFC應用程序,直接拷貝過去就能用

/////////////////////
   // TODO: 在此添加控件通知處理程序代碼
 //CPoint point;
 //GetCursorPos(&point);
 /////////////////////注意系統版本檢測函數,專門寫成一個接口////////////
 OSVERSIONINFO osvi;
 osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);

 //獲取系統的版本信息。
 ::GetVersionEx(&osvi);
 bool bIsWindowsXPorLater = (osvi.dwMajorVersion > 5) ||
  ( (osvi.dwMajorVersion == 5) && (osvi.dwMinorVersion >= 1) );

 CString str;
 str.Format("%d=%d=%d=%d=%d",osvi.dwBuildNumber,osvi.dwMajorVersion,osvi.dwMinorVersion,osvi.dwOSVersionInfoSize,osvi.dwOSVersionInfoSize,osvi.dwPlatformId);
    AfxMessageBox(str);
 //顯示當前的版本。
 if (bIsWindowsXPorLater)
 {
  OutputDebugString(_T("Windows XP或更新版本!/r/n"));
 }
 else
 {
  OutputDebugString(_T("Windows XP以前版本!/r/n"));               
 }    

備註1:另外期間找了一些很不錯的資料,也順便拷貝過來供參考了

 // create fonts
 NONCLIENTMETRICS info; 
 info.cbSize = sizeof(info);
 ::SystemParametersInfo(SPI_GETNONCLIENTMETRICS, sizeof(info), &info, 0);

 if (!_fontHorzMenu.CreateFontIndirect(&info.lfMenuFont))
  return FALSE;

DWORD dw = GetLastError();

上面這段代碼在VC 6.0 中工作正常,但是在VC 2008 裏 SystemParametersInfo 返回 0,而 GetLastError 返回的卻是 0
簡單訂正如下:
info.cbSize = sizeof(info) - sizeof(info.iPaddedBorderWidth);
  實際是由於從 Vista 和 Windows Server 2008 開始 NONCLIENTMETRICS 在最後增加了iPaddedBorderWidth字段,如果你的程序打算同時支持 vista 或 XP ,Windows 2000, Windows Server 2003,那麼應該先調用 GetVersionEx 檢測Windows版本,然後決定是否需要減去 sizeof (ncms.iPaddedBorderWidth)
備註2:另外對爲什麼一個沒加,而另一個加了#define WINVER 0x0501就好了.這裏也順便說下原因:以下是從網上摘抄來的.
VC中使用高版本API的方法——undeclared identifier引發的血案
2010-10-09 20:32

在VC6中使用GetListBoxInfo這個API函數的時候編譯提示: 'GetListBoxInfo' : undeclared identifier

他已經將GetListBoxInfo所在的頭文件WinUser.h直接或者間接的包含進來了,打開WinUser.h文件,看到GetListBoxInfo就活生生的躺在那裏呢,爲什麼還是報“'GetListBoxInfo' : undeclared identifier”呢?難道VC眼瞎了嗎???
一開始就解決GetListBoxInfo的這個問題可能比較麻煩,咱們先來看另外一個API函數:LockWorkStation,他就在GetListBoxInfo函數的定義下面:

WINUSERAPI   BOOL WINAPI   LockWorkStation(VOID);這個函數用來鎖定操作系統。LockWorkStation安靜的的躺在WinUser.h中,按理來說應該能輕鬆的調用,因此我在文件中調用LockWorkStation卻無情的報告了“LockWorkStation undeclared identifier”。爲什麼呢?
我們仔細看LockWorkStation函數的定義,看更大範圍的:

代碼:#if(_WIN32_WINNT >= 0x0500)
WINUSERAPI
BOOL
WINAPI
LockWorkStation(
VOID);
#endif /* _WIN32_WINNT >= 0x0500 */

對C/C++比較熟悉的同學應該知道“#if”是條件編譯的意思,也就是說被“#if”、“#endif”包圍的代碼只有滿足“#if”的條件的時候纔會被編譯,這個if判斷是在編譯的時候執行的,而不是運行的時候執行的。

_WIN32_WINNT是一個宏定義,它表示Windows的版本,它有很多取值,取值列表如下:

代碼:Windows XP _WIN32_WINNT>=0x0501    
Windows 2000 _WIN32_WINNT>=0x0500    
Windows NT 4.0   _WIN32_WINNT>=0x0400    

因此“#if(_WIN32_WINNT >= 0x0500)”這句話的意思是隻有Windows的版本大於Windows 2000 的時候LockWorkStation函數的定義纔會起作用,纔會被編譯。
爲什麼要根據操作系統的版本決定函數的定義是否編譯呢?道理很簡單,因爲有的API函數是高版本的Windows下才提供的,低版本的Windows沒有那個API函數,所以需要根據操作系統進行判斷。

不對呀!我的操作系統是WindowsXP,確實比Windows 2000高呀,爲什麼不能編譯呢?
原因就是“_WIN32_WINNT”並不是像大家想象的那樣真的會代表當前編譯程序電腦的操作系統的版本,它需要程序員去指定,當然VC也給了“_WIN32_WINNT”一個默認值,不過這個默認值是VC從微軟那發佈的時候微軟定義的:

代碼:#ifndef _WIN32_WINNT
#define _WIN32_WINNT 0x0400
#endif

VC6是1998年發佈的,那時候Windows98剛發佈,微軟當時肯定認爲大部分人還是Windows NT 4.0操作系統,爲了“主流”,微軟就把VC6中_WIN32_WINNT宏的默認值設置成了Windows NT 4.0的0x0400

時光荏苒,日月如梭,現在已經是2010年了,主流的Windows桌面操作系統已經是WindowsXP了,還有相當一部分人用上了Vista甚至Windows7,那麼“ _WIN32_WINNT 0x0400”這個默認值已經Out了!
那麼咱們怎麼修改_WIN32_WINNT的默認值呢?打開StdAfx.h文件,在文件最開始加入“#define _WIN32_WINNT 0x0501”就ok了,也就是設置爲WindowsXP。再編譯LockWorkStation函數就通過了!!!

暈呀,怎麼GetListBoxInfo函數還是不能編譯通過???還是“undeclared identifier”,難道LockWorkStation是“這個可以有”,而GetListBoxInfo是“這個真沒有”嗎?搞技術的不信邪,慢慢琢磨,是不是還是有其他的宏定義控制的條件編譯呢?順着GetListBoxInfo的定義向上搜“#if”,終於發現這麼一句“#if(WINVER >= 0x0500)”,WINVER是什麼宏呢?也是表示Windows的版本,可取值列表如下:

代碼:Windows 95、98 and Windows NT 4.0     WINVER=0x0400       
Windows 98 and Windows 2000       WINVER=0x0500    
Windows 2000                             WINVER=0x0500 
Windows xp                             WINVER=0x0501  

估計WINVER還是和_WIN32_WINNT一樣的問題,因此我們同樣打開StdAfx.h文件,在文件最開始加入“#define WINVER   0x0501”就ok了,也就是設置爲WindowsXP。再編譯GetListBoxInfo函數就通過了!!!
有同學問,怎麼微軟還弄了_WIN32_WINNT、WINVER兩個宏來表示Windows版本呢?詳見這篇文章,我就不詳細講了:

引用:The WINVER symbol is the earliest one. That's the symbol that 16-bit Windows used to control the versioning of its header files, and its use carried forward into the 32-bit header files, presumably from the people who did the initial conversion of the header files to 32-bit and who grew up with the WINVER symbol. This symbol is still used a lot in the header files that can trace their origins to 16-bit Windows, such as winuser.h, wingdi.h, and mmsystem.h. 
The _WIN32_WINNT symbol came next. I'm not sure where it came from, but from its name it probably was invented by the Windows NT team in order to allow them to block off sections of the header file that are available only in the Windows NT implementation of Win32. Don't forget that in the early days, there was also Win32s, a subset of Win32 that could run on 16-bit Windows 3.1. The single WINVER symbol wasn't enough to specify exactly what you wanted to be compatible with. For example, a function available only in Windows NT 3.1 would be guarded with #if _WIN32_WINNT >= 0x030A so that programs that wanted to run on Win32s could set _WIN32_WINNT to zero and keep that function off-limits. 
Similarly, both Windows 95 and Windows NT 4 identified themselves as Windows major version 4, so the WINVER symbol was insufficient to distinguish them. Functions that existed in Windows NT 4 but not in Window 95 were therefore guarded with _WIN32_WINNT. 
On the other hand, there were also functions that were first introduced in Windows 95 and did not exist in the original version of Windows NT 4. The _WIN32_WINDOWS symbol let you specify that you wanted access to stuff that was new for Windows 95 and which would also be ported to Windows NT 4 and future versions of Windows NT.

我承認我很卑鄙,在這等着你們呢。我相信80%的人跳過了剛纔我貼的這段英文,“全是英文,密密麻麻,不看了”,就像上面這段英文像梵文一樣你看不懂。呵呵。學計算機的要鍛鍊自己閱讀英文資料的能力,所以請跳過這段文章的同學現在回去讀一讀,“浪子回頭金不換”!每個閱讀的同學都把它翻譯出來,最好落實到紙面上。
除了_WIN32_WINNT、WINVER這兩個宏,還有一個重要的宏_WIN32_IE,顯而易見,它表示IE的版本,可選值如下:

代碼:Internet Explorer 3.0 _WIN32_IE=0x0300    
Internet Explorer 4.0 _WIN32_IE=0x0400    
Internet Explorer 5.0 _WIN32_IE=0x0500 
Internet Explorer 6.0   _WIN32_IE=0x0600

有部分函數也是依賴於IE的版本進行條件編譯的,因此最好也把_WIN32_IE設置爲0x0600。
總結一下,爲了避免麻煩,最好每次新建項目的時候把下面幾個宏定義加到StdAfx.h中,這樣就免除了後顧之憂:

代碼:#define _WIN32_WINNT 0x0501
#define _WIN32_IE 0x0600  
#define WINVER   0x0501
引用:
你給的這些宏的數值可以更改嗎?按照我上面蒐集的資料,vista系統的應該是:
_WIN32_WINNT>=0x0600
WINVER>=0x0600

爲何你給的那些數還要小呢?

當然可以改,因爲我的電腦是xp,所以我設的是0x0600。其實VC6中設成0x0500以上的任意值已經沒區別,因爲翻遍了VC6中所有的頭文件沒有發現依賴於0x0501以上的函數,很簡單,VC6第一個版本發佈的的時候是1998年,而VC6配套的SDK頭文件最新的版本發佈的時候還只有Windows2000,那時候根本沒有WindowsXP、Vista這些東西,所以哪怕 _WIN32_WINNT設成0x0900都行,只不過沒意義,VC6下設成0x0500就足夠了。


 

附註1:寫的不怎麼完善,但總算解決了問題.更新中...

轉自:http://hi.baidu.com/vc_net/blog/item/4528f703c90f19f509fa93f6.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章