Membuat sesi Pseudoconsole
Windows Pseudoconsole, terkadang juga disebut sebagai konsol pseudo, ConPTY, atau Windows PTY, adalah mekanisme yang dirancang untuk membuat host eksternal untuk aktivitas subsistem mode karakter yang menggantikan bagian interaktivitas pengguna dari jendela host konsol default.
Menghosting sesi pseudoconsole sedikit berbeda dari sesi konsol tradisional. Sesi konsol tradisional secara otomatis dimulai ketika sistem operasi mengenali bahwa aplikasi mode karakter akan dijalankan. Sebaliknya, sesi pseudoconsole dan saluran komunikasi perlu dibuat oleh aplikasi hosting sebelum membuat proses dengan aplikasi mode karakter anak yang akan dihosting. Proses anak masih akan dibuat menggunakan fungsi CreateProcess , tetapi dengan beberapa informasi tambahan yang akan mengarahkan sistem operasi untuk membangun lingkungan yang sesuai.
Anda dapat menemukan informasi latar belakang tambahan tentang sistem ini di posting blog pengumuman awal.
Contoh lengkap penggunaan Pseudoconsole tersedia di repositori GitHub microsoft/terminal kami di direktori sampel.
Menyiapkan saluran komunikasi
Langkah pertama adalah membuat sepasang saluran komunikasi sinkron yang akan disediakan selama pembuatan sesi pseudoconsole untuk komunikasi dua arah dengan aplikasi yang dihosting. Saluran ini diproses oleh sistem pseudoconsole menggunakan ReadFile dan WriteFile dengan I/O sinkron. File atau perangkat I/O menangani seperti aliran file atau pipa dapat diterima selama struktur yang TUMPANG TINDIH tidak diperlukan untuk komunikasi asinkron.
Peringatan
Untuk mencegah kondisi balapan dan kebuntuan, kami sangat menyarankan agar setiap saluran komunikasi dilayankan pada utas terpisah yang mempertahankan status buffer kliennya sendiri dan antrean olahpesan di dalam aplikasi Anda. Melayani semua aktivitas pseudoconsole pada utas yang sama dapat mengakibatkan kebuntuan di mana salah satu buffer komunikasi terisi dan menunggu tindakan Anda saat Anda mencoba mengirimkan permintaan pemblokiran di saluran lain.
Membuat Pseudoconsole
Dengan saluran komunikasi yang telah dibuat, identifikasi akhir "baca" saluran input dan akhir "tulis" saluran output. Pasangan handel ini disediakan saat memanggil CreatePseudoConsole untuk membuat objek.
Pada pembuatan, ukuran yang mewakili dimensi X dan Y (dalam hitungan karakter) diperlukan. Ini adalah dimensi yang akan berlaku untuk permukaan tampilan untuk jendela presentasi akhir (terminal). Nilai digunakan untuk membuat buffer dalam memori di dalam sistem pseudoconsole.
Ukuran buffer memberikan jawaban untuk aplikasi mode karakter klien yang menyelidiki informasi menggunakan fungsi konsol sisi klien seperti GetConsoleScreenBufferInfoEx dan menentukan tata letak dan posisi teks saat klien menggunakan fungsi seperti WriteConsoleOutput.
Terakhir, bidang bendera disediakan pada pembuatan pseudoconsole untuk melakukan fungsionalitas khusus. Secara default, atur ini ke 0 agar tidak memiliki fungsionalitas khusus.
Saat ini, hanya satu bendera khusus yang tersedia untuk meminta warisan posisi kursor dari sesi konsol yang sudah dilampirkan ke pemanggil API pseudoconsole. Ini ditujukan untuk digunakan dalam skenario yang lebih canggih di mana aplikasi hosting yang menyiapkan sesi pseudoconsole itu sendiri juga merupakan aplikasi mode karakter klien dari lingkungan konsol lain.
Cuplikan sampel disediakan di bawah ini menggunakan CreatePipe untuk membuat sepasang saluran komunikasi dan membuat pseudoconsole.
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;
}
// ...
}
Catatan
Cuplikan ini tidak lengkap dan digunakan untuk demonstrasi panggilan khusus ini saja. Anda harus mengelola masa pakai HANDLEdengan tepat. Kegagalan untuk mengelola masa pakai HANDLEdengan benar dapat mengakibatkan skenario kebuntuan, terutama dengan panggilan I/O sinkron.
Setelah menyelesaikan panggilan CreateProcess untuk membuat aplikasi mode karakter klien yang melekat pada pseudoconsole, handel yang diberikan selama pembuatan harus dibebaskan dari proses ini. Ini akan mengurangi jumlah referensi pada objek perangkat yang mendasar dan memungkinkan operasi I/O mendeteksi saluran yang rusak dengan benar ketika sesi pseudoconsole menutup salinan handelnya.
Mempersiapkan Pembuatan Proses Anak
Fase selanjutnya adalah menyiapkan struktur STARTUPINFOEX yang akan menyampaikan informasi pseudoconsole saat memulai proses anak.
Struktur ini berisi kemampuan untuk memberikan informasi startup yang kompleks termasuk atribut untuk pembuatan proses dan utas.
Gunakan InitializeProcThreadAttributeList dengan cara panggilan ganda untuk terlebih dahulu menghitung jumlah byte yang diperlukan untuk menyimpan daftar, mengalokasikan memori yang diminta, lalu panggil lagi yang menyediakan pointer memori buram untuk menyiapkannya sebagai daftar atribut.
Selanjutnya, panggil UpdateProcThreadAttribute yang meneruskan daftar atribut yang diinisialisasi dengan bendera PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE, handel pseudoconsole, dan ukuran handel pseudoconsole.
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;
}
Membuat Proses yang Dihosting
Selanjutnya, panggil CreateProcess yang melewati struktur STARTUPINFOEX bersama dengan jalur ke yang dapat dieksekusi dan informasi konfigurasi tambahan jika berlaku. Penting untuk mengatur bendera EXTENDED_STARTUPINFO_PRESENT saat memanggil untuk memperingatkan sistem bahwa referensi pseudoconsole terkandung dalam informasi yang diperluas.
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());
}
// ...
}
Catatan
Menutup sesi pseudoconsole saat proses yang dihosting masih dimulai dan menyambungkan dapat mengakibatkan dialog kesalahan ditampilkan oleh aplikasi klien. Dialog kesalahan yang sama ditampilkan jika proses yang dihosting diberikan handel pseudoconsole yang tidak valid untuk startup. Untuk kode inisialisasi proses yang dihosting, dua keadaan tersebut identik. Dialog pop-up dari aplikasi klien yang dihosting pada kegagalan akan dibaca 0xc0000142
dengan pesan yang dilokalkan yang merinci kegagalan untuk menginisialisasi.
Berkomunikasi dengan Sesi Pseudoconsole
Setelah proses berhasil dibuat, aplikasi hosting dapat menggunakan akhir tulis pipa input untuk mengirim informasi interaksi pengguna ke pseudoconsole dan akhir baca pipa output untuk menerima informasi presentasi grafis dari konsol pseudo.
Sepenuhnya terserah aplikasi hosting untuk memutuskan cara menangani aktivitas lebih lanjut. Aplikasi hosting dapat meluncurkan jendela di utas lain untuk mengumpulkan input interaksi pengguna dan menserialisasikannya ke ujung tulis pipa input untuk pseudoconsole dan aplikasi mode karakter yang dihosting. Utas lain dapat diluncurkan untuk menguras ujung baca pipa output untuk pseudoconsole, mendekode informasi urutan teks dan terminal virtual, dan menyajikannya ke layar.
Utas juga dapat digunakan untuk menyampaikan informasi dari saluran pseudoconsole ke saluran atau perangkat yang berbeda termasuk jaringan ke informasi jarak jauh ke proses atau mesin lain dan menghindari transkode lokal informasi.
Mengubah ukuran Pseudoconsole
Sepanjang runtime, mungkin ada keadaan di mana ukuran buffer perlu diubah karena interaksi pengguna atau permintaan yang diterima dari pita dari perangkat tampilan/interaksi lain.
Ini dapat dilakukan dengan fungsi ResizePseudoConsole yang menentukan tinggi dan lebar buffer dalam hitungan karakter.
// 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);
}
Mengakhiri Sesi Pseudoconsole
Untuk mengakhiri sesi, panggil fungsi ClosePseudoConsole dengan handel dari pembuatan pseudoconsole asli. Setiap aplikasi mode karakter klien yang terlampir, seperti aplikasi dari panggilan CreateProcess , akan dihentikan saat sesi ditutup. Jika anak asli adalah aplikasi jenis shell yang membuat proses lain, proses terlampir terkait di pohon juga akan dihentikan.
Peringatan
Menutup sesi memiliki beberapa efek samping yang dapat mengakibatkan kondisi kebuntuan jika pseudoconsole digunakan dengan cara sinkron berulir tunggal. Tindakan menutup sesi pseudoconsole dapat memancarkan pembaruan hOutput
bingkai akhir yang harus dikosongkan dari buffer saluran komunikasi. Selain itu, jika PSEUDOCONSOLE_INHERIT_CURSOR
dipilih saat membuat pseudoconsole, mencoba menutup pseudoconsole tanpa menanggapi pesan kueri pewarisan kursor (diterima dan dibalas hOutput
melalui hInput
) dapat mengakibatkan kondisi kebuntuan lain. Disarankan agar saluran komunikasi untuk pseudoconsole dilayankan pada utas individu dan tetap dikuras dan diproses sampai rusak dari kemauan mereka sendiri oleh aplikasi klien yang keluar atau dengan penyelesaian aktivitas teardown dalam memanggil fungsi ClosePseudoConsole.