Jegyzet
Az oldalhoz való hozzáférés engedélyezést igényel. Próbálhatod be jelentkezni vagy könyvtárat váltani.
Az oldalhoz való hozzáférés engedélyezést igényel. Megpróbálhatod a könyvtár váltását.
A Windows Pseudoconsole( más néven pszeudokonzol, ConPTY vagy Windows PTY) egy olyan mechanizmus, amellyel külső gazdagépet hozhat létre karaktermódú alrendszertevékenységekhez, amelyek felülírják az alapértelmezett konzol gazdagépablakának felhasználói interaktivitási részét.
A pszeudokonzol-munkamenetek üzemeltetése kissé eltér a hagyományos konzolos munkamenetek megszokottól. A hagyományos konzolbeszélgetések automatikusan elindulnak, amikor az operációs rendszer felismeri, hogy egy karaktermódú alkalmazás fog futni. Ezzel szemben az üzemeltetési alkalmazásnak létre kell hoznia egy pszeudokole munkamenetet és a kommunikációs csatornákat, mielőtt létrehozna egy folyamatot a üzemeltetni kívánt gyermek karaktermódú alkalmazással. A gyermekfolyamat továbbra is a CreateProcess függvénnyel jön létre, de néhány további információval, amelyek az operációs rendszert a megfelelő környezet létrehozására irányítják.
A rendszerről további háttérinformációkat a kezdeti közlemény blogbejegyzésében talál.
A Pseudoconsole használatára vonatkozó teljes példák a Microsoft/Terminal GitHub-adattárban érhetők el a mintakönyvtárban.
A kommunikációs csatornák előkészítése
Az első lépés egy szinkron kommunikációs csatornapár létrehozása, amely a pszeudokonzol munkamenet létrehozásakor lesz elérhető a üzemeltetett alkalmazással való kétirányú kommunikációhoz. Ezeket a csatornákat a pseudoconsole rendszer dolgozza fel a ReadFile és a WriteFile szinkron I/O-val. A fájl- vagy I/O-eszközkezelők, például a fájlstreamek vagy a cső elfogadhatók, ha az aszinkron kommunikációhoz nincs szükség ÁTFEDÉSBEN lévő szerkezetre.
Figyelmeztetés
A versenyfeltételek és holtpontok elkerülése érdekében javasoljuk, hogy minden kommunikációs csatorna külön szálon legyen kiszolgálva, amely fenntartja saját ügyfélpufferállapotát és üzenetsorát az alkalmazásban. Az ugyanazon a szálon végzett összes pszeudokole-tevékenység karbantartása holtponthoz vezethet, ahol az egyik kommunikációs puffer megtelik, és várja a műveletet, miközben egy blokkoló kérést kísérel meg elküldeni egy másik csatornán.
A Pseudoconsole létrehozása
A létrehozott kommunikációs csatornákkal azonosítsa a bemeneti csatorna "olvasási" végét és a kimeneti csatorna "írási" végét. Ez a fogópontpár a CreatePseudoConsole meghívásával jön létre az objektum létrehozásához.
Létrehozáskor az X és az Y dimenziót (karakterszámban) képviselő méretre van szükség. Ezek azok a dimenziók, amelyek a végső (terminálos) bemutatóablak megjelenítési felületére vonatkoznak. Az értékek egy memóriabeli puffer létrehozására szolgálnak a pszeudokonzolrendszeren belül.
A pufferméret választ ad az ügyféloldali konzolfüggvények ( például GetConsoleScreenBufferInfoEx ) használatával információt kérő ügyfélkaraktér-módú alkalmazásokra, és diktálja a szöveg elrendezését és elhelyezését, amikor az ügyfelek olyan függvényeket használnak, mint a WriteConsoleOutput.
Végül egy jelzőmezőt ad meg egy pszeudokole létrehozásakor a speciális funkciók elvégzéséhez. Alapértelmezés szerint állítsa a 0 értéket úgy, hogy nincs speciális funkciója.
Jelenleg csak egy speciális jelölő érhető el, amely a kurzor pozíciójának öröklését kéri a pseudoconsole API hívójának már csatolt konzolmunkamenetből. Ez speciálisabb helyzetekben használható, ahol egy pszeudokonzol-munkamenetet előkészítő üzemeltetési alkalmazás maga is egy másik konzolkörnyezet ügyfélkaraktere.
Az alábbi mintarészlet a CreatePipe használatával hoz létre kommunikációs csatornákat, és hozza létre a pszeudokonzolt.
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;
}
// ...
}
Megjegyzés:
Ez a kódrészlet hiányos, és csak az adott hívás bemutatására szolgál. A HANDLE-kélettartamát megfelelően kell kezelnie. A HANDLEs élettartamának helytelen kezelése holtponti forgatókönyveket eredményezhet, különösen szinkron I/O-hívások esetén.
A pszeudokonzolhoz csatolt ügyfél karakter módú alkalmazás létrehozására irányuló CreateProcess-hívás befejezése után a létrehozás során megadott leírókat fel kell szabadítani ebből a folyamatból. Ez csökkenti a mögöttes eszközobjektum hivatkozási számát, és lehetővé teszi, hogy az I/O-műveletek megfelelően észleljék a hibás csatornát, amikor a pseudoconsole munkamenet bezárja a leírók másolatát.
Felkészülés a gyermekfolyamat létrehozására
A következő fázis a STARTUPINFOEX struktúra előkészítése, amely a gyermekfolyamat indításakor közvetíti a pszeudokonzoladatokat.
Ez a struktúra összetett indítási információkat tartalmaz, beleértve a folyamat- és szállétrehozáshoz szükséges attribútumokat is.
Az InitializeProcThreadAttributeList kettős hívással először kiszámíthatja a lista tárolásához szükséges bájtok számát, lefoglalhatja a kért memóriát, majd ismét meghívhatja az átlátszatlan memóriamutatót, hogy az attribútumlistaként legyen beállítva.
Ezután hívja meg az UpdateProcThreadAttribute parancsot , amely átadja az inicializált attribútumlistát a jelölő PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE, a pseudoconsole leíróval és a pseudoconsole leíró méretével.
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;
}
A üzemeltetett folyamat létrehozása
Ezután hívja meg a CreateProcess-et , amely átadja a STARTUPINFOEX struktúrát, valamint a végrehajtható elérési utat és adott esetben a további konfigurációs információkat. Fontos beállítani a EXTENDED_STARTUPINFO_PRESENT jelzőt, amikor a rendszer figyelmezteti, hogy a pseudoconsole-referenciát a kiterjesztett információk tartalmazzák.
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());
}
// ...
}
Megjegyzés:
Ha bezárja a pszeudokonzol munkamenetet, miközben az üzemeltetett folyamat még elindul, és a csatlakozás azt eredményezheti, hogy az ügyfélalkalmazás hibaüzenetet jelenít meg. Ugyanez a hiba párbeszédpanel jelenik meg, ha az üzemeltetett folyamat érvénytelen pszeudokole leírót kap az indításhoz. Az üzemeltetett folyamat inicializálási kódjának két körülménye megegyezik. A sikertelen üzemeltetésű ügyfélalkalmazás előugró párbeszédpanelje egy honosított üzenettel jelenik meg 0xc0000142 , amely részletezi az inicializálás sikertelenségét.
Kommunikáció a Pseudoconsole munkamenettel
A folyamat sikeres létrehozása után az üzemeltetési alkalmazás a bemeneti cső írási végénél felhasználói interakciós információkat küldhet a pszeudokonzolba és a kimeneti cső olvasási végére, hogy grafikus bemutatóadatokat fogadjon a pszeudokonzolról.
Teljesen az üzemeltetési alkalmazáson múlik, hogy hogyan kell kezelni a további tevékenységeket. Az üzemeltetési alkalmazás elindíthat egy ablakot egy másik szálon, amely összegyűjti a felhasználói interakció bemenetét, és szerializálja azt a pszeudokonzol és az üzemeltetett karaktermódú alkalmazás bemeneti csőjének írási végébe. Egy másik szál is elindítható a kimeneti cső olvasási végének leeresztéséhez a pszeudokonzolhoz, dekódolni a szöveget és a virtuális terminál szekvenciaadatait , és bemutatni azt a képernyőn.
A szálak arra is használhatók, hogy a pszeudokonzolcsatornákból származó információkat továbbítsuk egy másik csatornára vagy eszközre, beleértve a hálózatot egy másik folyamatba vagy gépre, és elkerüljük az információk helyi átkódolását.
A Pseudoconsole átméretezése
A futtatókörnyezet során előfordulhat, hogy a puffer méretét módosítani kell egy felhasználói beavatkozás vagy egy másik kijelző/interakciós eszközről sávon kívül kapott kérés miatt.
Ez a ResizePseudoConsole függvénnyel végezhető el, amely a puffer magasságát és szélességét karakterszámban határozza meg.
// 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);
}
A Pseudoconsole munkamenet befejezése
A munkamenet befejezéséhez hívja meg a ClosePseudoConsole függvényt az eredeti pseudoconsole-létrehozás leírójával. Minden csatolt ügyfélkarakte-módú alkalmazás, például a CreateProcess-hívásból származó alkalmazás a munkamenet bezárásakor leáll. Ha az eredeti gyermek egy olyan rendszerhéj típusú alkalmazás volt, amely más folyamatokat hoz létre, a fa kapcsolódó csatolt folyamatai is leállnak.
Figyelmeztetés
A munkamenet bezárásának számos mellékhatása van, amelyek holtpontot eredményezhetnek, ha a pszeudokonzolt egyszálas szinkron módon használják. A pszeudokonzol munkamenet bezárásával végleges keretfrissítést hOutput bocsáthat ki, amelyhez a kommunikációs csatorna pufferéből kell üríteni. Emellett, ha PSEUDOCONSOLE_INHERIT_CURSOR a pszeudokonzol létrehozásakor lett kiválasztva, a pszeudokonzol bezárásának megkísérlése a kurzor öröklődési lekérdezési üzenetének megválaszolása nélkül (be lett fogadva hOutput és megválaszolva hInput) egy másik patthelyzetet eredményezhet. Javasoljuk, hogy a pszeudokonzol kommunikációs csatornái egyes szálakon legyenek kiszolgálva, és mindaddig ürítsék és dolgozzák fel őket, amíg az ügyfélalkalmazás kilép, vagy a ClosePseudoConsole függvény meghívásával kapcsolatos szakadási tevékenységek befejeződnek.