msdn Home  產品資訊 |  技術支援 |  搜尋 |  台灣微軟 
Microsoft
 首頁 |  研討會 |  訂閱電子快訊 |  專屬網頁 |  採購資訊 |  關於微軟 |  網站導覽 |  MSN台灣 |

教育訓練

線上技術支援

SDK開發軟體平臺套件文件

合格的windows應用程式

專家詢問聊天室

會員報名

會員服務

Microsoft Windows 2000 應用程式相容性
Kyle Marsh
Microsoft Corporation
1999 年 11 月

摘要: 討論一些使應用程式與 Microsoft Windows 2000 不相容的議題。 (23 列印頁) 涵蓋:

簡介
安裝議題
Windows 2000 相容性議題
應用程式穩定性議題
Windows 平台差異

簡介

最近這幾個月,我被指派一項任務,去找出在 Windows 2000 作業系統中的應用程式相容性議題。我接著要談論的是應用程式與 Windows 2000 不相容的議題。實際上,沒有人關心應用程式相容性的議題。

我與 Windows 2000 測試團隊合作,在最近幾個月內測試了數百種應用程式。我們記錄在 Windows 2000 應用程式成功與失敗的原因。所發現的議題分為四類:

  • 應用程式無法安裝在 Windows 2000 上。這是至今最大的問題。在 Windows 2000 應用程式安裝方式並沒有太大的不同;問題是應用程式就是無法在新版本的作業系統上安裝。

  • 我們在作業系統所作的變更影響應用程式的執行方式。每當 Microsoft Windows NT 開發團隊面臨讓系統作為平台更穩定或功能健全,或使應用程式相容的選擇時,他們總是選擇讓系統更穩定。開發 Windows 2000 的主要目標之一是使系統成為更穩定的平台。很不幸地,為達成這個目標,一些必要的變更使應用程式與 Windows 2000 不相容。

  • 我們在作業系統所作的變更應該不會使應用程式不相容,但會損毀某些應用程式。

  • 應用程式的設計目標太針對 Windows 9x 平台。因為 Windows 2000 的目標是要將許多使用者從 Windows 9x 升級,我們測試 Windows 9x 應用程式,將它們移到 Windows 2000。我們發現有些應用程式的設計目標太針對 Windows 9x。

安裝議題

我們討論的第一類是安裝議題;最常見的問題是無法將應用程式安裝在 Windows 2000。事實上,應用程式無法安裝的主要共同原因是,Windows 2000 是 Windows NT 版本 5.0。

測試團隊用一些方法測試應用程式。他們將應用程式安裝在 Windows 2000 系統來測試應用程式。測試團隊也將應用程式安裝在 Windows NT 4.0 或 Windows 95 後,再將系統升級到 Windows 2000,來測試應用程式。

我們使用乾淨機器,在它上面安裝 Windows 2000 後,再安裝應用程式,我們發現相容率比起升級情形 (不論從 Windows NT 4.0 或 Windows 98) 要明顯偏低。

版本檢查

應用程式無法安裝在 Windows 2000 的第一個原因是,它們無法正確處理版本號碼。我們發現許多應用程式執行類似下列程式碼範例的動作。它們進一步呼叫 GetVersionEX 後,再撰寫「if」敘述式,大意是「如果它是版本 3,我可能不應該安裝,因為沒有新殼層就不要工作;如果它是版本 4,我就可以安裝。」 問題是它是版本 5;在這個「if」敘述式中並沒有指定。所以,我們發現許多應用程式因為版本號碼是 5.0 無法安裝。

if (osvi.dwMajorVersion == 3)

   {

   // 做這樣的處理

   }

else if (osvi.dwMajorVersion == 4)

   {

   // 做那樣的處理

   }

測試團隊採取行動,欺騙了許多這些應用程式。稍早的 build 中,我們用了技巧取得 GetVersionEx 的回覆值。我們取得那個回覆值,欺騙應用程式說它是版本 4.0 後,它便開始進行安裝,並且運作正常。有些應用程式卻故意阻止在 Windows 2000 的安裝。病毒掃描程式或其他低階公用程式受限於特定作業系統版本,這是可以理解的。不過,這些應用程式會顯示訊息說明這個情況。我們發現有些應用程式在無法安裝或無法正常運作時,甚至不顯示訊息給使用者。

如何正確地進行版本號碼檢查呢?我們在 Windows 2000 加一個新的 API: VerifyVersionInfo,它會依序檢查主版本、副版本及 Service Pack。當作業系統的版本出現時,您的應用程式仍會安裝及執行。事實上,VerifyVersionInfo 的使用方式很多,但若只要檢查「這個作業系統符合我的需要嗎?」您可以在呼叫中使用下列三個旗標,檢查主版本、副版本及 Service Pack。您可能說「我的應用程式需要 Windows NT 版本 4.0 及 SP 2」,並且問 VerifyVersionInfo 「這個執行的 OS 符合標準嗎?」 它會傳回 true 或 false。

VerifyVersionInfo(&osvi, 

      VER_MAJORVERSION |

      VER_MINORVERSION |

      VER_SERVICEPACKMAJOR,

      dwlConditionMask);

如果您這樣檢查版本,就符合 Windows 2000 應用程式規格,它基本上說「只要有新版本,您就必須在作業系統的新版本上安裝。」

使用 VerifyVersionInfo 的問題是,目前只有 Windows 2000 平台提供。若要檢查舊平台 (如 Windows 95) 的版本,您必須使用 GetVersionEx。請參閱下列的程式碼範例,您會發現它的作用與 VerifyVersionInfo 相同。我們依序檢查主版本、副版本及 Service Pack。

BOOL bIsWindowsVersionOK(DWORD dwMajor, DWORD dwMinor, DWORD dwSPMajor )

   {

   OSVERSIONINFO osvi;

   // 初始化 OSVERSIONINFO 結構。

   //

   ZeroMemory(&osvi, sizeof(OSVERSIONINFO));

   osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);

   GetVersionEx((OSVERSIONINFO*)&osvi);

   // 首先是主版本

   if ( osvi.dwMajorVersion > dwMajor )

   return TRUE ;

   else if ( osvi.dwMajorVersion == dwMajor )

      {

      // 然後是副版本

      if (osvi.dwMinorVersion > dwMinor )

   return TRUE ;

      else if (osvi.dwMinorVersion == dwMinor )

         {

         // OK,最好也檢查 Service Pack

         if ( dwSPMajor && osvi.dwPlatformId == VER_PLATFORM_WIN32_NT )

            {

            HKEY   hKey;

            DWORD   dwCSDVersion;

             DWORD   dwSize;

            BOOL   fMeetsSPRequirement = FALSE;

 

            if (RegOpenKeyEx(HKEY_LOCAL_MACHINE,

                         "System\\CurrentControlSet\\Control\\Windows", 0, 

                         KEY_QUERY_VALUE, &hKey) == ERROR_SUCCESS)

               {

               dwSize = sizeof(dwCSDVersion);

               if (RegQueryValueEx(hKey, "CSDVersion", 

                        NULL, NULL, (unsigned char*)&dwCSDVersion, &dwSize) == ERROR_SUCCESS)

                  {

                  fMeetsSPRequirement = (LOWORD(dwCSDVersion) >= dwSPMajor);

                  }

                RegCloseKey(hKey);

                }

            return fMeetsSPRequirement;

            }

   return TRUE ;

         }

      }

 

      return FALSE ;



   }



