gpt4 book ai didi

c# - C# Windows 控制台应用程序如何判断它是否以交互方式运行

转载 作者:IT王子 更新时间:2023-10-29 04:22:37 24 4
gpt4 key购买 nike

用 C# 编写的 Windows 控制台应用程序如何确定它是在非交互式环境(例如从服务或作为计划任务)中调用还是从能够与用户交互的环境(例如命令提示符或 PowerShell)中调用?

最佳答案

[编辑:4/2021 - 新答案...]

由于 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;
}
}

这会检查 PE header 中的 Subsystem 值.对于控制台应用程序,该值将为 3 而不是 2


2.讨论

related question 中所述, GUI vs. console 最可靠的指标是 PE header 中的“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,
};

就像该代码(在另一个答案中)一样简单,我们这里的案例可以大大简化。由于我们只对自己正在运行的进程(必须加载)特别感兴趣,因此您不必打开任何文件或从磁盘读取来获取 subsystem 值。我们的可执行镜像保证已经映射到内存中。通过调用 GetModuleHandleW 可以很简单地检索任何已加载文件图像的基址。功能:

[DllImport("kernel32.dll")]
static extern IntPtr GetModuleHandleW(IntPtr lpModuleName);

虽然我们可能会为此函数提供一个文件名,但事情又变得更容易了,我们不必这样做。传递 null,或者在本例中为 default(IntPtr.Zero)(与 IntPtr.Zero 相同),返回基地址当前进程的虚拟内存镜像。这消除了必须获取入口程序集及其 Location 属性等的额外步骤(前面提到过)。事不宜迟,这里是新的简化代码:

static Subsystem GetSubsystem()
{
var p = GetModuleHandleW(default); // VM base address of mapped PE image
p += Marshal.ReadInt32(p, 0x3C); // RVA of COFF/PE within DOS header
return (Subsystem)Marshal.ReadInt16(p + 0x5C); // PE offset to 'Subsystem' word
}

public static bool IsGui => GetSubsystem() == Subsystem.WindowsGui;

public static bool IsConsole => GetSubsystem() == Subsystem.WindowsCui;


【新答案正式结束】


3.奖金讨论

就 .NET 而言,子系统 可能是或唯一PE header 中最有用的信息。但是,根据您对细节的容忍度,可能还有其他无价的花絮,并且很容易使用刚刚描述的技术来检索其他有趣的数据。

显然,通过改变前面使用的最终字段偏移量(0x5C),您可以访问 COFF 或 PE header 中的其他字段。下一个片段说明了 Subsystem(如上所述)加上三个附加字段及其各自的偏移量。

NOTE: To reduce clutter, the enum declarations used in the following can be found here

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 header 合并到以下 C# struct 定义中,并且仅包含我们认为有趣的四个字段:

[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;
};

NOTE: A fuller version of this struct, without the omitted fields, can be found here

像这样的任何互操作 struct 都必须在运行时正确设置,并且有很多选项可以这样做。理想情况下,将 struct 覆盖“in-situ”直接强加到非托管内存通常更好,这样就不需要发生内存复制。但是,为了避免进一步延长此处的讨论,我将展示一种涉及复制的更简单的方法。

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:        Amd64Characteristics:    ExecutableImage, LargeAddressAwareSubsystem:          WindowsCui (3)DllCharacteristics: HighEntropyVA, DynamicBase, NxCompatible, NoSeh, TSAware

...compared to GUI (WPF) application:

MachineType:        Amd64Characteristics:    ExecutableImage, LargeAddressAwareSubsystem:          WindowsGui (2)DllCharacteristics: HighEntropyVA, DynamicBase, NxCompatible, NoSeh, TSAware


[OLD: original answer from 2012...]

To determine if a .NET application is running in GUI mode:

bool is_console_app = Console.OpenStandardInput(1) != Stream.Null;

关于c# - C# Windows 控制台应用程序如何判断它是否以交互方式运行,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/1188658/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com