Sdílet prostřednictvím


Vytvoření pseudokonsoleové relace

Pseudokonsole Windows, někdy označovaná také jako pseudokonsole, ConPTY nebo Windows PTY, je mechanismus navržený pro vytvoření externího hostitele pro aktivity subsystému v režimu znaků, které nahrazují část interaktivity uživatele výchozího okna hostitele konzoly.

Hostování pseudokonsoleové relace se trochu liší od tradiční konzolové relace. Tradiční relace konzoly se automaticky spustí, když operační systém rozpozná, že aplikace v režimu znaků se chystá spustit. Naproti tomu před vytvořením procesu s podřízenou aplikací v režimu znaků, která se má hostovat, je potřeba vytvořit pseudokonsoleovou relaci a komunikační kanály. Podřízený proces bude stále vytvořen pomocí funkce CreateProcess , ale s některými dalšími informacemi, které nasměrují operační systém na vytvoření vhodného prostředí.

Další informace o tomto systému najdete v blogovém příspěvku s počátečním oznámením.

Kompletní příklady použití pseudokonsole jsou k dispozici v našem úložišti GitHub microsoft/terminal v adresáři ukázek.

Příprava komunikačních kanálů

Prvním krokem je vytvoření dvojice synchronních komunikačních kanálů, které se budou poskytovat během vytváření pseudokonsole relace pro obousměrnou komunikaci s hostované aplikace. Tyto kanály zpracovává pseudokonsole systém pomocí ReadFile a WriteFile s synchronní vstupně-výstupní operace. Obslužné rutiny souborového nebo vstupně-výstupního zařízení, jako je datový proud souboru nebo kanál, jsou přijatelné, pokud se pro asynchronní komunikaci nevyžaduje překrývající se struktura.

Výstraha

Abychom zabránili podmínkám časování a zablokování, důrazně doporučujeme, aby byly všechny komunikační kanály obsluhovány na samostatném vlákně, které udržuje svůj vlastní stav vyrovnávací paměti klienta a frontu zasílání zpráv uvnitř vaší aplikace. Údržba všech pseudokonsoleových aktivit ve stejném vlákně může vést k zablokování, kdy je vyplněná jedna z komunikačních vyrovnávacích pamětí a čeká se na vaši akci při pokusu o odeslání blokující žádosti v jiném kanálu.

Vytvoření pseudokonsole

S vytvořenými komunikačními kanály identifikujte konec vstupního kanálu "čtení" a konec "zápisu" výstupního kanálu. Tato dvojice popisovačů je k dispozici při volání CreatePseudoConsole k vytvoření objektu.

Při vytváření je vyžadována velikost představující rozměry X a Y (v počtu znaků). Jedná se o rozměry, které se použijí na plochu zobrazení pro konečné okno prezentace (terminálu). Hodnoty se používají k vytvoření vyrovnávací paměti v systému pseudokonsole.

Velikost vyrovnávací paměti poskytuje odpovědi na aplikace v režimu znaků klienta, které testují informace pomocí funkcí konzoly na straně klienta , jako je GetConsoleScreenBufferInfoEx , a určuje rozložení a umístění textu, když klienti používají funkce, jako je WriteConsoleOutput.

Nakonec je k dispozici pole příznaků při vytváření pseudokonsole pro provádění speciálních funkcí. Ve výchozím nastavení nastavte hodnotu 0 tak, aby neměla žádné speciální funkce.

V tuto chvíli je k dispozici pouze jeden speciální příznak pro vyžádání dědění pozice kurzoru z relace konzoly, která je již připojena k volajícímu pseudokonsole API. To je určeno pro použití v pokročilejších scénářích, kdy hostitelská aplikace, která připravuje pseudokonsole relaci, je také klientská aplikace v režimu znaků jiného prostředí konzoly.

Níže je k dispozici ukázkový fragment kódu využívající CreatePipe k vytvoření dvojice komunikačních kanálů a vytvoření pseudokonsole.


HRESULT SetUpPseudoConsole(COORD size)
{
    HRESULT hr = S_OK;

    // Create communication channels

    // - Close these after CreateProcess of child application with pseudoconsole object.
    HANDLE inputReadSide, outputWriteSide;

    // - Hold onto these and use them for communication with the child through the pseudoconsole.
    HANDLE outputReadSide, inputWriteSide;

    if (!CreatePipe(&inputReadSide, &inputWriteSide, NULL, 0))
    {
        return HRESULT_FROM_WIN32(GetLastError());
    }

    if (!CreatePipe(&outputReadSide, &outputWriteSide, NULL, 0))
    {
        return HRESULT_FROM_WIN32(GetLastError());
    }

    HPCON hPC;
    hr = CreatePseudoConsole(size, inputReadSide, outputWriteSide, 0, &hPC);
    if (FAILED(hr))
    {
        return hr;
    }

    // ...

}

