Wykrywanie dostępności zestawu interfejsów API

W niektórych przypadkach dana nazwa kontraktu zestawu interfejsów API może być celowo zamapowana na pustą nazwę modułu na niektórych urządzeniach Windows. Przyczyny tej różnicy, ale typowym przykładem jest to, że kosztowna funkcja pod względem zasobów systemowych może zostać usunięta z systemu operacyjnego Windows w przypadku skonfigurowania urządzenia z ograniczonymi zasobami. Stanowi to wyzwanie dla aplikacji, aby bezpiecznie obsługiwać funkcje opcjonalne na poziomie interfejsu API.

Tradycyjne podejście do testowania, czy dostępny jest interfejs API Win32, to użycie LoadLibrary lub GetProcAddress. Nie są to jednak niezawodne metody testowania zestawów interfejsów API ze względu na przekierowywanie odwrotne. W przypadku zastosowania przekazywania wstecznego do danego interfejsu API funkcja LoadLibrary lub GetProcAddress może być rozpoznawana jako prawidłowy wskaźnik funkcji nawet w przypadkach usunięcia wewnętrznej implementacji. W takim przypadku wskaźnik funkcji będzie wskazywać funkcję wycinkową, która po prostu zwraca błąd.

Aby wykryć ten przypadek, możesz użyć funkcji IsApiSetImplemented, aby wykonać zapytanie dotyczące podstawowej dostępności danej implementacji interfejsu API. Ten test raportuje, czy zestaw interfejsów API znajduje się w schemacie zestawu interfejsu API komponowanego na uruchomionym urządzeniu i czy jest mapowany na moduł implementacji.

Ważna

Pomyślne zapytanie dostępności nie jest gwarancją, że określone wywołanie powiedzie się. Kontynuuj obsługę błędów ładowania modułów, brak eksportów i udokumentowanych wyników awarii interfejsu API.

W poniższym przykładzie kodu pokazano, jak używać metody IsApiSetImplemented w celu określenia, czy zestaw interfejsu API, który zawiera element WTSEnumerateSessionsW , jest dostępny na bieżącym urządzeniu przed wywołaniem go.

#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 jest zadeklarowany w pliku apiquery2.h, a normalna publiczna ścieżka łącza zestawu SDK dostarcza ją z biblioteki OneCore.lib. Ta biblioteka jest oddzielona od dowolnej biblioteki importu potrzebnej do wywołania opcjonalnego docelowego interfejsu API.

Powyższy przykład łączy bibliotekę Wtsapi32.lib dla importu statycznego, co sprawia, że przykład jest krótki. Aplikacja produkcyjna, która musi działać, gdy zestaw interfejsów API jest nieobecny, powinna również zastosować wskazówki zawarte w temacie Zachowaj dostępną opcjonalną ścieżkę kodu.

Wybierz nazwę zapytania

Przekaż nazwę zestawu interfejsu API, który testujesz. Nazwy zestawów interfejsów API są zwykle zapisywane bez sufiksu .dll , a przykłady na tej stronie używają tego formularza. Sufiks nie jest częścią nazwy zestawu interfejsu API.

Aby znaleźć nazwę do przekazania, zobacz tabelę Requirements (Wymagania ) na stronie referencyjnej interfejsu API, który chcesz wywołać. Jeśli ta tabela zawiera wiersz zestawu interfejsów API , nadaje nazwę kontraktu. W przeciwnym razie użyj wiersza biblioteki DLL , ale tylko wtedy, gdy nazwa kontraktu zaczyna się od api- lub ext-; nazwa modułu fizycznego, taka jak Wtsapi32.dll nie jest kontraktem, i zapytanie zwraca wartość FALSE.

Forma nazwy zależy od sposobu adresowania interfejsu API.

Powierzchnia interfejsu API Formularz zapytania Example
Nazwana grupa <contract>~<group> api-win-core-samplefeature~AdvancedOperations
Grupa domyślna Alias kontraktu bez ~Default api-win-core-samplefeature
Wersja kontraktu Kompletna nazwa umowy w wersji ext-ms-win-core-samplefeature-l1-1-0

