Nuta
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować się zalogować lub zmienić katalog.
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zmienić katalogi.
Pseudokonsole systemu Windows, czasami nazywane pseudo konsolą, ConPTY lub Windows PTY, jest mechanizmem przeznaczonym do tworzenia hosta zewnętrznego dla działań podsystemu trybu znaków, które zastępują część interakcyjności użytkownika w domyślnym oknie hosta konsoli.
Hostowanie sesji pseudokonsole jest nieco inne niż tradycyjna sesja konsoli. Tradycyjne sesje konsoli są uruchamiane automatycznie, gdy system operacyjny rozpoznaje, że aplikacja w trybie znaków ma zostać uruchomiona. Z kolei sesja pseudokonsole i kanały komunikacyjne muszą zostać utworzone przez aplikację hostingową przed utworzeniem procesu z aplikacją trybu znaków podrzędnych do hostowania. Proces podrzędny będzie nadal tworzony przy użyciu funkcji CreateProcess , ale z dodatkowymi informacjami, które przekierowują system operacyjny w celu ustanowienia odpowiedniego środowiska.
Dodatkowe informacje ogólne na temat tego systemu można znaleźć we wstępnym wpisie w blogu z ogłoszeniem.
Kompletne przykłady użycia pseudokonsole są dostępne w naszym repozytorium GitHub microsoft/terminalu w katalogu samples.
Przygotowywanie kanałów komunikacyjnych
Pierwszym krokiem jest utworzenie pary synchronicznych kanałów komunikacyjnych, które będą udostępniane podczas tworzenia sesji pseudokonsole na potrzeby dwukierunkowej komunikacji z hostowaną aplikacją. Te kanały są przetwarzane przez system pseudokonsole przy użyciu plików ReadFile i WriteFile z synchronicznym we/wy. Obsługa plików lub we/wy urządzeń, takich jak strumień plików lub potok, jest akceptowalna, o ile struktura NAKŁADAJĄCE SIĘ nie jest wymagana do komunikacji asynchronicznej.
Ostrzeżenie
Aby zapobiec warunkom wyścigu i zakleszczeniom, zdecydowanie zalecamy, aby każdy z kanałów komunikacyjnych był obsługiwany w osobnym wątku, który utrzymuje swój własny stan buforu klienta i kolejkę obsługi komunikatów wewnątrz aplikacji. Obsługa wszystkich działań pseudokonsole w tym samym wątku może spowodować zakleszczenie, w którym jeden z komunikacji jest wypełniony i czeka na akcję podczas próby wysłania żądania blokowania w innym kanale.
Tworzenie pseudokonsole
Po ustanowieniu kanałów komunikacyjnych zidentyfikuj koniec kanału wejściowego "odczyt" i koniec "zapisu" kanału wyjściowego. Ta para dojść jest udostępniana podczas wywoływania metody CreatePseudoConsole w celu utworzenia obiektu.
Podczas tworzenia wymagany jest rozmiar reprezentujący wymiary X i Y (w liczbie znaków). Są to wymiary, które będą stosowane do powierzchni wyświetlania dla końcowego (terminalu) okna prezentacji. Wartości są używane do tworzenia buforu w pamięci wewnątrz systemu pseudokonsole.
Rozmiar buforu udostępnia odpowiedzi na aplikacje trybu znaków klienta, które sondują informacje przy użyciu funkcji konsoli po stronie klienta , takich jak GetConsoleScreenBufferInfoEx , i określają układ i pozycjonowanie tekstu, gdy klienci używają funkcji, takich jak WriteConsoleOutput.
Na koniec pole flagi jest udostępniane podczas tworzenia pseudokonsole w celu wykonania specjalnych funkcji. Domyślnie ustaw tę wartość na 0, aby nie miała specjalnej funkcjonalności.
Obecnie dostępna jest tylko jedna specjalna flaga do żądania dziedziczenia położenia kursora z sesji konsoli dołączonej już do obiektu wywołującego interfejsu API pseudokonsole. Jest to przeznaczone do użytku w bardziej zaawansowanych scenariuszach, w których aplikacja hostingu, która przygotowuje sesję pseudokonsole, jest również aplikacją trybu znaków klienta innego środowiska konsoli.
Poniżej przedstawiono przykładowy fragment kodu przy użyciu metody CreatePipe w celu ustanowienia pary kanałów komunikacyjnych i utworzenia 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;
}
// ...
}
Uwaga / Notatka
Ten fragment kodu jest niekompletny i używany tylko do demonstracji tego konkretnego wywołania. Należy odpowiednio zarządzać okresem istnienia uchwytu. Brak prawidłowego zarządzania okresem istnienia obsługimoże spowodować zakleszczenie scenariuszy, zwłaszcza w przypadku synchronicznych wywołań we/wy.
Po zakończeniu wywołania CreateProcess w celu utworzenia aplikacji trybu znaków klienta dołączonej do pseudokonsole uchwyty podane podczas tworzenia powinny zostać zwolnione z tego procesu. Spowoduje to zmniejszenie liczby odwołań względem bazowego obiektu urządzenia i umożliwienie operacji we/wy prawidłowego wykrywania uszkodzonego kanału, gdy sesja pseudokonsole zamyka jego kopię dojść.
Przygotowanie do utworzenia procesu podrzędnego
Kolejną fazą jest przygotowanie struktury STARTUPINFOEX , która przekaże informacje pseudokonsole podczas uruchamiania procesu podrzędnego.
Ta struktura zawiera możliwość udostępniania złożonych informacji dotyczących uruchamiania, w tym atrybutów tworzenia procesów i wątków.
Użyj metody InitializeProcThreadAttributeList w sposób podwójnego wywołania, aby najpierw obliczyć liczbę bajtów wymaganych do przechowywania listy, przydziel żądaną pamięć, a następnie wywołaj ponownie wskaźnik nieprzezroczystej pamięci, aby skonfigurować ją jako listę atrybutów.
Następnie wywołaj metodę UpdateProcThreadAttribute przekazując zainicjowaną listę atrybutów z flagą PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE, uchwyt pseudokonsole i rozmiar uchwytu pseudokonsole.
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;
}
Tworzenie procesu hostowanego
Następnie wywołaj metodę CreateProcess przekazującą strukturę STARTUPINFOEX wraz ze ścieżką do pliku wykonywalnego i wszelkimi dodatkowymi informacjami o konfiguracji, jeśli ma to zastosowanie. Ważne jest, aby ustawić flagę EXTENDED_STARTUPINFO_PRESENT podczas wywoływania alertu systemu, że odwołanie pseudokonsole znajduje się w rozszerzonych informacjach.
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());
}
// ...
}
Uwaga / Notatka
Zamknięcie sesji pseudokonsole podczas uruchamiania hostowanego procesu i nawiązanie połączenia może spowodować wyświetlenie okna dialogowego błędu przez aplikację kliencą. To samo okno dialogowe błędu jest wyświetlane, jeśli hostowany proces ma nieprawidłową obsługę pseudokonsole na potrzeby uruchamiania. W kodzie inicjowania procesu hostowanego te dwie okoliczności są identyczne. Okno dialogowe wyskakujące z hostowanej aplikacji klienckiej po niepowodzeniu zostanie odczytane 0xc0000142 z zlokalizowanym komunikatem z informacją o niepowodzeniu inicjowania.
Komunikacja z sesją pseudokonsole
Po pomyślnym utworzeniu procesu aplikacja hostingowa może używać końca zapisu potoku wejściowego do wysyłania informacji o interakcji użytkownika do pseudokonsole i końca odczytu potoku wyjściowego w celu odbierania informacji o prezentacji graficznej z pseudo konsoli.
To jest całkowicie do aplikacji hostingowej, aby zdecydować, jak obsługiwać dalsze działania. Aplikacja hostingowa może uruchomić okno w innym wątku, aby zebrać dane wejściowe interakcji użytkownika i serializować je na końcu zapisu potoku wejściowego dla pseudokonsole i hostowanej aplikacji trybu znaków. Inny wątek można uruchomić, aby opróżnić koniec odczytu potoku wyjściowego dla pseudokonsole, zdekodować tekst i wirtualne informacje o sekwencji terminalu i przedstawić je na ekranie.
Wątki mogą również służyć do przekazywania informacji z kanałów pseudokonsole do innego kanału lub urządzenia, w tym sieci do informacji zdalnych do innego procesu lub maszyny i uniknięcia dowolnego lokalnego transkodowania informacji.
Zmiana rozmiaru pseudokonsole
W całym czasie wykonywania może istnieć sytuacja, w której rozmiar buforu musi zostać zmieniony z powodu interakcji użytkownika lub żądania odebranego poza pasmem z innego urządzenia wyświetlania/interakcji.
Można to zrobić za pomocą funkcji ResizePseudoConsole określającej zarówno wysokość, jak i szerokość buforu w liczbie znaków.
// 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);
}
Kończenie sesji pseudokonsole
Aby zakończyć sesję, wywołaj funkcję ClosePseudoConsole z uchwytem z oryginalnego tworzenia pseudokonsole. Wszystkie dołączone aplikacje trybu znaków klienta, takie jak te z wywołania CreateProcess , zostaną zakończone po zamknięciu sesji. Jeśli oryginalne dziecko było aplikacją typu powłoki, która tworzy inne procesy, wszystkie powiązane dołączone procesy w drzewie również zostaną zakończone.
Ostrzeżenie
Zamknięcie sesji ma kilka skutków ubocznych, które mogą spowodować impas, jeśli pseudokonsole jest stosowany w sposób synchroniczny jednowątkowy. Akt zamknięcia sesji pseudokonsole może emitować ostateczną aktualizację ramową, do hOutput której należy opróżnić z buforu kanału komunikacji. Ponadto, jeśli PSEUDOCONSOLE_INHERIT_CURSOR został wybrany podczas tworzenia pseudokonsole, próba zamknięcia pseudokonsole bez odpowiadania na komunikat zapytania dziedziczenia kursora (odebrany hOutput i odpowiedział za pośrednictwem hInput) może spowodować inny warunek zakleszczenia. Zaleca się, aby kanały komunikacji dla pseudokonsole działały na poszczególnych wątkach i pozostają opróżniane i przetwarzane do momentu przerwania własnej zgody przez aplikację kliencją lub przez zakończenie działań usuwania w wywołaniu funkcji ClosePseudoConsole .