//

//這個範例用於 Windows2000 及以上版本

//

BOOL bIsWindowsVersionOK(DWORD dwMajor, DWORD dwMinor, DWORD dwSPMajor )

    {



    OSVERSIONINFOEX osvi;

   ZeroMemory(&osvi, sizeof(OSVERSIONINFOEX));

   osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);

   osvi.dwMajorVersion = dwMajor;

   osvi.dwMinorVersion = dwMinor;

   osvi.wServicePackMajor = dwSPMajor;

   // 設定條件遮罩。

   VER_SET_CONDITION( dwlConditionMask, VER_MAJORVERSION, VER_GREATER_EQUAL );

   VER_SET_CONDITION( dwlConditionMask, VER_MINORVERSION, VER_GREATER_EQUAL );

   VER_SET_CONDITION( dwlConditionMask, VER_SERVICEPACKMAJOR, VER_GREATER_EQUAL );

   // 執行測試。

   return VerifyVersionInfo(&osvi, 

                             VER_MAJORVERSION | VER_MINORVERSION | VER_SERVICEPACKMAJOR,

                          dwlConditionMask);

     }

首先,您必須檢查主版本。如果執行的作業系統主版本高於需要的主版本,就不進一步檢查;我們就可以安裝及執行。如果主版本相等,便以相同的方式檢查副版本。最後再檢查 Service Pack。每當我們遇到點版次號碼,您可以說「是,很好,我們不用煩惱這是 Service Pack 3、4 或 5。」 如果主版本或副版本增加,很好沒問題。我們發現許多應用程式檢查版本時會個別檢查每個元件。它們會說「是,它是主版本 5,我只需要 4,很好。它是副版本 .0,很好,可是它不是 Service Pack 0,我需要 Service Pack 3。」很明顯地,目前 Windows 2000 還沒有 Service Pack 3,因此這不是檢查版本的正確方法。

在 Windows NT 檢查版本的詳細資料:檢查版本號碼及 Service Pack 資訊有三個不同方法。首先,您可以取得 GetVersionEx 的回覆碼,以及檢查字串「szCSDVersion」。在這個字串的某個位置內嵌實際的 Service Pack 號碼。這的確很難解析,並且您可能記不得它是否本土化;這並不是檢查的最佳方法。如果您在 Windows NT 4.0 執行,應該檢查下列登錄機碼,在登錄機碼中 Service Pack 是個數字,所以您可以拿它進行「等於」或「大於」比較:

HCLM\System\CurrentControlSet\Control\Windows\CSDVersion

如果您在 Windows 2000 或以上版本執行,仍可使用 GetVersionEx,但傳給它 OSVERSIONINFOEX 結構,而不是以上所使用的 OSVERSIONINFO 結構。Windows 2000 首先會因為運算子成員大小,認為它是較大結構,並且給您新的整數欄位--Service Pack、主版本及副版本--您可以用來比較。再強調一次,如果您使用 VerifyVersionInfo,會發現它是最簡單的方法。

DLL 版本檢查

檢查 Windows 版本時,我們發現另一個版本問題:沒有人檢查 DLL 版本。將 DLL 複製到系統之前,一定要檢查 DLL 版本,不論它是系統目錄中的 Microsoft DLL 或是您自己的 DLL。您一定要檢查版本,這樣舊版 DLL 才不會覆寫新版的 DLL。

您必須確定確實將版本資訊加入您的 DLL 中,才能進行檢查。這個處理很重要,可避免許多棘手問題。如果您嘗試變更系統目錄中的 DLL,必須馬上停止。考慮升級或降級系統 DLL 或複製相同的 DLL 是多餘的。系統 DLL 是位於系統目錄中的 DLL,由 CD 上的 Windows 2000 或 Service Pack 所傳遞的。

如果您要製作新的 Windows 2000 應用程式,必須使用 Windows 安裝程式,它會為您檢查 DLL 版本。只要您說將特定的 DLL 放入特定目錄中,Windows 安裝程式就一定會為您檢查版本。您不必處理這個程式碼位元。

DLL 地獄

不正確檢查 DLL 版本號碼的後果當然是「DLL 地獄」(DLL Hell)。我確定不需要跟您說明「DLL 地獄」。我確定您已體驗過。因為我原本做 ctl3d.dll,很熟悉這個感覺。

幾年來為使 DLL 正常運作傷透腦筋,我們的結論是,應用程式提供者根本無法維持與舊版的相容性。大家努力要實現它,它是偉大目標但無法執行。事實上,不可能讓您的 DLL 與舊版相容。結果:一個應用程式總是需要特定版本的 DLL 才能運作,其他應用程式需要該 DLL 的其他版本,這兩個應用程式無法共存於同一個系統上,因為它們為這個共用元件 (共用的 DLL) 發生衝突。

現在我們仍然認為對於 Windows,共用 DLL 的功能是應用程式很好、很重要的一部分。我們也認為,從 DLL 得到的所有好處仍然可貴。問題是,在應用程式之間全域共用 DLL 而不測試版本已引起太多問題。

並列 DLL

在 Windows 2000,我們開始施行「並列 DLL」(side-by-side DLL)。我們要您的應用程式開始使用並列版本策略。在 Windows 2000,我們採取一些積極步驟以避免「DLL 地獄」。第一步,我們要確定不論安裝哪些應用程式,系統維持受保護的、原封不動的。我們很快會討論「Windows 檔案保護」。

另外,我們要讓元件並列 (也要求應用程式廠商做這樣的處理)。我們為 Microsoft 元件做這樣的處理;建議您也為您的元件做這樣的處理。我們採用並列版本,並且建議您為您目前全域共用的所有元件採用並列版本。

系統穩定性

同時,Windows NT 團隊也致力於確保系統運行一段時間後保持穩定。Microsoft 的問題之一是,我們讓許多功能透過 Service Pack 發佈引進到 Windows NT 平台,這使得安裝 Windows NT 的使用者及公司非常小心地選用 Service Pack,因為其中不僅是修復程式。它通常有一些修復程式,及「喔,我們在這裡新增這個功能,我們在哪裡新增那個功能」,這樣使得系統較不穩定。

