質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

90.60%

  • C#

    6862questions

    C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

既存アプリケーション標準出力の非同期取得

受付中

回答 3

投稿

  • 評価
  • クリップ 2
  • VIEW 226

myuzupa

score 2

 前提・実現したいこと

C#ですでに起動しているコンソールアプリケーションの標準出力を非同期で読み込みたい。
開発環境:Windows10、VS 2017

 発生している問題・エラーメッセージ

C#プログラム内部でSystem.Diagnostics.Processクラスを利用して外部アプリケーションを起動して・・・のサンプルや解説はネットで検索して調査できるのですが、前提に記述の通り既に起動しているコンソールアプリケーションの標準出力を取得可能かどうかについては情報がなく・・・
そもそもC#では不可能なのか?すら分かりません。
ご存知の方、よろしくお願いします。

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 3

+4

既に起動しているコンソールアプリケーションの標準出力を取得可能かどうか」と言うことが興味深かったので、この点について調べてみました。回答としては不完全ですのでご注意ください。

本件について、制限はあるものの、まったくの不可能と言えなくもない方法があります。ただ、その方法はC#/.NET Frameworkのプログラムだけでは無理です。

アイディアとしては CreateRemoteThreadと言うネイティブのWin32 APIを使用し、既に起動しているコンソールアプリケーションに自分のコードを割り込ませ、既に開いている標準出力(STDOUT)のファイルハンドルを切り替えてしまおう、と言うものです。尚、CreateRemoteThreadを使用して別プロセスでコードを実行する手法は、ネイティブのWin32 APIを利用したものとしては良く知られたものです。

参考:
CodeZine - 別のプロセスにコードを割り込ませる3つの方法

イメージとしてはコンソールアプリケーションの中で始動したスレッドがfreopenを実行しているようなものです。
freopen、_wfreope

私の方で試した限りでは、コマンドプロンプト上でprintf等でコンソールに出力しているコンソールアプリケーションに、別のコマンドプロンプト上で実行したプログラムからCreateRemoteThreadでコードを実行させ、ファイルに出力を切り替えさせることができました。ただ、少なくとも以下のような制限事項を認めましたので、実用とするには難しいかもしれません。

  1. 32ビット版、64ビット版で分ける必要がある。既に起動しているコンソールアプリケーションが32ビット版なら、
    割り込ませる方も32ビット版で動作させなければならない。

  2. コンソールアプリケーションが.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/06/01 10:18

    ご回答ありがとうございます。プロジェクトの規模的にご提示いただいた方法は実装が厳しそうですので別のアプローチを含めてもう少し模索してみます。ご提供いただいた情報を含めて内部で検討させていただきます。

    キャンセル

  • 2018/06/01 22:51

    私の回答はどちらかと言うと「できるかできないか」と言う技術的興味から、という部分も大きいので、あまり気にしないでください。私も一般的な製品でこの案を採用するかと言えば、安全性も考えて採用しない可能性が高いです。

    キャンセル

+3

こんにちは。

 はじめに

プロセスのデバッグイベントを監視することで、出力を補足することができます。
ただしネットで検索してもC++の情報が多くヒットし、質問者様のようにC#でやるには少しテクニックが必要です。

プロセスデバッガを作ってみる

上記のサイトではVC++でプロセスデバッガの作り方を解説しています。

 どうするか

これらをC#で実現するシンプルな方法は、先のチュートリアルで示されたC++のコードをC#に置き換えてしまうことです。
WIN32 APIの関数をDllImportを使ってC#からコールします。

例えば...

