C#で記述されたWindowsコンソールアプリケーションは、非対話型環境(サービスやスケジュールされたタスクなど)で呼び出されたのか、ユーザーとの対話が可能な環境(コマンドプロンプトやPowerShellなど)から呼び出されたのかをどのように判断できますか?
6 に答える
[編集:2021年4月-新しい答え...]
Visual Studioデバッガーの最近の変更により、デバッグ時に元の回答が正しく機能しなくなりました。これを改善するために、私はまったく異なるアプローチを提供しています。元の回答のテキストは下部に含まれています。
1.コードだけをお願いします...
.NETアプリケーションがGUIモードで実行されているかどうかを確認するには:
[DllImport("kernel32.dll")] static extern IntPtr GetModuleHandleW(IntPtr _);
public static bool IsGui
{
get
{
var p = GetModuleHandleW(default);
return Marshal.ReadInt16(p, Marshal.ReadInt32(p, 0x3C) + 0x5C) == 2;
}
}
Subsystem
これにより、PEヘッダーの値がチェックされます。コンソールアプリケーションの場合、値はの3
代わりになり2
ます。
2.ディスカッション
関連する質問で述べたように、GUI と コンソールの最も信頼できる指標は、実行可能イメージのPEヘッダーの「Subsystem
」フィールドです。次のC#は、許容される(文書化された)値を示しています。enum
public enum Subsystem : ushort
{
Unknown /**/ = 0x0000,
Native /**/ = 0x0001,
WindowsGui /**/ = 0x0002,
WindowsCui /**/ = 0x0003,
OS2Cui /**/ = 0x0005,
PosixCui /**/ = 0x0007,
NativeWindows /**/ = 0x0008,
WindowsCEGui /**/ = 0x0009,
EfiApplication /**/ = 0x000A,
EfiBootServiceDriver /**/ = 0x000B,
EfiRuntimeDriver /**/ = 0x000C,
EfiRom /**/ = 0x000D,
Xbox /**/ = 0x000E,
WindowsBootApplication /**/ = 0x0010,
};
そのコードは簡単ですが、ここでのケースは単純化できます。実行中のプロセス(必ずロードされる)にのみ関心があるため、サブシステム値を取得するためにファイルを開いたり、ディスクから読み取ったりする必要はありません。実行可能イメージは、すでにメモリにマップされていることが保証されています。次の関数を呼び出すことで、ロードされたファイルイメージのベースアドレスを簡単に取得できます。
[DllImport("kernel32.dll")]
static extern IntPtr GetModuleHandleW(IntPtr lpModuleName);
この関数にファイル名を指定することもできますが、これも簡単であり、そうする必要はありません。を渡すnull
か、この場合はdefault(IntPtr.Zero)
(と同じIntPtr.Zero
)、現在のプロセスの仮想メモリイメージのベースアドレスを返します。これにより、エントリアセンブリとそのプロパティなどをフェッチする必要があるという余分な手順(前述のとおり)が不要になります。これLocation
以上の手間をかけずに、新しく簡略化されたコードを次に示します。
static Subsystem GetSubsystem()
{
var p = GetModuleHandleW(default); // PE image VM mapped base address
p += Marshal.ReadInt32(p, 0x3C); // RVA of COFF/PE within DOS header
return (Subsystem)Marshal.ReadInt16(p + 0x5C); // PE offset to 'Subsystem' value
}
public static bool IsGui => GetSubsystem() == Subsystem.WindowsGui;
public static bool IsConsole => GetSubsystem() == Subsystem.WindowsCui;
【公式回答終了】
3.ボーナスディスカッション
.NETの目的では、 PEヘッダーSubsystem
でおそらく最も(<em>または唯一の)有用な情報です。ただし、細目に対する許容度によっては、他にも貴重な情報が存在する可能性があり、今説明した手法を使用して、追加の興味深いデータを取得するのは簡単です。
明らかに、前に使用した最終フィールドオフセット(0x5C
)を変更することで、COFFまたはPEヘッダーの他のフィールドにアクセスできます。次のスニペットは、Subsystem
(上記のように)これに加えて、それぞれのオフセットを持つ3つの追加フィールドを示しています。
注:混乱を減らすため
enum
に、以下で使用される宣言はここにあります
var p = GetModuleHandleW(default); // PE image VM mapped base address
p += Marshal.ReadInt32(p, 0x3C); // RVA of COFF/PE within DOS header
var subsys = (Subsystem)Marshal.ReadInt16(p + 0x005C); // (same as before)
var machine = (ImageFileMachine)Marshal.ReadInt16(p + 0x0004); // new
var imgType = (ImageFileCharacteristics)Marshal.ReadInt16(p + 0x0016); // new
var dllFlags = (DllCharacteristics)Marshal.ReadInt16(p + 0x005E); // new
// ... etc.
アンマネージメモリ内の複数のフィールドにアクセスする際の改善には、オーバーレイを定義することが不可欠struct
です。これにより、C#を使用した直接かつ自然な管理アクセスが可能になります。実行中の例では、隣接するCOFFヘッダーとPEヘッダーを一緒に次のC#定義にマージし、struct
興味深いと思われる4つのフィールドのみを含めました。
[StructLayout(LayoutKind.Explicit)]
struct COFF_PE
{
[FieldOffset(0x04)] public ImageFileMachine MachineType;
[FieldOffset(0x16)] public ImageFileCharacteristics Characteristics;
[FieldOffset(0x5C)] public Subsystem Subsystem;
[FieldOffset(0x5E)] public DllCharacteristics DllCharacteristics;
};
注:フィールドが省略されていない、この構造体の完全なバージョンは、ここにあります。
このような相互運用機能struct
は、実行時に適切に設定する必要があり、そのための多くのオプションがあります。理想的には、一般に、struct
オーバーレイを「その場で」アンマネージメモリに直接適用して、メモリのコピーを実行する必要がないようにすることをお勧めします。ただし、ここでの議論がさらに長くなることを避けるために、代わりに、コピーを伴うより簡単な方法を示します。
var p = GetModuleHandleW(default);
var _pe = Marshal.PtrToStructure<COFF_PE>(p + Marshal.ReadInt32(p, 0x3C));
Trace.WriteLine($@"
MachineType: {_pe.MachineType}
Characteristics: {_pe.Characteristics}
Subsystem: {_pe.Subsystem}
DllCharacteristics: {_pe.DllCharacteristics}");
4.デモコードの出力
これは、コンソールプログラムが実行されているときの出力です...
MachineType:Amd64 特徴:ExecutableImage、LargeAddressAware サブシステム:WindowsCui(3) DllCharacteristics:HighEntropyVA、DynamicBase、NxCompatible、NoSeh、TSAware
... GUI(WPF)アプリケーションとの比較:
MachineType:Amd64 特徴:ExecutableImage、LargeAddressAware サブシステム:WindowsGui(2) DllCharacteristics:HighEntropyVA、DynamicBase、NxCompatible、NoSeh、TSAware
[旧:2012年からの元の回答...]
.NETアプリケーションがGUIモードで実行されているかどうかを確認するには:
bool is_console_app = Console.OpenStandardInput(1) != Stream.Null;
私はそれをテストしていませんが、Environment.UserInteractiveは有望に見えます。
プログラムが終了した後もコンソールが存在し続けるかどうかを判断するだけの場合(たとえば、Enter
プログラムが終了する前にユーザーにヒットを促すことができるようにするため)、必要なのは次のことだけです。プロセスがコンソールに接続されている唯一のプロセスであるかどうかを確認してください。そうである場合、プロセスが終了するとコンソールは破棄されます。コンソールに接続されている他のプロセスがある場合、コンソールは引き続き存在します(プログラムが最後のプロセスではないため)。
例えば*:
using System;
using System.Runtime.InteropServices;
namespace CheckIfConsoleWillBeDestroyedAtTheEnd
{
internal class Program
{
private static void Main(string[] args)
{
// ...
if (ConsoleWillBeDestroyedAtTheEnd())
{
Console.WriteLine("Press any key to continue . . .");
Console.ReadKey();
}
}
private static bool ConsoleWillBeDestroyedAtTheEnd()
{
var processList = new uint[1];
var processCount = GetConsoleProcessList(processList, 1);
return processCount == 1;
}
[DllImport("kernel32.dll", SetLastError = true)]
static extern uint GetConsoleProcessList(uint[] processList, uint processCount);
}
}
(*)ここにあるコードから適応。
Glenn Slaydenのソリューションの可能な改善:
bool isConsoleApplication = Console.In != StreamReader.Null;
インタラクティブコンソールでユーザー入力を求めるプロンプトを表示しますが、コンソールなしで実行した場合、または入力がリダイレクトされた場合は何もしません。
if (Environment.UserInteractive && !Console.IsInputRedirected)
{
Console.ReadKey();
}