原則上,Service Pack 僅包含錯誤修復程式而已。如果我們認為新功能很重要,必須加入作業系統,我們會將它作為 Windows 2000 的點版次發行。您會有類似 Windows NT 5.1、5.2 等作業系統,以及 Service Pack 個別發行給 Windows NT 的三個不同版本。在 QEF、錯誤檢查及快速修復的範圍內,我們會繼續保持每個平台功能健全。每次有新增功能時,表示作業系統的新版次。

最後,我們要確定知道在發行 Microsoft 產品時有哪些附屬元件 (我們強烈建議您為您的產品也做這樣的處理)。我們努力減少元件隨不同產品發行的數量。如果特定元件搭配其他特定元件,我們一定會將它們一起發行。我們會架構出如何發行所有重新發佈的元件之方式。

並列 DLL

當您必須將元件從全域共用元件或 DLL 變更為新的並列 DLL 時,必須對您的 DLL 做一些變更。您必須變更 DLL 本身。您必須說「我要將我的元件設計成為可同時執行多重版本」。

若要將元件變成並列元件,首先您必須將 DLL 本身重新命名,並且變更 COM 物件 (OCX 控制) 可能擁有的任何 GUID。您只需重新命名一次,然後新的 DLL 便可以有效地平行執行,不再是全域共用。

一旦 DLL 已重新命名,應用程式便可以將它安裝在它們自己管理的目錄,而不是在系統目錄中。這表示應用程式開發人員可以說「我已經充分測試我的產品及特定版本的這個 DLL ,現在我確定除非再次測試,否則這個 DLL 不會更新。」這可讓程式開發人員確定其他人不會擅改您的應用程式與共用元件,避免可能的損毀及「DLL 地獄」。

當您開始使用這些元件,在您自己的本機目錄中登錄一個相對路徑,而不是在系統目錄。它便會載入本機複本,而不會載入系統其他位置上的通用複本。

我們修改了 LoadLibrary 函數功能,如果應用程式以相對路徑登錄元件,我們就一定以相對路徑載入它,不論系統目錄中是否已存在或其他位置正在執行相同元件。這確保您可取得用來測試應用程式的元件版本。

孤立的應用程式

我們也修改 LoadLibrary 碼以支援 DLL 重新導向。這讓系統管理員原本從某個位置的 DLL 載入重新導向到從本機目錄載入該 DLL。現在您的 DLL 在重新導向後可以被孤立。假設在大公司,有人測試是否可以使用您的應用程式。他們安裝了其他應用程式,並要看看這兩個應用程式是否可以一起運作。他們發現結果不行,系統管理員便必須檢查這兩個應用程式在哪個元件衝突。系統管理員找到元件後,瞭解員工需要使用這兩個應用程式,他將 DLL (或包含該元件的 OCX) 放入應用程式的目錄中。然後系統管理員建立名為 foo.exe 的檔案,檔名結尾附加 .local。在呼叫 LoadLibrary,並且 LoadLibrary 發現有 foo.exe.local 檔案時,它會先從應用程式目錄載入元件,不論應用程式的 LoadLibrary 呼叫本身使用的特定路徑。多個應用程式需要不同版本的元件時,現在便可以在相同系統上執行。

Windows 檔案保護

要系統功能更健全,平台更穩定的第一個步驟是,確定系統本身不發生「DLL 地獄」問題。我們要確定不論發生任何問題,系統仍然運行,仍可開機--使用者可以信任系統的穩定性。

運用「Windows 檔案保護」(WFP),如果應用程式嘗試變更系統檔案,Windows 2000 就會將它變更回原狀。您的應用程式在安裝時為使用某個功能,可能說「喔,我需要這個這個 DLL 的新版本」,或者它可能是不良應用程式,沒有正確地檢查版本。Windows 2000 會檢查這個情況,並監視檔案變更。一旦 Windows 2000 發現變更系統檔案,它會說「我不允許變更那個檔案」,它會將它回復。

對系統鎖定的檔案進行更新,唯一的方法是透過 Windows NT 團隊發出的支援檔案取代機制:Service Pack、 QFE 或快速修復。它們會提供系統檔案取代,其他應用程式則不能。

例如,我們鎖定了 mfc42.dll。語言群組不再能夠更新它,僅 Windows NT 群組可以變更您系統上的那個 DLL。C 開發人員若要更新他們的 DLL (我想在 Windows 2000 發行到 Windows NT 下一版發行期間,他們會想要更新它),唯一的方法是採用並列元件版本。

大部分 *.sys、*.dll、*.exe 及 *.ocx 檔案,以及一些字型檔案是受保護的。

有幾個相容性議題。首先,反病毒應用程式必須知道「Windows 檔案保護」並與其合作 (備份及還原應用程式也一樣);您無法僅僅複製、備份及還原這些檔案。因為您不是系統支援的檔案取代機制,如果您那麼做,「Windows 檔案保護」就開始作用。

為幫助大眾,我們在系統中新增一些 API。

WFP API

第一個 API 是 SFCGetNextProtectedFile。這個 API 可提供您受保護的或可被保護的所有檔案清單。您以 NULL 開始,並重複呼叫它,以取得受保護的檔案清單。

BOOL WINAPI SfcGetNextProtectedFile (IN HANDLE RpcHandle, IN PPROTECTED_FILE_DATA ProtFileData );

//

// 這個函數會列出受保護的檔案

//

void ListProtectedFiles(HWND hWnd)

   {

   HWND   hwndList;

   PROTECTED_FILE_DATA pfd;

   int iCount;

   char szFileName[260];

   int iLen;

   RECT rt;

   

   hwndList = GetWindow(hWnd,GW_CHILD);

   if ( hwndList == NULL )

      {



      GetClientRect(hWnd, &rt);

      // 第一次建立 List 控制項

      hwndList = CreateWindow("LISTBOX", NULL,

                        WS_CHILD | WS_VISIBLE | LBS_STANDARD | LBS_NOINTEGRALHEIGHT |

                        LBS_USETABSTOPS,

                        0,20,rt.right,rt.bottom-40,

                        hWnd,

                        NULL,

                        hInst,

                        NULL);

      }

   else

      SendMessage(hwndList, LB_RESETCONTENT, 0, 0);



   ZeroMemory(&pfd,sizeof(PROTECTED_FILE_DATA));

   iCount = 0;

   while ( g_pfnSfcGetNextProtectedFile(NULL, &pfd) != 0 )

      {

      // 為這個 ANSI App 將 WCHAR 轉換成 ASNI

      iLen = WideCharToMultiByte(CP_ACP,NULL,pfd.FileName, wcslen(pfd.FileName),

                      szFileName,260,NULL,NULL);

      szFileName[iLen] = '\0';

      SendMessage(hwndList, LB_ADDSTRING, 0, (LPARAM)szFileName);

      iCount++;

      }

   }

