Hinweis
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, sich anzumelden oder das Verzeichnis zu wechseln.
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, das Verzeichnis zu wechseln.
Die Windows Pseudoconsole, manchmal auch als Pseudokonsole, ConPTY oder Windows PTY bezeichnet, ist ein Mechanismus zum Erstellen eines externen Hosts für Zeichenmodus-Subsystemaktivitäten, die den Benutzerinteraktivitätsteil des Standardkonsolenhostfensters ersetzen.
Das Hosten einer Pseudoconsole-Sitzung unterscheidet sich etwas von einer herkömmlichen Konsolensitzung. Herkömmliche Konsolensitzungen werden automatisch gestartet, wenn das Betriebssystem erkennt, dass eine Zeichenmodusanwendung ausgeführt werden soll. Im Gegensatz dazu müssen eine Pseudoconsole-Sitzung und die Kommunikationskanäle von der Hostinganwendung erstellt werden, bevor der Prozess mit der untergeordneten Zeichenmodusanwendung gehostet wird. Der untergeordnete Prozess wird weiterhin mithilfe der CreateProcess-Funktion erstellt, aber mit einigen zusätzlichen Informationen, die das Betriebssystem leiten, um die entsprechende Umgebung einzurichten.
Weitere Hintergrundinformationen zu diesem System finden Sie im ersten Blogbeitrag zur Ankündigung.
Vollständige Beispiele für die Verwendung der Pseudoconsole sind in unserem GitHub-Repository microsoft/terminal im Beispielverzeichnis verfügbar.
Vorbereiten der Kommunikationskanäle
Der erste Schritt besteht darin, ein Paar synchroner Kommunikationskanäle zu erstellen, die während der Erstellung der Pseudoconsole-Sitzung für die bidirektionale Kommunikation mit der gehosteten Anwendung bereitgestellt werden. Diese Kanäle werden vom Pseudoconsole-System mithilfe von ReadFile und WriteFile mit synchroner E/A verarbeitet. Datei- oder E/A-Gerätehandles wie ein Dateidatenstrom oder -pipe sind akzeptabel, solange für die asynchrone Kommunikation keine ÜBERLAPPENDE Struktur erforderlich ist.
Warnung
Um Rennbedingungen und Deadlocks zu verhindern, empfehlen wir dringend, dass jeder der Kommunikationskanäle in einem separaten Thread gewartet wird, der seinen eigenen Clientpufferstatus und die Messagingwarteschlange in Ihrer Anwendung verwaltet. Die Wartung aller Pseudoconsole-Aktivitäten auf demselben Thread kann zu einem Deadlock führen, bei dem ein der Kommunikationspuffer gefüllt ist und auf Ihre Aktion wartet, während Sie versuchen, eine blockierende Anforderung an einen anderen Kanal zu senden.
Erstellen der Pseudoconsole
Identifizieren Sie mit den kommunikationskanälen, die eingerichtet wurden, das Ende des Eingabekanals "lesen" und das "Schreiben"-Ende des Ausgabekanals. Dieses Handlespaar wird beim Aufrufen von CreatePseudoConsole zum Erstellen des Objekts bereitgestellt.
Beim Erstellen ist eine Größe erforderlich, die die X- und Y-Dimensionen (in der Anzahl der Zeichen) darstellt. Dies sind die Abmessungen, die auf die Anzeigeoberfläche für das endgültige Präsentationsfenster (Terminal) angewendet werden. Die Werte werden verwendet, um einen In-Memory-Puffer innerhalb des Pseudoconsole-Systems zu erstellen.
Die Puffergröße stellt Antworten auf Client-Zeichenmodusanwendungen bereit, die informationen mithilfe der clientseitigen Konsolenfunktionen wie GetConsoleScreenBufferInfoEx untersuchen und das Layout und die Positionierung von Text diktieren, wenn Clients Funktionen wie WriteConsoleOutput verwenden.
Schließlich wird ein Flags-Feld zum Erstellen einer Pseudoconsole bereitgestellt, um spezielle Funktionen auszuführen. Legen Sie dies standardmäßig auf 0 fest, damit keine speziellen Funktionen vorhanden sind.
Derzeit ist nur ein spezielles Flag verfügbar, um die Vererbung der Cursorposition von einer Konsolensitzung anzufordern, die bereits an den Aufrufer der Pseudoconsole-API angefügt ist. Dies ist für die Verwendung in komplexeren Szenarien vorgesehen, in denen eine Hostanwendung, die eine Pseudoconsole-Sitzung vorbereitet, selbst eine Client-Zeichenmodusanwendung einer anderen Konsolenumgebung ist.
Unten finden Sie einen Beispielausschnitt, der CreatePipe verwendet, um ein Paar Kommunikationskanäle einzurichten und die Pseudoconsole zu erstellen.
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;
}
// ...
}
Hinweis
Dieser Codeausschnitt ist unvollständig und wird nur für die Demonstration dieses bestimmten Aufrufs verwendet. Sie müssen die Lebensdauer der HANDLE-Datenentsprechend verwalten. Fehler beim Ordnungsgemäßen Verwalten der Lebensdauer von HANDLEs können zu Deadlock-Szenarien führen, insbesondere bei synchronen E/A-Aufrufen.
Nach Abschluss des CreateProcess-Aufrufs zum Erstellen der Clientzeichenmodusanwendung, die an die Pseudoconsole angefügt ist, sollten die während der Erstellung angegebenen Handles aus diesem Prozess freigegeben werden. Dadurch wird die Referenzanzahl des zugrunde liegenden Geräteobjekts verringert und es I/O-Vorgängen ermöglichen, einen fehlerhaften Kanal ordnungsgemäß zu erkennen, wenn die Pseudoconsole-Sitzung die Kopie der Handles schließt.
Vorbereiten der Erstellung des untergeordneten Prozesses
Die nächste Phase besteht darin, die STARTUPINFOEX-Struktur vorzubereiten, die die Pseudoconsole-Informationen beim Starten des untergeordneten Prozesses vermittelt.
Diese Struktur enthält die Möglichkeit, komplexe Startinformationen bereitzustellen, einschließlich Attributen für die Prozess- und Threaderstellung.
Verwenden Sie InitializeProcThreadAttributeList in doppelter Aufrufart, um zuerst die Anzahl der Bytes zu berechnen, die zum Speichern der Liste erforderlich sind, weisen Sie den angeforderten Speicher zu, und rufen Sie dann erneut den undurchsichtigen Speicherzeiger auf, damit er als Attributliste eingerichtet wird.
Rufen Sie als Nächstes UpdateProcThreadAttribute auf, indem Sie die initialisierte Attributliste mit dem Flag PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE, dem Pseudoconsole-Handle und der Größe des Pseudoconsole-Handles übergeben.
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;
}
Erstellen des gehosteten Prozesses
Rufen Sie als Nächstes CreateProcess auf, indem Sie die STARTUPINFOEX-Struktur zusammen mit dem Pfad zur ausführbaren Datei und ggf. weiteren Konfigurationsinformationen übergeben. Es ist wichtig, das EXTENDED_STARTUPINFO_PRESENT Flag festzulegen, wenn das System aufgerufen wird, dass der Pseudoconsole-Verweis in den erweiterten Informationen enthalten ist.
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());
}
// ...
}
Hinweis
Das Schließen der Pseudoconsole-Sitzung, während der gehostete Prozess noch gestartet wird und eine Verbindung hergestellt wird, kann zu einem Fehlerdialogfeld führen, das von der Clientanwendung angezeigt wird. Dasselbe Fehlerdialogfeld wird angezeigt, wenn dem gehosteten Prozess ein ungültiger Pseudoconsole-Handle für den Start gewährt wird. Für den Initialisierungscode des gehosteten Prozesses sind die beiden Umstände identisch. Das Popupdialogfeld der gehosteten Clientanwendung beim Fehler liest mit einem lokalisierten Meldungsdetail, bei dem fehler beim Initialisieren angezeigt wird 0xc0000142 .
Kommunikation mit der Pseudoconsole-Sitzung
Nachdem der Prozess erfolgreich erstellt wurde, kann die Hostinganwendung das Schreibende der Eingabepipeline verwenden, um Benutzerinteraktionsinformationen an die Pseudoconsole und das Leseende der Ausgabepipeline zu senden, um grafische Präsentationsinformationen aus der Pseudokonsole zu empfangen.
Es liegt ganz bei der Hostinganwendung, zu entscheiden, wie weitere Aktivitäten behandelt werden sollen. Die Hostinganwendung könnte ein Fenster in einem anderen Thread starten, um Benutzerinteraktionseingaben zu sammeln und in das Schreibende der Eingabepipeline für die Pseudoconsole und die gehostete Zeichenmodusanwendung zu serialisieren. Ein weiterer Thread könnte gestartet werden, um das Leseende der Ausgabepipeline für die Pseudoconsole zu entwässern, die Text- und virtuellen Terminalsequenzinformationen zu decodieren und diese auf dem Bildschirm darzustellen.
Threads können auch verwendet werden, um die Informationen aus den Pseudoconsole-Kanälen an einen anderen Kanal oder Gerät weiterzuleiten, einschließlich eines Netzwerks an Remoteinformationen an einen anderen Prozess oder Computer und vermeiden die lokale Transcodierung der Informationen.
Ändern der Größe der Pseudoconsole
Während der Laufzeit kann es vorkommen, dass die Größe des Puffers aufgrund einer Benutzerinteraktion oder einer Anforderung, die von einem anderen Anzeige-/Interaktionsgerät empfangen wurde, geändert werden muss.
Dies kann mit der ResizePseudoConsole-Funktion erfolgen, die sowohl die Höhe als auch die Breite des Puffers in einer Anzahl von Zeichen angibt.
// 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);
}
Beenden der Pseudoconsole-Sitzung
Rufen Sie zum Beenden der Sitzung die ClosePseudoConsole-Funktion mit dem Handle aus der ursprünglichen Pseudoconsole-Erstellung auf. Alle angefügten Client-Zeichenmodusanwendungen, z. B. die anwendung aus dem CreateProcess-Aufruf , werden beendet, wenn die Sitzung geschlossen wird. Wenn es sich beim ursprünglichen untergeordneten Element um eine Shellanwendung handelt, die andere Prozesse erstellt, werden auch alle verwandten angefügten Prozesse in der Struktur beendet.
Warnung
Das Schließen der Sitzung hat mehrere Nebenwirkungen, die zu einem Deadlock-Zustand führen können, wenn die Pseudoconsole in einer synchronen Singlethread-Ausführung verwendet wird. Das Schließen der Pseudoconsole-Sitzung kann ein endgültiges Frameupdate ausgeben, das hOutput aus dem Kommunikationskanalpuffer entwässert werden soll.
PSEUDOCONSOLE_INHERIT_CURSOR Wenn beim Erstellen der Pseudoconsole ausgewählt wurde, kann der Versuch, die Pseudoconsole zu schließen, ohne auf die Cursorererbungsabfragenachricht zu reagieren (empfangen hOutput und auf diese geantwortethInput) zu einer anderen Deadlock-Bedingung führen. Es wird empfohlen, Kommunikationskanäle für die Pseudoconsole auf einzelnen Threads zu warten und bleiben entwässert und verarbeitet, bis sie von der Clientanwendung beendet oder durch den Abschluss von Teardown-Aktivitäten beim Aufrufen der ClosePseudoConsole-Funktion unterbrochen und verarbeitet werden.