Poznámka:

Tento fragment kódu je neúplný a používá se pouze pro ukázku tohoto konkrétního volání. Budete muset odpovídajícím způsobem spravovat životnost popisovačů. Selháním správy životnosti popisovačůsprávně může dojít ke scénářům vzájemného zablokování, zejména u synchronních vstupně-výstupních volání.

Po dokončení volání CreateProcess vytvořit aplikaci v režimu znaků klienta připojenou k pseudokonsole, popisovače zadané během vytváření by měly být uvolněny z tohoto procesu. Tím se sníží počet odkazů na základní objekt zařízení a umožníte operacím vstupně-výstupních operací správně rozpoznat poškozený kanál, když pseudokonsoleová relace zavře jeho kopii popisovačů.

Příprava na vytvoření podřízeného procesu

Další fází je příprava struktury STARTUPINFOEX , která bude předávat pseudokonsole informace při spuštění podřízeného procesu.

Tato struktura obsahuje schopnost poskytovat komplexní informace o spuštění, včetně atributů pro proces a vytváření vláken.

Pomocí metody InitializeProcThreadAttributeList můžete nejprve vypočítat počet bajtů potřebných k uložení seznamu, přidělit požadovanou paměť a potom znovu zavolat, aby byl ukazatel neprůhledné paměti nastaven jako seznam atributů.

Dále zavolejte UpdateProcThreadAttribute předávání seznam inicializovaných atributů příznakem PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE, pseudokonsole popisovač a velikost pseudokonsole popisovače.


HRESULT PrepareStartupInformation(HPCON hpc, STARTUPINFOEX* psi)
{
    // Prepare Startup Information structure
    STARTUPINFOEX si;
    ZeroMemory(&si, sizeof(si));
    si.StartupInfo.cb = sizeof(STARTUPINFOEX);

    // Discover the size required for the list
    size_t bytesRequired;
    InitializeProcThreadAttributeList(NULL, 1, 0, &bytesRequired);

    // Allocate memory to represent the list
    si.lpAttributeList = (PPROC_THREAD_ATTRIBUTE_LIST)HeapAlloc(GetProcessHeap(), 0, bytesRequired);
    if (!si.lpAttributeList)
    {
        return E_OUTOFMEMORY;
    }

    // Initialize the list memory location
    if (!InitializeProcThreadAttributeList(si.lpAttributeList, 1, 0, &bytesRequired))
    {
        HeapFree(GetProcessHeap(), 0, si.lpAttributeList);
        return HRESULT_FROM_WIN32(GetLastError());
    }

    // Set the pseudoconsole information into the list
    if (!UpdateProcThreadAttribute(si.lpAttributeList,
                                   0,
                                   PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE,
                                   hpc,
                                   sizeof(hpc),
                                   NULL,
                                   NULL))
    {
        HeapFree(GetProcessHeap(), 0, si.lpAttributeList);
        return HRESULT_FROM_WIN32(GetLastError());
    }

    *psi = si;

    return S_OK;
}

Vytvoření hostovaného procesu

Dále zavolejte CreateProcess , který předává strukturu STARTUPINFOEX spolu s cestou ke spustitelnému souboru a všechny další informace o konfiguraci, pokud je to možné. Je důležité nastavit příznak EXTENDED_STARTUPINFO_PRESENT při volání výstrah systému, že pseudokonsole odkaz je obsažen v rozšířených informacích.

HRESULT SetUpPseudoConsole(COORD size)
{
    // ...

    PCWSTR childApplication = L"C:\\windows\\system32\\cmd.exe";

    // Create mutable text string for CreateProcessW command line string.
    const size_t charsRequired = wcslen(childApplication) + 1; // +1 null terminator
    PWSTR cmdLineMutable = (PWSTR)HeapAlloc(GetProcessHeap(), 0, sizeof(wchar_t) * charsRequired);

    if (!cmdLineMutable)
    {
        return E_OUTOFMEMORY;
    }

    wcscpy_s(cmdLineMutable, charsRequired, childApplication);

    PROCESS_INFORMATION pi;
    ZeroMemory(&pi, sizeof(pi));

    // Call CreateProcess
    if (!CreateProcessW(NULL,
                        cmdLineMutable,
                        NULL,
                        NULL,
                        FALSE,
                        EXTENDED_STARTUPINFO_PRESENT,
                        NULL,
                        NULL,
                        &siEx.StartupInfo,
                        &pi))
    {
        HeapFree(GetProcessHeap(), 0, cmdLineMutable);
        return HRESULT_FROM_WIN32(GetLastError());
    }

    // ...
}