較直接的 API 是 SfcIsFileProtected。對大部分的應用程式而言,直接呼叫它可能方便許多,並說「這裡有特定檔案,它是受保護的嗎?」不過,請記得,它需要檔案的完整路徑。您不能僅指定 NTS.sys;必須給它 NTS.sys 的位置路徑。當您將檔名傳給 API 時,它會傳回「是,這是受保護的檔案」或「否,它不是受保護的檔案」。所以,如果您要備份或還原,必須使用這個 API。如果您要安裝,可能會更新系統檔案,也應該呼叫這個 API。如果您有目標檔案,並要將它放入系統目錄中,在您動作之前應該先呼叫 API,以避免啟動「Windows 檔案保護」。下一版 Windows 安裝程式 (即 Windows 2000 所附的) 在複製前會先檢查,不會輕易地使用 WFP。

BOOL WINAPI SfcIsFileProtected (IN HANDLE RpcHandle,IN LPCWSTR ProtFileName);



//

// 這個函數使用開啟檔案 dlg,從使用者取得檔名

// 並且檢查,看看它是否受保護的。

void CheckFileForProtection(HWND hWnd)

   {

   OPENFILENAME OpenFileName;

   CHAR szFile[MAX_PATH]      = "\0";

   CHAR szSystem32[MAX_PATH];

    strcpy( szFile, "");

   ZeroMemory(&OpenFileName, sizeof(OPENFILENAME));

   // 填入 OPENFILENAME 結構以支援範本及勾點。

   OpenFileName.lStructSize       = sizeof(OPENFILENAME);

    OpenFileName.hwndOwner         = hWnd;

    OpenFileName.hInstance         = hInst;

    OpenFileName.lpstrFile         = szFile;

    OpenFileName.nMaxFile          = sizeof(szFile);

    OpenFileName.lpstrTitle        = "Select a File";

    OpenFileName.Flags             = OFN_FILEMUSTEXIST;



   if (g_pfnSHGetFolderPath != NULL )

      g_pfnSHGetFolderPath(NULL, CSIDL_SYSTEM, NULL, NULL, szSystem32);

   else

      szSystem32[0] = '\0';

   OpenFileName.lpstrInitialDir   = szSystem32;

   // 呼叫公用的對話函數。

    if (GetOpenFileName(&OpenFileName))

      {

      // 檢查檔案

      WCHAR wzFileName[260];

      int iLen;

      iLen = MultiByteToWideChar(CP_ACP,NULL,szFile, strlen(szFile), wzFileName, 260);

      wzFileName[iLen] = '\0';

      if (g_pfnSfcIsFileProtected(NULL, wzFileName) == TRUE )

         {

         MessageBox(hWnd,"Is Protected", szFile, MB_OK);

         }

      else

         MessageBox(hWnd,"Is NOT Protected", szFile, MB_OK);



      }

元件檢查

對於應用程式無法安裝在 Windows 2000,下一個議題我們要討論的是元件檢查。明顯地,我們每個作業系統都是由許多不同的元件所組成,包括 TAPI、MAPI、Microsoft DirectX 等等。我們發現應用程式會假設哪些是元件、元件位置或特定元件是否存在。應用程式因為其他元件存在而假設某個元件也存在,例如因為某個元件版本 2 存在而假設其他元件版本 3 必定也存在。如果您需要有某個元件,則必須特別檢查系統是否存在該元件以及版本是否正確。

並且,人們以為 Windows NT 沒有 DirectX。過去這是事實,在撰寫應用程式的時候,可能也是事實。當它檢查時,會假設 Windows 2000 沒有 DirectX,並說「喔,我無法執行」。但 Windows 2000 的確有 DirectX,那個假設錯誤。

我們遇到的另一個問題是硬性編碼問題,應用程式假設元件位於錯誤的位置,硬性編碼一個無法執行的路徑。

另一個例子是,Windows 2000 現在預設包含 TAPI (最新版本 TAPI 3.0) 及 DirectX (最新版本 7),但不包含 MAPI。過去,Windows NT 包含 MAPI。現在不再是這個情況。如果您使用元件,一定要檢查它是否存在,不要根據平台或元件假設。

將檔案在錯誤的位置

我們發現其他安裝議題是,人們將檔案放在錯誤的位置。如果您要將其他人已安裝的檔案升級,您可以將它們放在使用者要使用的位置。可是,每當您第一次安裝時,必須預設為 Program Files 目錄。

附註   不一定是 C:\Program Files。依需要而定。例如,在我的機器就不是。我總是傾向於將 Windows NT 磁碟分割安裝在不同的磁碟分割上,將 C 留給 Windows 98。

我希望您儘可能停止將檔案放在 Windows 目錄或其子目錄 (如 System32)。明顯地,這並不是件壞事,過去我們也一直告訴您這麼做,所以現在無法要求您絕對停止,但我們希望系統上的所有檔案開始有點組織。我們發現使用者被他們的 System32 目錄中的成千上百個檔案嚇倒,不認識這些檔案也不刪除它們。您會聽到有人言論「每年應該清理您的系統,重新安裝 Windows」。解除安裝程式及清除程式是流行應用程式商品。我們希望清理目錄,讓事情簡單一些。

可能的話,我們希望您甚至開始將一些共用元件寫入其他位置;Microsoft Visual Basic 是很好的例子。許多不同的應用程式都使用它。與先前 Visual Basic 所在的系統目錄不同中,我們現在將它放在 Program Files 的特定資料夾中。

要找出檔案的正確放置位置,方法是開始呼叫一個稱為 SHGetFolderPath 的新 API。SHGetFolderPath 是 Windows 2000 的一部分。Windows 98 第二版也有它。如果您發現系統沒有這個 API,您可以自行散佈 SHFolder.dll。這個 DLL 會開放這新的 API 殼層 SHGetFolderPath。這個 API 知道系統上每個特殊資料夾的位置。您可以在 SDK 中找到 API。SDK 實際上是可用的資料夾清單。

在舊平台上,請注意,只支援四個 CSLID。如果您尋找特定目錄,卻找不到,如果您指定的話,SHGetFolderPath 可以幫您建立,但在舊平台上 (如 Windows 95),它只建立下列四個 CSIDL:

CSIDL_PERSONAL

CSIDL_APPLICATIONDATA

CSIDL_MYPICTURES

CSIDL_LOCAL_APPLICATIONDATA

下列程式碼範例顯示如何使用 SHGetFolderPath;它是很直接的 API。首先注意,如果您要您的程式碼在所有平台執行 (不管是具備原始 SHGetFolderPath 的 Windows 2000 或其他沒有的平台,如 Windows 95),必須將您的應用程式一直動態連結到 SHFolder.dll 的執行方式。

            // SHGetFolderPath can work everywhere SHFOLDER is installed.

            HMODULE hModSHFolder = LoadLibrary("shfolder.dll");

            if ( hModSHFolder != NULL ) 

               {

               (*(FARPROC*)&g_pfnSHGetFolderPath = GetProcAddress(hModSHFolder, "SHGetFolderPathA"));

               }

            else

               g_pfnSHGetFolderPath = NULL;

            }



   if (g_pfnSHGetFolderPath != NULL )

      g_pfnSHGetFolderPath(NULL, CSIDL_SYSTEM, NULL, NULL, szSystem32);

   else

      szSystem32[0] = '\0';

   OpenFileName.lpstrInitialDir   = szSystem32;

