偵測 API 集合可用性

在某些情況下,某些 Windows 裝置上,某個 API 合約集名稱可能會被刻意映射到空的模組名稱。 造成這種情況的原因各有不同,但一個常見的例子是,當為資源有限的裝置設定時,Windows 作業系統可能會移除一個昂貴的系統資源功能。 這給應用程式在 API 層級正常處理選擇性功能帶來了挑戰。

測試 Win32 API 的傳統方法是使用 LoadLibrary 或 GetProcAddress。 然而,這些並非測試 API 集的可靠方法,因為會有反向轉發。 當對特定 API 採用反向轉發時,即使內部實作已被移除, LoadLibrary 或 GetProcAddress 也可能解析為有效的函式指標。 在此情況下,函式指標會指向只傳回錯誤的存根函式。

若要偵測此案例,您可以使用 IsApiSetImplemented 函式來查詢指定 API 實作的基礎可用性。 此測試報告 API 集合是否存在於執行裝置上的組合 API 集合架構中,以及是否映射到實作模組。

Important

成功的可用性查詢並不保證某次通話必定成功。 繼續處理模組載入錯誤、缺失匯出,以及 API 本身已記錄的失敗結果。

以下程式碼範例示範如何使用 IsApiSetImplemented 來判斷包含 WTSEnumerateSessionsW 的 API 集合是否在當前裝置上可用,然後再呼叫該裝置。

#include <windows.h>
#include <apiquery2.h>
#include <stdio.h>
#include <wtsapi32.h>

#pragma comment(lib, "OneCore.lib")
#pragma comment(lib, "Wtsapi32.lib")

int __cdecl wmain(int /* argc */, PCWSTR /* argv */ [])
{
    PWTS_SESSION_INFOW pInfo = nullptr;
    DWORD count = 0;

    if (!IsApiSetImplemented("ext-ms-win-session-wtsapi32-l1-1-0"))
    {
        wprintf(L"ext-ms-win-session-wtsapi32-l1-1-0 is not available.\n");
        return 0;
    }

    if (WTSEnumerateSessionsW(WTS_CURRENT_SERVER_HANDLE, 0, 1, &pInfo, &count))
    {
        wprintf(L"SessionCount = %lu\n", count);

        for (DWORD i = 0; i < count; i++)
        {
            PWTS_SESSION_INFOW pCurInfo = &pInfo[i];
            wprintf(L"    %ls: ID = %lu, state = %d\n", pCurInfo->pWinStationName,
                pCurInfo->SessionId, static_cast<int>(pCurInfo->State));
        }

        WTSFreeMemory(pInfo);
    }
    else
    {
        wprintf(L"WTSEnumerateSessionsW failure: %lu\n", GetLastError());
    }

    return 0;
}

IsApiSetImplemented 在 apiquery2.h 中宣告,而一般的公開 SDK 連結路徑則由 OneCore.lib 提供。 這個函式庫與呼叫可選目標 API 所需的匯入函式庫是分開的。

上述範例連結 Wtsapi32.lib 以進行靜態匯入,讓範例保持簡潔。 必須在缺少 API 集合的情況下執行的生產應用程式,也應套用「 保持可選程式碼路徑可達」中的指引。

選擇查詢名稱

傳送你正在測試的 API 集合名稱。 API 集合名稱通常不加 .dll 後綴,本頁範例即使用此形式。 這個後綴並不是 API 集合名稱的一部分。

要找到要傳遞的名稱,請參閱參考頁面中你想呼叫的 API 需求 表。 如果該資料表有 API 設定 的列,它就會給出合約名稱。 否則使用 DLL 列,但前提是該名稱本身是以 api- or ext-開頭的合約名稱;像 Wtsapi32.dll 這類實體模組名稱不是合約,查詢時會回傳 FALSE。

名稱的形式取決於 API 的位址。

API 介面 查詢形式 Example
命名群 <contract>~<group> api-win-core-samplefeature~AdvancedOperations
預設群組 合約別名,包含 ~Default api-win-core-samplefeature
版本化合約 完整版本化合約名稱 ext-ms-win-core-samplefeature-l1-1-0

這些samplefeature名稱是虛構 Windows 元件的說明性名稱。 名稱前綴(api- 或 ext-)不會影響可用性行為。

對於命名群組,成功的查詢表示該群組存在,其合約已映射到實作模組,該主機在目前執行環境中可用,群組未被停用,且與該群組相關的任何系統功能已被啟用。 使用合約別名的查詢會套用合約與主機檢查。

如果 API 的公開標頭提供 Is<APIName>Present 輔助工具,建議優先使用該輔助工具。 它已經包含承載該 API 的 API 集合或群組的正確名稱。

查詢的結果是契約細節或群組細節,而非函式細節。 兩個由相同命名群組支援的輔助器,即使每個輔助器名稱為不同的 API,也會回傳相同的結果。

保持可選的程式碼路徑可達

如果可選 API 是靜態匯入連結,可用性檢查無法保護程序啟動。 載入器會在程式碼執行前解決靜態匯入,因此缺少的模組會在執行到檢查前失敗。

請採用以下其中一種方法:

  • 設定攜帶可選 API 的模組以進行 延遲載入。 延遲載入是一種連結器設定:指定 /DELAYLOAD:<module> 並連結 delayimp.lib。 指定你二進位檔匯入表中出現的模組名稱,可能是經典的 DLL 名稱,而非 API 集合的合約名稱。 在前述例子中,該名稱為 WTSAPI32.dll。
  • 或者在可用性查詢成功後,用 LoadLibrary 和 GetProcAddress 動態解析目標。

不要用 LoadLibrary 或 GetProcAddress 來取代可用性查詢。 它們會回答模組和匯出問題,不會評估命名的群組狀態,並且可以如前所述解析成反向轉發的存根。 查詢後再用它們處理獨立模組和匯出檢查。

早期版本 Windows 上的行為

OneCore.lib 提供的實作會在執行時選擇可用的查詢機制,因此呼叫 IsApiSetDelivered 的應用程式仍可在基礎查詢支援之前的系統上執行。

在沒有查詢機制的系統上:

  • 包含 ~ 的群組限定名稱會回傳 FALSE。 無法評估命名群組的系統無法報告群組是否可用。
  • 不符合群組資格的名稱可以回傳 TRUE。 這保留了與早期 Windows 版本中自有轉發 DLL 的應用程式的相容性,因為該轉發器確實履行了合約。

不要用 API 設定的可用性查詢來檢查安全或授權。

另請參閱