Nazwy samplefeature są ilustracyjnymi nazwami fikcyjnego składnika Windows. Prefiks nazwy (api- lub ext-) nie odgrywa roli w zachowaniu dostępności.

W przypadku nazwanej grupy pomyślne zapytanie oznacza, że grupa istnieje, jego kontrakt jest mapowany na moduł implementacji, host jest dostępny w bieżącym środowisku wykonywania, grupa nie jest wyłączona, a każda funkcja systemowa skojarzona z grupą jest włączona. Zapytanie korzystające z aliasu kontraktu stosuje sprawdzanie kontraktu i hosta.

Jeśli nagłówek publiczny interfejsu API dostarcza Is<APIName>Present pomocnika, preferuj ten pomocnik. Zawiera już poprawną nazwę zestawu interfejsu API lub grupy, która przenosi interfejs API.

Wynikiem zapytania jest stopień szczegółowości kontraktu lub grupy, a nie szczegółowa funkcja. Dwa pomocniki wspierane przez tę samą grupę o tej samej nazwie zawsze zwracają ten sam wynik, mimo że każdy pomocnik ma nazwę innego interfejsu API.

Zachowaj dostępną opcjonalną ścieżkę kodu

Sprawdzanie dostępności nie może chronić uruchamiania procesu, jeśli opcjonalny interfejs API jest połączony jako import statyczny. Moduł ładujący rozwiązuje problemy ze statycznymi importami przed uruchomieniem kodu, więc brakujący moduł kończy się niepowodzeniem procesu przed osiągnięciem sprawdzenia.

Użyj jednego z następujących podejść:

  • Skonfiguruj moduł, który przenosi opcjonalny interfejs API na potrzeby ładowania opóźnionego. Opóźnienie ładowania to ustawienie konsolidatora: określ /DELAYLOAD:<module> i link delayimp.lib. Określ nazwę modułu wyświetlaną w tabeli importu pliku binarnego, która może być klasyczną nazwą biblioteki DLL, a nie nazwą kontraktu zestawu interfejsu API. W poprzednim przykładzie ta nazwa to WTSAPI32.dll.
  • Możesz też rozwiązać problem z obiektem docelowym dynamicznie za pomocą parametrów LoadLibrary i GetProcAddress po pomyślnym wykonaniu zapytania dostępności.

Nie używaj elementu LoadLibrary ani GetProcAddress jako zamiennika zapytania dostępności. Odpowiadają na pytania modułu i eksportują, nie oceniają nazwanego stanu grupy i mogą rozwiązać problem z wycinkiem przekierowania wstecznego zgodnie z wcześniejszym opisem. Użyj ich po zapytaniu, aby obsłużyć oddzielny moduł i testy eksportu.

Zachowanie we wcześniejszych wersjach Windows

Implementacja dostarczana przez bibliotekę OneCore.lib wybiera dostępny mechanizm zapytań w czasie wykonywania, więc aplikacja, która wywołuje metodę IsApiSetImplemented , nadal może działać w systemie, który poprzedza obsługę zapytań bazowych.

W systemie, w którym nie jest dostępny żaden mechanizm zapytań:

  • Nazwa kwalifikowana przez grupę zawierająca ~ zwraca wartość FALSE. System, który nie może ocenić nazwanych grup, nie może zgłosić, że grupa jest dostępna.
  • Nazwa, która nie jest kwalifikowana przez grupę, może zwrócić wartość TRUE. Pozwala to zachować zgodność z aplikacjami, które dostarczyły własne biblioteki DLL usług przesyłania dalej we wczesnych wersjach Windows, gdzie umowa była w rzeczywistości zgodna z tym usługą przesyłania dalej.

Nie używaj zapytania dostępności zestawu interfejsów API jako sprawdzania zabezpieczeń ani autoryzacji.

Zobacz także