安全性議題

在最後安裝議題中,我們要討論一些 Windows 2000 的相關安全性議題:

  • 高階使用者應該能夠安裝系統範圍的應用程式。我們發現應用程式堅持讓系統管理員執行這種安裝。公司可能會鎖定機器,以便一天兩三位不同的使用者可以共用機器;在特定日每個員工都可以使用這個系統。他們不要讓任何人都能夠以系統管理專用權來變更系統。許多客戶開始要求讓高階使用者安裝應用程式,而不是系統管理員。

  • 另外注意,任何使用者應該為個人用途在系統安裝應用程式,而不是為每個人使用。如果我有遊戲要執行,我應該安裝那個遊戲,並且不要讓系統其他人使用它。不過,請記得,非高階使用者無法寫入 Program Files 目錄;他不能寫入 HKEY_LOCAL_MACHINE。所以,在安裝期間,您應該開啟 HKEY_LOCAL_MACHINE,檢查您是否可以寫入該位置,讓它說「您不可以在寫入那個位置;您要為個人用途安裝這個應用程式嗎?」

  • 另一個安全性議題是預設伺服器使用權限。如果您要安裝伺服器應用程式或服務,並且使用非專用的服務帳戶 (換句話說,您不是使用本機系統中的帳戶,並且不是使用本機系統管理員群組成員的帳戶),這個帳戶很可能就沒有需要的專用權來執行您的服務。在 Windows 2000,沒有專用權的使用者 (亦即沒有專用權的服務帳戶) 所擁有的使用權限不同於 Windows NT 4.0。他們擁有較少的使用權限。例如,沒有專用權的使用者無法寫入 Windows 系統目錄。如果您讓伺服器執行,嘗試讓它安裝,它可能沒有正確的使用權限。如果您要執行伺服器應用程式,請確定它有正確的登入帳戶,這樣它才有所有必需的使用權限以正常運作。

Windows 2000 相容性議題

以下我要討論的議題稱為 Windows 2000 相容性議題。這些是我們在 Windows 平台所作的變更,以升級平台並且達成我們為使用者建立更穩定平台的目標。這些變更影響有些應用程式在 Windows 2000 的執行方式。

設定前景視窗

我們從較簡單的開始:設定前景視窗。事實上,這個變更開始於 Windows 98。您無法依賴您的應用程式呼叫 SetForegroundWindow,以及視窗自動變成前景視窗。這可以停止潛在惱人選項--任何人都可以蹦現到頂端。在您愉快地打字時,突然蹦現一個要求。在您不瞭解時,可能對您不清楚的事情鍵入同意。

為停止這個情況,現在有規則來規範何時應用程式可以變成前景應用程式。這些實際上開始於Windows 98:

  • 如果您的處理已經是前景處理,即可使用前景視窗。

  • 如果您的處理已由前景處理啟動,您可以使用前景視窗。

  • 如果您的處理最後的輸入,它可以變成前景視窗。

  • 如果目前沒有前景視窗,您可以使用前景。

  • 如果前景處理目前正在偵錯中,任何人都可以使用前景。

  • 如果前景發生等候逾時鎖定--前景鎖定在一段時間內尚未執行,並且看起來尚未回應--便可以使用前景。

  • 任何系統功能表在使用中,您的應用程式無法取得前景。這是針對以下惱人例項,在您使用 [開始] 功能表時,正在指向其階層中並啟動某個應用程式,突然它不見了。Windows 2000 中的這個新規則應該能避免該問題。

超級隱藏檔

另一個 Windows 2000 的新功能稱為「超級隱藏檔」。系統會將一些檔案標示為系統及隱藏屬性。檔案還是存在,我們還是可以使用它們;這個功能的作用是,在 [Windows 檔案總管] 中,這些檔案不會出現。甚至當您勾選顯示隱藏檔,仍然看不到它們。資料夾的屬性清單中有個新的核取方塊可讓使用者看到這些檔案,但一般使用者若不勾選這個核取方塊便看不到。

另外,在 Windows 環境中會發出噪音的檔案也不再顯示 (大部分是舊型 MS-DOS 檔案及類似檔案)。在多數情況下,這並不影響一般使用者;他們僅覺得系統比較乾淨。

就 32 位元應用程式而言,這並不是相容性問題。應用程式可以在公用的開啟舊檔對話方塊中看到檔案,開啟檔案也沒有問題。指令行還是可以運作。如果您發出 Dir /ASH 指令來看超級隱藏檔,可以看到所有檔案。

相容性議題是針對 16 位元應用程式,這會發生設陷。這些應用程式透過 INT21 在 MS-DOS 呼叫。如果您實際上要求尋找隱藏檔,在 MS-DOS 的 INT21 僅尋找隱藏服務檔。

下列檔案會被隱藏:

  • MS-DOS 系統檔案,如 io.dos

  • Office 快速尋找檔案

NetBIOS

NetBIOS 一直是 Windows NT 的一部分。自從 Windows 2000,這不再是事實。它不是預設設定,但使用者可以在設定系統時,不要載入 NetBIOS 並且不顯示它。如果您的應用程式要呼叫 API,但 API 使用系統上的 NetBIOS 不再出現,API 便不再正常運作,它們會傳回錯誤。例如,如果您使用 NetServerEnum 呼叫,它執行的系統沒有 NetBIOS,就會傳回錯誤。您必須檢查要使用 NetBIOS 呼叫的地方,瞭解機器將沒有 NetBIOS,並且正確地處理這個情況。或者您可以切換回非 NetBIOS 呼叫。確定您的使用者知道,您的系統一定需要 NetBIOS,並且使它成為安裝或版本資訊的一部分。

需要新的網路 .inf 檔案

如果您有網路裝置,包含網路驅動程式、傳輸驅動程式及有些網路檔案列印提供者,您必須確定裝置有新的網路 .inf 檔案,以支援 Windows 2000 隨插即用。每當使用您網路裝置的系統進行乾淨安裝時,或者機器從 Windows NT 4.0 升級到 Windows 2000 時,您需要這些新檔案。您可能已經用過這個格式,因為它與 Windows 98 相容。無論如何,您都需要馬上提供這些檔案給使用者,這樣系統移到 Windows 2000 後,您的裝置仍然是支援的。

實體磁碟機號碼

