前提・実現したいこと
C#ですでに起動しているコンソールアプリケーションの標準出力を非同期で読み込みたい。
開発環境:Windows10、VS 2017
発生している問題・エラーメッセージ
C#プログラム内部でSystem.Diagnostics.Processクラスを利用して外部アプリケーションを起動して・・・のサンプルや解説はネットで検索して調査できるのですが、前提に記述の通り既に起動しているコンソールアプリケーションの標準出力を取得可能かどうかについては情報がなく・・・
そもそもC#では不可能なのか?すら分かりません。
ご存知の方、よろしくお願いします。
気になる質問をクリップする
クリップした質問は、後からいつでもMYページで確認できます。
またクリップした質問に回答があった際、通知やメールを受け取ることができます。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
回答3件
0
「既に起動しているコンソールアプリケーションの標準出力を取得可能かどうか」と言うことが興味深かったので、この点について調べてみました。回答としては不完全ですのでご注意ください。
本件について、制限はあるものの、まったくの不可能と言えなくもない方法があります。ただ、その方法はC#/.NET Frameworkのプログラムだけでは無理です。
アイディアとしては CreateRemoteThread
と言うネイティブのWin32 APIを使用し、既に起動しているコンソールアプリケーションに自分のコードを割り込ませ、既に開いている標準出力(STDOUT)のファイルハンドルを切り替えてしまおう、と言うものです。尚、CreateRemoteThread
を使用して別プロセスでコードを実行する手法は、ネイティブのWin32 APIを利用したものとしては良く知られたものです。
参考:
CodeZine - 別のプロセスにコードを割り込ませる3つの方法
イメージとしてはコンソールアプリケーションの中で始動したスレッドがfreopen
を実行しているようなものです。
freopen、_wfreope
私の方で試した限りでは、コマンドプロンプト上でprintf
等でコンソールに出力しているコンソールアプリケーションに、別のコマンドプロンプト上で実行したプログラムからCreateRemoteThread
でコードを実行させ、ファイルに出力を切り替えさせることができました。ただ、少なくとも以下のような制限事項を認めましたので、実用とするには難しいかもしれません。
- 32ビット版、64ビット版で分ける必要がある。既に起動しているコンソールアプリケーションが32ビット版なら、
割り込ませる方も32ビット版で動作させなければならない。
- コンソールアプリケーションが.NET Frameworkのプログラムで、
Console.WriteLine
のようなコンソール出力用のメソッドを使用している場合、切り替えた瞬間にExceptionが発生する。(Windows 7で発生。Windows 10ではOK)
突然ファイルハンドルの実体が変わっているので、.NET Frameworkの中で不整合が発生しているのだと思われます。
ハンドルされていない例外: System.IO.IOException: ハンドルが無効です。 場所 System.IO.__Error.WinIOError(Int32 errorCode, String maybeFullPath) 場所 System.IO.__ConsoleStream.Write(Byte[] buffer, Int32 offset, Int32 count ) 場所 System.IO.StreamWriter.Flush(Boolean flushStream, Boolean flushEncoder) 場所 System.IO.StreamWriter.Write(Char[] buffer, Int32 index, Int32 count) 場所 System.IO.TextWriter.WriteLine(String value) 場所 System.IO.TextWriter.SyncTextWriter.WriteLine(String value) 場所 System.Console.WriteLine(String value) 場所 ConApp1.Program.Main(String[] args) 場所 D:\project\ConApps\ConApp1\Program.cs:行 23
現状ではまだ不完全なので、実験したコードは載せないことにしました。完成度が上がったら参考程度にgithubにあげるかもしれません。その際は抜粋で本回答に追記したいと思います。(勿体付けるほどのコードではありません。。。)
投稿2018/05/30 13:18
総合スコア9183
0
こんにちは。
はじめに
プロセスのデバッグイベントを監視することで、出力を補足することができます。
ただしネットで検索してもC++
の情報が多くヒットし、質問者様のようにC#
でやるには少しテクニックが必要です。
上記のサイトではVC++でプロセスデバッガの作り方を解説しています。
どうするか
これらをC#で実現するシンプルな方法は、先のチュートリアルで示されたC++
のコードをC#
に置き換えてしまうことです。
WIN32 APIの関数をDllImport
を使ってC#からコールします。
例えば...
csharp
1[DllImport("kernel32.dll", EntryPoint = "WaitForDebugEvent", CallingConvention = CallingConvention.StdCall)] 2[return: MarshalAs(UnmanagedType.Bool)] 3public static extern bool WaitForDebugEvent([In,Out] ref DEBUG_EVENT lpDebugEvent, uint dwMilliseconds);
このような感じで使えます。
C++
の関数をC#
で使いたい場合はPInvoke.netが便利です。
誤解なきように申し上げると、全部の関数をDllImportしてください、という話ではありません。
C#
にない(または実現するのが難しい)ものに対して使ってください。
最後に
具体的なコード例を示さず申し訳ありません。コード量が多くなるのご了承ください。
本件、ニッチなことで情報が少ないのはよくわかります。
チュートリアルにあるような関数でネット検索すると類似の情報がヒットするかもしれませんので、もしよければ調べてみてください。
また、ここで示した話は少し古い情報です。昨今のバージョンではもっと別のアプローチがあるかもしれません。
全く別のアイディア
ここまでの話とは別に、シンプルなアイディアとして、監視先のプロセスからテキストファイルに出力を行い、監視元のプロセスはそのテキストファイルの変更を監視するというはどうでしょう。
これだとC#の標準的な機能で実現できそうです。
投稿2018/05/30 01:19
総合スコア212
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2018/06/01 01:37
2018/06/01 13:46
0
要求水準が分かりませんが、UIAutomationを使用して画面情報から文字列を取得する方法でかろうじて取得することはできます。
しかし、この方法だとポーリングやイベント等で監視して定期的に取得する必要があるのと
コンソールのバッファをオーバーして消えてしまった文字列は取れません。
APIフックを使用する場合でも、すでに出力されてしまった文字列は取得できません。
どう考えてもコンソールアプリを直接起動するかコンソールアプリ側を改修して別の手段で受け渡したほうがいいと思います。
一応、以下がUIAutomationを使用したサンプルコードです。
cmd.exeを起動してから実行してください。
参照に
UIAutomationClient
UIAutomationTypes
を追加
C#
1using System; 2using System.Collections.Generic; 3using System.Linq; 4using System.Text; 5using System.Threading.Tasks; 6using System.Windows.Automation; 7 8namespace ConsoleReader 9{ 10 class Program 11 { 12 [MTAThread] 13 static void Main(string[] args) 14 { 15 var console = AutomationElement.RootElement.FindFirst(TreeScope.Children, 16 new PropertyCondition( 17 AutomationElement.NameProperty, 18 //ここはWindowのタイトルなので適当に変える 19 @"C:\WINDOWS\system32\cmd.exe")); 20 21 if (console == null) return; 22 23 var textarea = console.FindFirst(TreeScope.Descendants, 24 new PropertyCondition( 25 AutomationElement.ControlTypeProperty, ControlType.Document)); 26 27 if (textarea == null) return; 28 29 var text = 30 textarea.GetCurrentPattern(TextPattern.Pattern) as TextPattern; 31 32 if (text == null) return; 33 34 var ranges = text.GetVisibleRanges(); 35 if (ranges == null) return; 36 if (ranges.Length == 0) return; 37 foreach(var range in ranges) 38 { 39 string line = range.GetText(-1);//日本語の部分の取得はおかしくて2重に取得される?(Win10 RS4 x64) 40 Console.WriteLine(line); 41 } 42 Console.ReadLine(); 43 44 } 45 } 46} 47
投稿2018/06/01 15:38
編集2018/06/01 15:39総合スコア818
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
あなたの回答
tips
太字
斜体
打ち消し線
見出し
引用テキストの挿入
コードの挿入
リンクの挿入
リストの挿入
番号リストの挿入
表の挿入
水平線の挿入
プレビュー
質問の解決につながる回答をしましょう。 サンプルコードなど、より具体的な説明があると質問者の理解の助けになります。 また、読む側のことを考えた、分かりやすい文章を心がけましょう。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2018/06/01 01:18
2018/06/01 13:51