Poznámka:

Zavření pseudokonsole relace, zatímco hostovaný proces se stále spouští a připojování může vést k chybovému dialogu zobrazenému klientskou aplikací. Stejné chybové dialogové okno se zobrazí, pokud hostovaný proces má neplatný pseudokonsole popisovač pro spuštění. Pro inicializační kód hostovaného procesu jsou dvě okolnosti identické. Automaticky otevírané dialogové okno z hostované klientské aplikace při selhání se přečte 0xc0000142 s lokalizovanou zprávou s podrobnostmi o selhání inicializace.

Komunikace s pseudokonsoleovou relací

Po úspěšném vytvoření procesu může hostitelská aplikace použít konec zápisu vstupního kanálu k odesílání informací o interakci uživatele do pseudokonsole a čtení výstupního kanálu pro příjem grafických informací o prezentaci z pseudo konzoly.

Je zcela na hostitelské aplikaci, aby se rozhodla, jak zpracovat další aktivity. Hostitelská aplikace může spustit okno v jiném vlákně, které shromažďuje vstup interakce uživatele a serializuje ho na konec zápisu vstupního kanálu pro pseudokonsole a hostované aplikace v režimu znaků. Můžete spustit další vlákno, které vyprázdní konec výstupního kanálu pro pseudokonsole, dekóduje text a informace o posloupnosti virtuálních terminálů a zobrazí se na obrazovce.

Vlákna lze také použít k předávání informací z pseudokonsole kanálů do jiného kanálu nebo zařízení, včetně sítě do vzdálené informace do jiného procesu nebo počítače a vyhnout se místnímu překódování informací.

Změna velikosti pseudokonsole

V průběhu běhu může docházet k okolnostem, kdy je potřeba změnit velikost vyrovnávací paměti z důvodu interakce uživatele nebo požadavku přijatého mimo pásmo z jiného zařízení pro zobrazení/interakci.

To lze provést pomocí funkce ResizePseudoConsole určující výšku i šířku vyrovnávací paměti v počtu znaků.

// Theoretical event handler function with theoretical
// event that has associated display properties
// on Source property.
void OnWindowResize(Event e)
{
    // Retrieve width and height dimensions of display in
    // characters using theoretical height/width functions
    // that can retrieve the properties from the display
    // attached to the event.
    COORD size;
    size.X = GetViewWidth(e.Source);
    size.Y = GetViewHeight(e.Source);

    // Call pseudoconsole API to inform buffer dimension update
    ResizePseudoConsole(m_hpc, size);
}

Ukončení pseudokonsoleové relace

Chcete-li ukončit relaci, zavolejte funkci ClosePseudoConsole s popisovačem z původní pseudokonsole vytvoření. Všechny připojené aplikace v režimu znaků klienta, například aplikace z volání CreateProcess, budou ukončeny při zavření relace. Pokud byla původní podřízená aplikace typu prostředí, která vytváří další procesy, všechny související připojené procesy ve stromu se také ukončí.

Výstraha

Uzavření relace má několik vedlejších účinků, které můžou vést k zablokování, pokud se pseudokonsole používá synchronně s jedním vláknem. Ukončení pseudokonsole relace může generovat konečnou aktualizaci rámce, do hOutput které by se mělo vyprázdnit z vyrovnávací paměti komunikačního kanálu. Pokud PSEUDOCONSOLE_INHERIT_CURSOR byl při vytváření pseudokonsole vybrán, pokus o zavření pseudokonsole bez reakce na zprávu dotazu zdědědění kurzoru (přijato hOutput a odpověděno prostřednictvím hInput) může vést k jinému stavu vzájemného zablokování. Doporučuje se, aby komunikační kanály pro pseudokonsole byly obsluhovány na jednotlivých vláknech a zůstávají vyprázdněny a zpracovávány, dokud klientská aplikace ukončí nebo dokončí aktivity slz při volání funkce ClosePseudoConsole .