如果您的應用程式嘗試以低階方式存取硬碟機及磁碟區 (例如病毒掃描程式的方式),您必須尋找實體磁碟機號碼,並且必須改變尋找號碼的方式。在過去,可以使用符號連結,它會傳回類似下列碼:

\Device\HarddiskX\PartitionY

其中在 Harddisk 後面的 X,看看它是硬碟 2 或硬碟 3。現在符號連結會傳回:

\Device\HarddiskVolumeZ

實體磁碟機號碼不再出現於該符號連結中。而您必須使用一些可用的 IOCTL。第一個:

IOCTL_STORAGE_GET_DEVICE_NUMBER

用於單一磁碟機號碼。例如,如果磁碟機是 C 磁碟機,可以用它,甚至在一個磁碟機上有多重磁碟分割也一樣。但如果您有多重磁碟區組,就必須使用:

IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS

在 Windows NT 4.0 也是這個情況;從符號連結取得實體磁碟機號碼總是有點危險--它們僅告訴您在可能多重磁碟組中的第一個磁碟機。

存取磁帶機

如果您的應用程式使用磁帶機,您必須變更存取磁帶機的方式。新的「階層式存放管理」使用稱為「卸除式存放管理員」的工具,它會檢查伺服器並判定哪個特定檔案在長時間內沒有被存取過。它會說「讓我們將它放在磁帶,如果有人需要它,我們可以從磁帶抓取它」。需要的人必須稍候久一點,但還是可取得檔案。這樣,您可以使用小型磁碟機,卻可支援更大空間。

因為「卸除式存放管理員」固定在伺服器執行,您的應用程式也會嘗試使用磁帶機,它會發現磁帶機使用中。應用程式不能輕易地抓取磁帶機。處理這個議題需要更多空間,這裡無法提供。建議您閱讀 Microsoft.com 中的〈Windows NT 5.0 存放應用程式的開發考量〉。在那篇文章中提供如何處理新磁帶機的概觀。若要執行,您必須花點時間閱讀 SDK 中的〈卸除式存放管理員程式設計師參考手冊〉。從這篇文章,您可以學習如何撰寫應用程式,讓它與「卸除式存放管理員」共用磁帶機。

勾點顯示驅動程式

如果您的應用程式嘗試嵌入顯示裝置 (例如,您撰寫顯示驅動程式,它先接收呼叫,再呼叫原始顯示驅動程式),您必須變更執行方式。您看過應用程式嘗試這麼做--遠端控制應用程式是個例子--它們會發出顯示驅動程式指令,透過電線傳送指令,並在本機執行。如果您要在 Windows 2000 執行,您必須使用新的「顯示驅動程式管理等級」(DDML) ,將該輸出鏡像到遠端裝置。這會啟用多重顯示驅動程式,如同遠端控制應用程式的執行方式。相關文件包含在 Windows 2000 Beta 3 版次的 DDK 中。

防寫保護核心模式

另外,Microsoft 有新功能,可增強平台的穩定性: 在核心模式執行的程式實際上使用記憶體的防寫保護區。如果您的裝置驅動程式使用一些程式碼區段或字串區段,並且將某個程式 (例如,您的筆記本) 放在唯讀區段作為捷徑,在 Windows 2000 這是行不通的。我們不允許以核心模式的任何程式覆蓋應該存在的保護,因為覆蓋會導致當機。

我們發現許多裝置驅動程式違反 Windows 2000 的這個規則。在檢查驅動程式時,系統會判定,如果裝置驅動程式針對 Windows NT 4.0 而不是 Windows 2000 時,它不會強制執行這個規則。否則,太多裝置驅動程式根本無法執行。針對專用於 Windows 2000 或升級版適用於 Windows 2000 的裝置驅動程式系統會強制執行這個規則。

增加堆疊使用

以下記載有關相容性的一些核心議題,第一個議題,Windows 2000 比 Windows NT 4.0 使用更多的堆疊空間。在單一、全球性可執行程式上,我們比起過去擁有更多 Unicode 空間,並且到處宣告更多字串。因此,系統需要更多堆疊。我們發現有些應用程式會藉由使用儘可能小的堆疊大小調整它們的效能。如果您要快速執行這是件好事,因為明顯地,使用越少空間,執行速度越快。但不幸地,有些空間太小而發生損毀,因為系統及應用程式太快用完堆疊空間。

您可以在應用程式的連結行檢查 /STACK-linker 選項,或在編譯器上使用 STACKSIZE-.def 檔案以及 STACKSIZE 參數或 /F 選項,檢查是否需要增加堆疊空間。您必須重新檢查這些來看看它們是否在 Windows 2000 執行,並確定堆疊空間不是太小。

Win32 API 變更

在 Windows 2000 有許多 Microsoft Win32 API 變更;在討論其中時可能意外發現相容性議題。這些是 Windows 2000 測試時我遇到最頻繁的。

在 Windows 2000 支援新的輸入法。為提供支援,我們要在 wParam 傳送一些資訊,您可以使用 WM_KEYUP 及 WM_KEYDOWN 訊息取得它。方法是將 wParam 原封不動地送到 TranslateMessage。如果不這麼做,就無法取得這個新輸入法的完整功能。

另一個議題有關 DS_SHELLFONT,它位在對話結構中。如果您指定 DS_SHELLFONT,您不再能夠變更字型。我們現在使用 Microsoft Shell Dlg 2 作為字型;您可以變更大小,但無法變更字型。

在開啟舊檔對話方塊的 OPENFILENAME STRUCTURE,初始目錄的行為有點不同。如果 OpenFile 找不到您要的檔案類型,它會預設到「我的文件夾」目錄作為捷徑。

GetWindowsDirectory 傳回按每個使用者的系統目錄。如果您在終端機伺服器執行,可能找不到您要的系統目錄;可能取得為特定使用者設定的系統目錄。新的 GetWindowsSystemDirectory 呼叫一定會傳回終端機伺服器上的實際系統目錄。

應用程式穩定性議題

現在討論應用程式穩定性議題。這是因為我們在 Windows 2000 所作的變更,暴露應用程式在執行上的一些錯誤,或詳細地說導致應用程式不相容。不過,這些變更應該不會損毀應用程式。有時因為應用程式的執行方式無法說明,也會產生錯誤。

硬性編碼的路徑

應用程式使用硬性編碼的參照,這是滿普遍的,當 Microsoft 有變更,移動系統上的物件,因為應用程式在原來位置找不到它所要的,它就無法執行。主要是這裡有硬性編碼的路徑。在 Windows 2000 及 Windows NT 4.0,路徑有許多移動。例如,「我的文件夾」在 Windows 9x 是 C 磁碟機或 D 磁碟機上的根目錄,如下所示:

\My Documents

Windows NT 開始移動那個路徑,並按每個使用者,它將那個資料夾放在 Windows 系統目錄中。如下列範例,雖然它名為 "personal",但實際上是 Windows NT 4.0 上的「我的文件夾」:

