| 1 | #Region "Microsoft.VisualBasic::78417a1280c39aa92aa5d39f87b1219e, Microsoft.VisualBasic.Core\CommandLine\CLI\IORedirect.vb" |
| 2 | |
| 3 | ' Author: |
| 4 | ' |
| 5 | ' asuka (amethyst.asuka@gcmodeller.org) |
| 6 | ' xie (genetics@smrucc.org) |
| 7 | ' xieguigang (xie.guigang@live.com) |
| 8 | ' |
| 9 | ' Copyright (c) 2018 GPL3 Licensed |
| 10 | ' |
| 11 | ' |
| 12 | ' GNU GENERAL PUBLIC LICENSE (GPL3) |
| 13 | ' |
| 14 | ' |
| 15 | ' This program is free software: you can redistribute it and/or modify |
| 16 | ' it under the terms of the GNU General Public License as published by |
| 17 | ' the Free Software Foundation, either version 3 of the License, or |
| 18 | ' (at your option) any later version. |
| 19 | ' |
| 20 | ' This program is distributed in the hope that it will be useful, |
| 21 | ' but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 22 | ' MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 23 | ' GNU General Public License for more details. |
| 24 | ' |
| 25 | ' You should have received a copy of the GNU General Public License |
| 26 | ' along with this program. If not, see <http://www.gnu.org/licenses/>. |
| 27 | |
| 28 | |
| 29 | |
| 30 | ' /********************************************************************************/ |
| 31 | |
| 32 | ' Summaries: |
| 33 | |
| 34 | ' Class IORedirect |
| 35 | ' |
| 36 | ' Properties: Bin, CLIArguments, ExitCode, HasExited, PID |
| 37 | ' StandardOutput |
| 38 | ' |
| 39 | ' Constructor: (+1 Overloads) Sub New |
| 40 | ' Delegate Function |
| 41 | ' |
| 42 | ' |
| 43 | ' Delegate Sub |
| 44 | ' |
| 45 | ' Function: GetError, Read, ReadLine, Run, Shell |
| 46 | ' (+3 Overloads) Start, ToString, WaitError, waitForExit, WaitForExit |
| 47 | ' WaitOutput |
| 48 | ' |
| 49 | ' Sub: (+2 Overloads) Dispose, errorHandler, Kill, outputHandler, Write |
| 50 | ' (+2 Overloads) WriteLine |
| 51 | ' |
| 52 | ' |
| 53 | ' |
| 54 | ' |
| 55 | ' |
| 56 | ' /********************************************************************************/ |
| 57 | |
| 58 | #End Region |
| 59 | |
| 60 | Imports System.IO |
| 61 | Imports System.Runtime.CompilerServices |
| 62 | Imports System.Text |
| 63 | Imports System.Text.RegularExpressions |
| 64 | Imports System.Threading |
| 65 | Imports Microsoft.VisualBasic.Language |
| 66 | Imports Microsoft.VisualBasic.Parallel |
| 67 | Imports Microsoft.VisualBasic.Terminal.STDIO |
| 68 | Imports Microsoft.VisualBasic.Terminal.STDIO__ |
| 69 | |
| 70 | Namespace CommandLine |
| 71 | |
| 72 | ''' <summary> |
| 73 | ''' A communication fundation class type for the commandline program interop. |
| 74 | ''' (一个简单的用于从当前进程派生子进程的Wrapper对象,假若需要folk出来的子进程对象 |
| 75 | ''' 不需要终端交互功能,则更加推荐使用<see cref="IORedirectFile"/>对象来进行调用) |
| 76 | ''' </summary> |
| 77 | ''' <remarks></remarks> |
| 78 | Public Class IORedirect : Implements I_ConsoleDeviceHandle |
| 79 | Implements IDisposable, IIORedirectAbstract |
| 80 | |
| 81 | ''' <summary> |
| 82 | ''' 当前的这个进程实例是否处于运行的状态 |
| 83 | ''' </summary> |
| 84 | ''' <remarks></remarks> |
| 85 | Dim processIsRunning As Boolean = False |
| 86 | Dim outputWaitHandle As New AutoResetEvent(False) |
| 87 | Dim errorWaitHandle As New AutoResetEvent(False) |
| 88 | |
| 89 | ''' <summary> |
| 90 | ''' The target invoked process event has been exit with a specific return code. |
| 91 | ''' (目标派生子进程已经结束了运行并且返回了一个错误值) |
| 92 | ''' </summary> |
| 93 | ''' <param name="exitCode"></param> |
| 94 | ''' <param name="exitTime"></param> |
| 95 | ''' <remarks></remarks> |
| 96 | Public Event ProcessExit(exitCode As Integer, exitTime As String) Implements IIORedirectAbstract.ProcessExit |
| 97 | Public Event PrintOutput(output As String) |
| 98 | |
| 99 | ''' <summary> |
| 100 | ''' The process invoke interface of current I/O redirect operation. |
| 101 | ''' </summary> |
| 102 | ''' <remarks></remarks> |
| 103 | Dim WithEvents processInfo As Process |
| 104 | |
| 105 | ''' <summary> |
| 106 | ''' Gets the standard output for the target invoke process. |
| 107 | ''' </summary> |
| 108 | ''' <value></value> |
| 109 | ''' <returns></returns> |
| 110 | ''' <remarks></remarks> |
| 111 | Public ReadOnly Property StandardOutput As String Implements IIORedirectAbstract.StandardOutput |
| 112 | <MethodImpl(MethodImplOptions.AggressiveInlining)> |
| 113 | Get |
| 114 | Return output.ToString |
| 115 | End Get |
| 116 | End Property |
| 117 | |
| 118 | Public ReadOnly Property Bin As String Implements IIORedirectAbstract.Bin |
| 119 | <MethodImpl(MethodImplOptions.AggressiveInlining)> |
| 120 | Get |
| 121 | Return processInfo.StartInfo.FileName |
| 122 | End Get |
| 123 | End Property |
| 124 | |
| 125 | Public ReadOnly Property CLIArguments As String Implements IIORedirectAbstract.CLIArguments |
| 126 | <MethodImpl(MethodImplOptions.AggressiveInlining)> |
| 127 | Get |
| 128 | Return processInfo.StartInfo.Arguments |
| 129 | End Get |
| 130 | End Property |
| 131 | |
| 132 | Public ReadOnly Property ExitCode As Integer |
| 133 | Get |
| 134 | Return processInfo.ExitCode |
| 135 | End Get |
| 136 | End Property |
| 137 | |
| 138 | Public ReadOnly Property HasExited As Boolean |
| 139 | Get |
| 140 | Return processInfo.HasExited |
| 141 | End Get |
| 142 | End Property |
| 143 | |
| 144 | Public ReadOnly Property PID As Integer |
| 145 | Get |
| 146 | Return processInfo.Id |
| 147 | End Get |
| 148 | End Property |
| 149 | |
| 150 | Dim input As StreamWriter |
| 151 | Dim output As New StringBuilder(1024) |
| 152 | Dim [error] As New StringBuilder() |
| 153 | Dim IOredirect As Boolean |
| 154 | Dim displayOutput As Boolean = False |
| 155 | |
| 156 | ''' <summary> |
| 157 | ''' Creates a <see cref="System.Diagnostics.Process"/> wrapper for the CLI program operations. |
| 158 | ''' (在服务器上面可能会有一些线程方面的兼容性BUG的问题,不太清楚为什么会导致这样) |
| 159 | ''' </summary> |
| 160 | ''' <param name="Exe">The file path of the executable file.</param> |
| 161 | ''' <param name="args"> |
| 162 | ''' The CLI arguments for the folked program. |
| 163 | ''' |
| 164 | ''' (程序会自动将这个参数之中的换行符替换为空格.) |
| 165 | ''' </param> |
| 166 | ''' <param name="ENV">Set up the environment variable for the target invoked child process.</param> |
| 167 | ''' <param name="displayDebug"></param> |
| 168 | ''' <param name="displayStdOut">是否显示目标被调用的外部程序的标准输出</param> |
| 169 | ''' <remarks></remarks> |
| 170 | Public Sub New(exe$, Optional args$ = "", |
| 171 | Optional ENV As IEnumerable(Of KeyValuePair(Of String, String)) = Nothing, |
| 172 | Optional IOredirect As Boolean = True, |
| 173 | Optional displayStdOut As Boolean = True, |
| 174 | Optional displayDebug As Boolean = False) |
| 175 | |
| 176 | Dim program$ = exe.GetString(""""c) |
| 177 | Dim pInfo As New ProcessStartInfo(program, args.TrimNewLine.Trim) With { |
| 178 | .UseShellExecute = False |
| 179 | } |
| 180 | |
| 181 | If IOredirect Then '只是重定向输出设备流 |
| 182 | pInfo.RedirectStandardOutput = True |
| 183 | pInfo.RedirectStandardError = True |
| 184 | pInfo.RedirectStandardInput = True |
| 185 | End If |
| 186 | |
| 187 | pInfo.RedirectStandardInput = True |
| 188 | pInfo.ErrorDialog = False |
| 189 | pInfo.WindowStyle = ProcessWindowStyle.Hidden |
| 190 | pInfo.CreateNoWindow = True |
| 191 | |
| 192 | If Not ENV Is Nothing Then |
| 193 | For Each para As KeyValuePair(Of String, String) In ENV |
| 194 | Call pInfo.EnvironmentVariables.Add(para.Key, para.Value) |
| 195 | Next |
| 196 | End If |
| 197 | |
| 198 | Me.IOredirect = IOredirect |
| 199 | Me.displayOutput = displayStdOut |
| 200 | Me.processInfo = New Process With { |
| 201 | .EnableRaisingEvents = True, |
| 202 | .StartInfo = pInfo |
| 203 | } |
| 204 | |
| 205 | If displayDebug Then |
| 206 | Call $"""{exe}"" {args}".__DEBUG_ECHO |
| 207 | Call $"disp_STDOUT -> ${ "Yes!" Or "NO!".When(Not Me.displayOutput) }".__DEBUG_ECHO |
| 208 | End If |
| 209 | End Sub |
| 210 | |
| 211 | Public Delegate Function ProcessAyHandle(WaitForExit As Boolean, PushingData As String(), _DISP_DEBUG_INFO As Boolean) As Integer |
| 212 | |
| 213 | ''' <summary> |
| 214 | ''' A function pointer for process the events when the target invoked child process was terminated and exit. |
| 215 | ''' (当目标进程退出的时候所调用的过程) |
| 216 | ''' </summary> |
| 217 | ''' <param name="exitCode">The exit code for the target sub invoke process.进程的退出代码</param> |
| 218 | ''' <param name="exitTime">The exit time for the target sub invoke process.(进程的退出时间)</param> |
| 219 | ''' <remarks></remarks> |
| 220 | Public Delegate Sub ProcessExitCallback(exitCode As Integer, exitTime As String) |
| 221 | |
| 222 | Private Sub outputHandler(sender As Object, e As DataReceivedEventArgs) Handles processInfo.OutputDataReceived |
| 223 | If e.Data Is Nothing Then |
| 224 | Call outputWaitHandle.[Set]() |
| 225 | Else |
| 226 | Call output.AppendLine(e.Data) |
| 227 | End If |
| 228 | End Sub |
| 229 | |
| 230 | Private Sub errorHandler(sender As Object, e As DataReceivedEventArgs) Handles processInfo.ErrorDataReceived |
| 231 | If e.Data Is Nothing Then |
| 232 | Call errorWaitHandle.[Set]() |
| 233 | Else |
| 234 | Call [error].AppendLine(e.Data) |
| 235 | End If |
| 236 | End Sub |
| 237 | |
| 238 | Public Sub Kill() |
| 239 | Call processInfo.Kill() |
| 240 | End Sub |
| 241 | |
| 242 | ''' <summary> |
| 243 | ''' Gets a <see cref="String"/> used to read the error output of the application. |
| 244 | ''' </summary> |
| 245 | ''' <returns>A <see cref="String"/> text value that read from the std_error of <see cref="StreamReader"/> |
| 246 | ''' that can be used to read the standard error stream of the application.</returns> |
| 247 | ''' |
| 248 | <MethodImpl(MethodImplOptions.AggressiveInlining)> |
| 249 | Public Function GetError() As String |
| 250 | Return [error].ToString |
| 251 | End Function |
| 252 | |
| 253 | ''' <summary> |
| 254 | ''' Start the target process. If the target invoked process is currently on the running state, |
| 255 | ''' then this function will returns the -100 value as error code and print the warning |
| 256 | ''' information on the system console.(启动目标进程) |
| 257 | ''' </summary> |
| 258 | ''' <param name="WaitForExit"> |
| 259 | ''' Indicate that the program code wait for the target process exit or not? |
| 260 | ''' (参数指示应用程序代码是否等待目标进程的结束) |
| 261 | ''' </param> |
| 262 | ''' <returns> |
| 263 | ''' 当发生错误的时候会返回错误代码,当当前的进程任然处于运行的状态的时候,程序会返回-100错误代码并在终端之上打印出警告信息 |
| 264 | ''' </returns> |
| 265 | ''' <remarks></remarks> |
| 266 | Public Function Start(Optional waitForExit As Boolean = False, |
| 267 | Optional pushingData As String() = Nothing, |
| 268 | Optional displaDebug As Boolean = False) As Integer |
| 269 | |
| 270 | If processIsRunning Then |
| 271 | Dim msg As String = $"Target process ""{processInfo.StartInfo.FileName.ToFileURL}"" is currently in the running state, Operation abort!" |
| 272 | Call VBDebugger.Warning(msg) |
| 273 | Return -100 |
| 274 | End If |
| 275 | |
| 276 | If displaDebug Then |
| 277 | Dim Exe As String = FileIO.FileSystem.GetFileInfo(processInfo.StartInfo.FileName).FullName.Replace("\", "/") |
| 278 | Dim argvs As String = (" " & processInfo.StartInfo.Arguments).Trim |
| 279 | |
| 280 | Call Console.WriteLine(" --> system(""file:""{0}""{1})", Exe, argvs) |
| 281 | End If |
| 282 | |
| 283 | Try |
| 284 | Call processInfo.Start() |
| 285 | |
| 286 | If IOredirect Then |
| 287 | Call processInfo.BeginOutputReadLine() |
| 288 | Call processInfo.BeginErrorReadLine() |
| 289 | End If |
| 290 | Catch ex As Exception |
| 291 | Call printf("FATAL_ERROR::%s", ex.ToString) |
| 292 | Call Console.WriteLine(" Exe ==> " & processInfo.StartInfo.FileName) |
| 293 | Call Console.WriteLine("argvs ==> " & processInfo.StartInfo.Arguments) |
| 294 | |
| 295 | ' Return ex.HResul '4.5 |
| 296 | Return -1 |
| 297 | End Try |
| 298 | |
| 299 | processIsRunning = True |
| 300 | input = processInfo.StandardInput |
| 301 | |
| 302 | If Not pushingData.IsNullOrEmpty Then |
| 303 | For Each line As String In pushingData |
| 304 | Call input.WriteLine(line) |
| 305 | Call input.Flush() |
| 306 | |
| 307 | Call Console.WriteLine(" >>> " & line) |
| 308 | Next |
| 309 | End If |
| 310 | |
| 311 | If waitForExit Then |
| 312 | ' 请注意这个函数名称是和当前的这个函数参数是一样的 |
| 313 | ' 会需要me来区别引用 |
| 314 | Return Me.waitForExit |
| 315 | Else |
| 316 | Call RunTask(AddressOf Me.waitForExit) |
| 317 | Return 0 |
| 318 | End If |
| 319 | End Function |
| 320 | |
| 321 | Private Function waitForExit() As Integer |
| 322 | Dim exitCode% |
| 323 | |
| 324 | Call processInfo.WaitForExit() |
| 325 | |
| 326 | Try |
| 327 | ' process exit and raise events |
| 328 | exitCode = processInfo.ExitCode |
| 329 | RaiseEvent ProcessExit(exitCode, Process.ExitTime) |
| 330 | Catch ex As Exception |
| 331 | |
| 332 | End Try |
| 333 | |
| 334 | Return exitCode |
| 335 | End Function |
| 336 | |
| 337 | <MethodImpl(MethodImplOptions.AggressiveInlining)> |
| 338 | Public Function WaitOutput(timeout As Integer) As Boolean |
| 339 | Return outputWaitHandle.WaitOne(timeout) |
| 340 | End Function |
| 341 | |
| 342 | <MethodImpl(MethodImplOptions.AggressiveInlining)> |
| 343 | Public Function WaitError(timeout As Integer) As Boolean |
| 344 | Return errorWaitHandle.WaitOne(timeout) |
| 345 | End Function |
| 346 | |
| 347 | ''' <summary> |
| 348 | ''' With a given timeout in milliseconds unit |
| 349 | ''' </summary> |
| 350 | ''' <param name="timeout"></param> |
| 351 | ''' <returns></returns> |
| 352 | ''' |
| 353 | <MethodImpl(MethodImplOptions.AggressiveInlining)> |
| 354 | Public Function WaitForExit(timeout As Integer) As Boolean |
| 355 | Return processInfo.WaitForExit(timeout) |
| 356 | End Function |
| 357 | |
| 358 | ''' <summary> |
| 359 | ''' Start the target process.(启动目标进程) |
| 360 | ''' </summary> |
| 361 | ''' <returns>当发生错误的时候会返回错误代码</returns> |
| 362 | ''' <remarks></remarks> |
| 363 | Public Function Start(processExitCallback As ProcessExitCallback, |
| 364 | Optional pushData As String() = Nothing, |
| 365 | Optional displayDebug As Boolean = False) As Integer |
| 366 | |
| 367 | AddHandler ProcessExit, Sub(exitCode As Integer, exitTime As String) |
| 368 | Call processExitCallback(exitCode, exitTime) |
| 369 | End Sub |
| 370 | Return Start( |
| 371 | waitForExit:=False, |
| 372 | pushingData:=pushData, |
| 373 | displaDebug:=displayDebug |
| 374 | ) |
| 375 | End Function |
| 376 | |
| 377 | ''' <summary> |
| 378 | ''' Gets the value that the associated process specified when it terminated. |
| 379 | ''' </summary> |
| 380 | ''' <param name="WaitForExit"></param> |
| 381 | ''' <returns>The code that the associated process specified when it terminated.</returns> |
| 382 | ''' |
| 383 | <MethodImpl(MethodImplOptions.AggressiveInlining)> |
| 384 | Public Function Start(Optional waitForExit As Boolean = False) As Integer Implements IIORedirectAbstract.Start |
| 385 | Return Start(waitForExit, Nothing, True) |
| 386 | End Function |
| 387 | |
| 388 | ''' <summary> |
| 389 | ''' 线程会被阻塞在这里,直到外部应用程序执行完毕 |
| 390 | ''' </summary> |
| 391 | ''' <returns></returns> |
| 392 | Public Function Run() As Integer Implements IIORedirectAbstract.Run |
| 393 | Return Start(waitForExit:=True) |
| 394 | End Function |
| 395 | |
| 396 | Public Sub WriteLine(Optional s As String = "") Implements I_ConsoleDeviceHandle.WriteLine |
| 397 | If s.StringEmpty Then |
| 398 | Call input.WriteLine() |
| 399 | Else |
| 400 | Call input.WriteLine(s) |
| 401 | End If |
| 402 | |
| 403 | Call input.Flush() |
| 404 | End Sub |
| 405 | |
| 406 | <MethodImpl(MethodImplOptions.AggressiveInlining)> |
| 407 | Public Sub Write(buffer As Byte()) |
| 408 | Call input.BaseStream.Write(buffer, Scan0, buffer.Length) |
| 409 | End Sub |
| 410 | |
| 411 | <MethodImpl(MethodImplOptions.AggressiveInlining)> |
| 412 | Public Overrides Function ToString() As String |
| 413 | Return $"{Bin} {CLIArguments}" |
| 414 | End Function |
| 415 | |
| 416 | ''' <summary> |
| 417 | ''' 在进行隐士转换的时候,假若可执行文件的文件路径之中含有空格,则这个时候应该要特别的小心 |
| 418 | ''' </summary> |
| 419 | ''' <param name="CLI"></param> |
| 420 | ''' <returns></returns> |
| 421 | Public Shared Widening Operator CType(CLI As String) As IORedirect |
| 422 | Dim tokens As String() = Regex.Split(CLI, SPLIT_REGX_EXPRESSION) |
| 423 | Dim EXE As String = tokens.First |
| 424 | Dim args As String = Mid$(CLI, Len(EXE) + 1) |
| 425 | |
| 426 | Return New IORedirect(EXE, args) |
| 427 | End Operator |
| 428 | |
| 429 | Public Shared Function Shell(commandLine As String) As IORedirect |
| 430 | Return CType(commandLine, IORedirect) |
| 431 | End Function |
| 432 | |
| 433 | Private Function Read() As Integer Implements I_ConsoleDeviceHandle.Read |
| 434 | Return output.Length |
| 435 | End Function |
| 436 | |
| 437 | Private Function ReadLine() As String Implements I_ConsoleDeviceHandle.ReadLine |
| 438 | Return "" |
| 439 | End Function |
| 440 | |
| 441 | Public Sub WriteLine(s$, ParamArray args() As String) Implements I_ConsoleDeviceHandle.WriteLine |
| 442 | Call input.WriteLine(String.Format(s, args)) |
| 443 | Call input.Flush() |
| 444 | End Sub |
| 445 | |
| 446 | #Region "IDisposable Support" |
| 447 | Private disposedValue As Boolean ' To detect redundant calls |
| 448 | |
| 449 | ' IDisposable |
| 450 | Protected Overridable Sub Dispose(disposing As Boolean) |
| 451 | If Not Me.disposedValue Then |
| 452 | If disposing Then |
| 453 | ' TODO: dispose managed state (managed objects). |
| 454 | Call processInfo.Close() |
| 455 | Call processInfo.Dispose() |
| 456 | Call outputWaitHandle.Dispose() |
| 457 | Call errorWaitHandle.Dispose() |
| 458 | End If |
| 459 | |
| 460 | ' TODO: free unmanaged resources (unmanaged objects) and override Finalize() below. |
| 461 | ' TODO: set large fields to null. |
| 462 | End If |
| 463 | Me.disposedValue = True |
| 464 | End Sub |
| 465 | |
| 466 | ' TODO: override Finalize() only if Dispose( disposing As Boolean) above has code to free unmanaged resources. |
| 467 | 'Protected Overrides Sub Finalize() |
| 468 | ' ' Do not change this code. Put cleanup code in Dispose( disposing As Boolean) above. |
| 469 | ' Dispose(False) |
| 470 | ' MyBase.Finalize() |
| 471 | 'End Sub |
| 472 | |
| 473 | ' This code added by Visual Basic to correctly implement the disposable pattern. |
| 474 | Public Sub Dispose() Implements IDisposable.Dispose |
| 475 | ' Do not change this code. Put cleanup code in Dispose(disposing As Boolean) above. |
| 476 | Dispose(True) |
| 477 | GC.SuppressFinalize(Me) |
| 478 | End Sub |
| 479 | #End Region |
| 480 | End Class |
| 481 | End Namespace |