[DllImport("kernel32.dll", EntryPoint = "WaitForDebugEvent", CallingConvention = CallingConvention.StdCall)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool WaitForDebugEvent([In,Out] ref DEBUG_EVENT lpDebugEvent, uint dwMilliseconds);


このような感じで使えます。
C++の関数をC#で使いたい場合はPInvoke.netが便利です。

誤解なきように申し上げると、全部の関数をDllImportしてください、という話ではありません。
C#にない(または実現するのが難しい)ものに対して使ってください。

 最後に

具体的なコード例を示さず申し訳ありません。コード量が多くなるのご了承ください。
本件、ニッチなことで情報が少ないのはよくわかります。
チュートリアルにあるような関数でネット検索すると類似の情報がヒットするかもしれませんので、もしよければ調べてみてください。

また、ここで示した話は少し古い情報です。昨今のバージョンではもっと別のアプローチがあるかもしれません。

 全く別のアイディア

ここまでの話とは別に、シンプルなアイディアとして、監視先のプロセスからテキストファイルに出力を行い、監視元のプロセスはそのテキストファイルの変更を監視するというはどうでしょう。
これだとC#の標準的な機能で実現できそうです。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/06/01 10:18

    ご丁寧な回答ありがとうございます。「全く別のアイディア」として頂いていたアプローチは検討中だったのですが、プロジェクトの規模的にそちらでの解決を考えるのが現実的かもしれません。この件では更に調べていますので、また追加の情報がわかりましたら書き込みします。

    キャンセル

  • 2018/06/01 10:37

    コメントありがとうございます。プロセス間の制御は様々なトラブルを生むことがあり、とくに.Netから外れた場合は問題の解決が難しくなると思います。
    そのため別案のほうでクリアできるのであればそれが好ましいと思います。ただそちらについては似たような仕様の実装を行った経験がありませんので、詳細なフォローは難しいです。無責任な回答で申し訳ありません。
    また表題の問題について何かあればよろしくお願いします。

    キャンセル

  • 2018/06/01 22:46

    監視先のプログラムのコードを修正できるのであれば、私もg_uoさんの代案を推します。
    ファイルをcloseせずに書き込み続けたり、flushしなかったり、ファイルの共有モードの設定が
    不適切だったりすると、排他の関係で書き込みに支障が出たり、監視側が読めなかったりするケースがあるので、気に留めてみてください。

    キャンセル

+1

要求水準が分かりませんが、UIAutomationを使用して画面情報から文字列を取得する方法でかろうじて取得することはできます。

しかし、この方法だとポーリングやイベント等で監視して定期的に取得する必要があるのと
コンソールのバッファをオーバーして消えてしまった文字列は取れません。
APIフックを使用する場合でも、すでに出力されてしまった文字列は取得できません。

どう考えてもコンソールアプリを直接起動するかコンソールアプリ側を改修して別の手段で受け渡したほうがいいと思います。

一応、以下がUIAutomationを使用したサンプルコードです。
cmd.exeを起動してから実行してください。

参照に
UIAutomationClient
UIAutomationTypes
を追加

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Automation;

namespace ConsoleReader
{
    class Program
    {
        [MTAThread]
        static void Main(string[] args)
        {
            var console = AutomationElement.RootElement.FindFirst(TreeScope.Children,
                new PropertyCondition(
                    AutomationElement.NameProperty,
                    //ここはWindowのタイトルなので適当に変える
                    @"C:\WINDOWS\system32\cmd.exe"));

            if (console == null) return;

            var textarea = console.FindFirst(TreeScope.Descendants,
                new PropertyCondition(
                    AutomationElement.ControlTypeProperty, ControlType.Document));

            if (textarea == null) return;

            var text = 
                textarea.GetCurrentPattern(TextPattern.Pattern) as TextPattern;

            if (text == null) return;

            var ranges = text.GetVisibleRanges();
            if (ranges == null) return;
            if (ranges.Length == 0) return;
            foreach(var range in ranges)
            {
                string line = range.GetText(-1);//日本語の部分の取得はおかしくて2重に取得される?(Win10 RS4 x64)
                Console.WriteLine(line);
            }
            Console.ReadLine();

        }
    }
}

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 90.60%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る

  • C#

    6862questions

    C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。