%windir%\profiles\kylemar\personal

Windows 2000 又將它移動。不是在系統目錄也不在根目錄中,Windows 2000 將該資料夾放在:

\Documents and Settings\KYLEMAR\My Documents

您可以發現路徑移動了,如果您已經將路徑硬性編碼,就會到錯誤的位置。事實上,在管理環境中「我的文件夾」可能在網路磁碟機上。為避免這個情況,請使用 SHGetFolderPath (稍早有討論),確定您在 Windows 95、Windows 98 或任何平台使用它。在 Windows 2000,它預設會到正確位置。

長檔名及印表機名稱

我們從 Windows 95 發行以來就不斷討論長檔名及印表機名稱。原來我們要求應用程式只要支援它們;現在使用 Windows 2000,我們必須要求應用程式正確地支援它們。我們發現有些應用程式未正確地支援長檔名。並不是應用程式根本不支援 (雖然有些應用程式不支援),而是我們發現應用程式在執行長檔名支援時發生錯誤。例如,有應用程式說它有緩衝區,對於長檔名它有完整的 256 個字元的緩衝區。不過,當我們移動檔案,並給應用程式一個較長的路徑--大約 55 個字元--應用程式會當掉。應用程式告訴我們它有長的緩衝區,但實際上卻傳給我們較短的緩衝區。它是應用程式的簡單錯誤;現在我們移到 Documents and Settings,取代根目錄或 Windows 系統目錄,可能特別會看到那類錯誤。路徑現在傾向於較長,平均路徑是 60 到 70 個字元,而不是 30 到 40 個字元。我們發現長路徑會導致更多錯誤。

在 Documents and Settings,另一個問題是,我們捨棄許多應用程式因為 "Documents (空格) and (空格) Settings"。應用程式解析目錄,當它們一發現 "documents" 這個字,便假設已到達鏈結結尾,實際上鏈結結尾是 "My (空格) Documents"。應用程式會中斷,而推想「因為我發現 'documents',我已到達我的文件夾」。那行不通的。

