ProcessFactory.cs 3.9 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798
  1. using System;
  2. using System.Runtime.InteropServices;
  3. using static MiniTerm.Native.ProcessApi;
  4. namespace MiniTerm
  5. {
  6. /// <summary>
  7. /// Support for starting and configuring processes.
  8. /// </summary>
  9. /// <remarks>
  10. /// Possible to replace with managed code? The key is being able to provide the PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE attribute
  11. /// </remarks>
  12. static class ProcessFactory
  13. {
  14. /// <summary>
  15. /// Start and configure a process. The return value represents the process and should be disposed.
  16. /// </summary>
  17. internal static Process Start(string command, IntPtr attributes, IntPtr hPC)
  18. {
  19. var startupInfo = ConfigureProcessThread(hPC, attributes);
  20. var processInfo = RunProcess(ref startupInfo, command);
  21. return new Process(startupInfo, processInfo);
  22. }
  23. private static STARTUPINFOEX ConfigureProcessThread(IntPtr hPC, IntPtr attributes)
  24. {
  25. // this method implements the behavior described in https://docs.microsoft.com/en-us/windows/console/creating-a-pseudoconsole-session#preparing-for-creation-of-the-child-process
  26. var lpSize = IntPtr.Zero;
  27. var success = InitializeProcThreadAttributeList(
  28. lpAttributeList: IntPtr.Zero,
  29. dwAttributeCount: 1,
  30. dwFlags: 0,
  31. lpSize: ref lpSize
  32. );
  33. if (success || lpSize == IntPtr.Zero) // we're not expecting `success` here, we just want to get the calculated lpSize
  34. {
  35. throw new InvalidOperationException("Could not calculate the number of bytes for the attribute list. " + Marshal.GetLastWin32Error());
  36. }
  37. var startupInfo = new STARTUPINFOEX();
  38. startupInfo.StartupInfo.cb = Marshal.SizeOf<STARTUPINFOEX>();
  39. startupInfo.lpAttributeList = Marshal.AllocHGlobal(lpSize);
  40. success = InitializeProcThreadAttributeList(
  41. lpAttributeList: startupInfo.lpAttributeList,
  42. dwAttributeCount: 1,
  43. dwFlags: 0,
  44. lpSize: ref lpSize
  45. );
  46. if (!success)
  47. {
  48. throw new InvalidOperationException("Could not set up attribute list. " + Marshal.GetLastWin32Error());
  49. }
  50. success = UpdateProcThreadAttribute(
  51. lpAttributeList: startupInfo.lpAttributeList,
  52. dwFlags: 0,
  53. attribute: attributes,
  54. lpValue: hPC,
  55. cbSize: (IntPtr)IntPtr.Size,
  56. lpPreviousValue: IntPtr.Zero,
  57. lpReturnSize: IntPtr.Zero
  58. );
  59. if (!success)
  60. {
  61. throw new InvalidOperationException("Could not set pseudoconsole thread attribute. " + Marshal.GetLastWin32Error());
  62. }
  63. return startupInfo;
  64. }
  65. private static PROCESS_INFORMATION RunProcess(ref STARTUPINFOEX sInfoEx, string commandLine)
  66. {
  67. int securityAttributeSize = Marshal.SizeOf<SECURITY_ATTRIBUTES>();
  68. var pSec = new SECURITY_ATTRIBUTES { nLength = securityAttributeSize };
  69. var tSec = new SECURITY_ATTRIBUTES { nLength = securityAttributeSize };
  70. var success = CreateProcess(
  71. lpApplicationName: null,
  72. lpCommandLine: commandLine,
  73. lpProcessAttributes: ref pSec,
  74. lpThreadAttributes: ref tSec,
  75. bInheritHandles: false,
  76. dwCreationFlags: EXTENDED_STARTUPINFO_PRESENT,
  77. lpEnvironment: IntPtr.Zero,
  78. lpCurrentDirectory: null,
  79. lpStartupInfo: ref sInfoEx,
  80. lpProcessInformation: out PROCESS_INFORMATION pInfo
  81. );
  82. if (!success)
  83. {
  84. throw new InvalidOperationException("Could not create process. " + Marshal.GetLastWin32Error());
  85. }
  86. return pInfo;
  87. }
  88. }
  89. }