請務必檢查並測試您的長檔名支援。您可以在 Windows 2000 「應用程式規格」(http://msdn.microsoft.com/certification/appspec.asp) 中找到一個長字串,您可以用它來確定正確支援長檔名。

堆集管理

其他應用程式穩定性議題是因為我們在 Windows NT 平台變更堆集管理而發現的。這是目前為止最可怕的問題;它最不易被察覺,並且會引起應用程式可能發生的所有問題。它實際上是相同的 C 舊問題--記憶體的錯誤指標或錯誤使用。但它可能是很難處理。

實際上是從 NT 4.0 Service Pack 4 開始。我們變更堆集管理員,讓它更有效率,尤其在多重處理器機器上執行更快。Windows 2000 做了其他變更,因此可能您的應用程式可以在 Windows NT 4.0 執行,當您安裝 Service Pack 4 後它卻損毀。或者,它在 Service Pack 4 正常運作,現在卻在 Windows 2000 上損毀,因為我們又改進堆集管理員的功能。明顯地,當您嘗試要系統效能佳,您可以讓堆集管理員更快執行,取得更好的效能。我們並未變更 API 本身;也沒有對堆集運作方式做過邏輯變更,但我們對區塊重複使用的方式做些深層變更,而暴露應用程式的錯誤。

簡而言之,我們的變更如下:先前當您釋放區塊,它會在未使用的區塊清單上,在清單底端,最後它會被過濾到頂端並被重新使用。現在,我們快取您使用的上次區塊;它們會有效地在清單頂端,當您需要其他區塊時,您會很快地取回區塊。如果您取回區塊,並且您已執行釋放,您會取回剛才所使用的區塊,這樣您會維持在相同頁面,加速系統運作速度。

現在這是與初期開發 C 與 C++ 程式時相同的問題。事實上沒有尋找這些錯誤的好方法。市面上有些商業工具可供您尋找這些問題。否則,您必須在 Windows 2000 測試您的應用程式,越徹底越好。

在我們發現許多應用程式都有的堆集問題中,最普遍的是嘗試在您釋放記憶體後存取它。應用程式會配置,它會讀取及寫入區塊,它會釋放區塊,然後嘗試再次讀取或寫入它。可怕的是,它可能導致資料損毀。您的應用程式可能會當掉,但因為它位於您所擁有的區塊或頁面,並且您從可寫入的位置來讀取或寫入,它不會導致存取違規。而是您可能損毀您的資料。希望是當機,因為那比較容易修復,但這很難說,兩種情形都可能發生。

另一個發生這個問題的原因: 為了充分運用頁面空間,Windows 2000 及 Service Pack 4 可能移動配置,如果您已執行重新配置較小區塊的話。有些程式開發人員針對可疑的最佳化說「嗯,如果我重新配置的區塊小於原先區塊,其他人就不會移動那個指標,所以我可以重新配置,並信任不移動的指標。如果我只是讓它較小,別人就無法移動它。」那真是天大錯誤。

呼叫慣例

下列議題是呼叫慣例問題。文件說您必須在所有視窗程序中使用 STDCALL。不幸地,我們發現許多應用程式在其視窗程序 (以及對話程序) 中並不使用 STDCALL。如果不使用 STDCALL,您的應用程式可能無法運作。在 Windows 95 及 Windows 98,您可以避開使用 C_DECL 慣例;換句話說,如果您忘了在視窗程序中使用 C_DECL,系統不會損毀。

我們在 Windows 2000 製作一些間接方法,解決了不少問題。不過,在上星期我發現一個問題,我要說「是,我們已放置這些控制碼,並且嘗試處理標準呼叫,但因為應用程式不使用標準呼叫,還是無法正確解決問題」。如果您確定您的視窗程序使用 STDCALL 而不是其他呼叫慣例,問題會較簡單。

另外,我們也發現應用程式使用正確呼叫慣例,卻沒有正確地執行它們,或發現編譯器的錯誤,表示沒有適當地使用呼叫慣例,或應用程式較快登錄。因為我們要最佳化,嘗試使用較緊登錄,並較小及更快執行,我們發現許多時候應用程式變成不相容,因為沒有嚴格遵守呼叫慣例。

檔案 I/O 及非緩衝區的檔案

如果您嘗試在不使用系統提供的緩衝區情況下執行檔案 I/O,您會使用 Create File FILE_FLAG_NO_BUFFERING 的旗標 (表示在讀取及寫入時,您自行提供緩衝區,不使用系統配置的緩衝區)。您必須確定為裝置傳送到 ReadFile 及 WriteFile API 的緩衝區已正確對齊。這與過去並沒有任何不同,但我們發現在 Windows 2000 對齊方式稍微不同,尤其在支援一些新裝置 (如 Ultra 66 IDE 新磁碟機)。所以當您配置緩衝區時,必須確定已正確配置它。最簡單的方式是使用 VirtualAlloc。VirtualAlloc 一定會將緩衝區對其在偶數界限;因此不管裝置需要任何大小,執行檔案 I/O 時,它一定會正確對齊。請記得,如果這麼做,也必須確定讀取及寫入是 I/O 裝置實際磁扇區大小的倍數。您可以使用 GetDiskFreeSpace 取得磁扇區大小,並確定您僅配置及讀取這些磁扇區倍數的區塊。

大型磁碟機

其他磁碟機相關議題是有些較大磁碟機的影響。明顯地,現今磁碟機遠大於 4 GB,並且可用空間也大於 4 GB。不過,如果您使用 GetDiskFreeSpace,它會傳給您一些 32 位元值,它會切斷,因為您會覆寫並對這些值四捨五入。我們發現有些應用程式傳回的磁碟空間是負數,並且發生其他問題。

您必須使用的是 GetDiskFreeSpaceEx,它使用 ULARGE_INTEGER_ 而不是 INT,這樣您可以精確看到可用空間。

開啟其他 HKEY_CURRENT_USER

有時候有些應用程式 (特別是有些伺服器應用程式) 必須取得不同的 HKEY_CURRENT_USER 資訊,不是應用程式開始的使用者或目前使用者資訊。問題是 HKEY_CURRENT_USER 實際上按每個處理快取的。應用程式應該嘗試先關閉 HKEY_CURRENT_USER,模擬新的使用者,再開啟 HKEY_CURRENT_USER 並希望取得正確的資訊。問題是,如果您使用多重引線做這樣的處理,某個引線可能已執行,其他引線可能執行一半。實際上您不知道會取得哪個 HKEY_CURRENT_USER,因為第二個開啟的會說「我已開啟 HKEY_CURRENT_USER,並且剛使用快取的那個」。嘗試那麼做的確很危險。為求改進,我們新增了 API (RegOpenCurrentUser),它會讓您正確模擬,以便取得所要的 HKEY_CURRENT_USER,而不是錯誤的那個。

檢查位元旗標

其他低階 C 類型問題:我們發現應用程式檢查選取旗標時,嘗試使用有些等號運算子,而不是實際檢查特定位元是否存在。明顯地,我們要在 Windows 2000 及後續版本中新增旗標,讓您確定要檢查位元而不是等號。我們也新增擁有人不繪製焦點及加速值,這樣它們可以有不同類型的繪圖參數。檢查位元旗標的方法如下:

if (fItemState & ODS_FOCUS)

訊息順序

就我記憶所及,我們總是警告程式開發人員,不要使用或依賴應用程式所收到的訊息順序訊息,來表示任何特殊意義。您真的不能依賴它們。我們發現有些應用程式特別依存於多重引線之應用程式的訊息順序。例如,可能發生某個引線關閉,並公布訊息到主訊息迴圈,而其他引線關閉,也公布訊息。應用程式根據引線關閉的順序來判定公佈訊息的順序。我們對於引線排程器操作的方式做了一些變更。完成公佈訊息的順序可能不是佇列的順序。如果您執行類似動作,我們發現它們取得訊息的順序不是引線關閉順序,並且發生其他問題。再次強調,如果您必須依賴訊息順序,特別是多重引線,您必須新增同步處理,並且不要在同步處理方法中使用訊息的概念。

多螢幕顯示功能

從 Windows 98 新增多螢幕顯示功能。在Windows 2000,這個功能是首次出現在 Windows NT 平台。重要的是,您必須確定您的應用程式可以正確處理負座標及很大的座標。如果您設定多螢幕顯示功能,並且只要監視器在次要監視器右邊,次要監視器便完全在負數座標空間。如果您應用程式起始的視窗應該在次要監視器,但應用程式要視窗完全看得到,不具備多螢幕顯示功能的應用程式會將視窗完全移到主要監視器,因為目前座標都是負數。實際上不應該發生這個情況。如果您要定位視窗,請確定在多螢幕測試您的應用程式,並且處理這些負座標。高正數座標也是一樣。您必須使用下圖所示的系統新公制,確定將視窗正確定位。

Windows 平台差異

最後我要討論的議題類別是 Windows 平台的基本差異,雖然較為簡短。Windows 2000 根據 Windows NT 基礎。我們期待許多客戶將他們的 Windows 9x 安裝升級到 Windows 2000。我們有測試應用程式並將他們從 Windows 9x 移到 Windows 2000 平台。您會發現在雜誌文章及 SDK 有許多相關資訊。您必須確定您的應用程式不是太針對 Windows 9x 平台,而可用於 Windows NT 平台。

目標集中的應用程式

應用程式目標集中主要是因為它們使用只有 Windows 9x 提供的 API,而不是 Windows NT 可用的 API。我以前總是使用「工具說明 API」。在 Windows 2000,我們支援一些「工具說明 API」,但有許多 Windows 9x 的 API 並不出現在 Windows NT。追蹤它們並沒有好方法。但您可以使用 SDK 上的 .csv 檔案 (基本上它是個試算表),它會告訴您關於每個 API,哪裡執行它,哪裡不執行它,運作方式等資訊。另一個方法是確定測試您的應用程式。請確定您將應用程式從 Windows 98 遷移到 Windows NT 平台。確定它們可執行。這可能較簡單,執行也較快。

您可能查覺 Windows NT 平台在 GDI 呼叫時使用完整的 32 位元座標系統,而 Windows 9x 使用 16 位元。您必須知道這些差異。事實上,Windows NT 所有的控制碼都使用完整的 32 位元。有些程式開發人員嘗試利用 Windows 9x 這個事實 (控制碼是 32 位元,卻只使用 16 位元)。在 Windows NT 這樣處理是錯誤的。

一般 Thunking

許多應用程式會掉入功能漏洞的其他原因是使用 thunk。Windows 9x 執行 flat thunk。它允許 16 位元應用程式去呼叫 32 位元應用程式,並且也允許 32 位元應用程式直接呼叫 16 位元元件或 16 位元應用程式。Windows 2000 不支援這個功能,特別是 32 位元應用程式直接呼叫 16 位元應用程式的功能。Windows 2000 或 Windows NT 執行一般 thunking。一般 thunking 允許 16 位元應用程式去呼叫 32 位元元件,並且也允許 16 位元應用程式初始對 32 位元元件的呼叫,然後從那個 32 位元元件回呼。讓 32 位元程式碼呼叫 16 位元元件的功能是無法執行且不受支援的。您實際上只能初始化呼叫從 16 位元到 32 位元,不能反向執行。關於 thunking,請牢記,即使基礎處理模式在 Windows 9x 及 Windows NT 是不同的。一般 thunking 會顯示一些差異。最簡單的方式是在 32 位元元件中埠入 16 位元元件。您必須瞭解這兩個平台上的 thunking 議題。

© 2001 Microsoft Corporation. All rights reserved. Legal Notices.