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

2023-12-24

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


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

由于 Visual Studio 调试器最近发生了变化,我原来的答案在调试时停止正常工作。为了解决这个问题,我提供了一种完全不同的方法。原始答案的文本包含在底部。


1. Just the code, please...

要确定 .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标头 https://en.wikipedia.org/wiki/Portable_Executable。对于控制台应用程序,该值将为3代替2.


2. Discussion

如a中所述相关问题 https://stackoverflow.com/questions/30890104/determine-whether-assembly-is-a-gui-application,最可靠的指标GUI vs. console是个 ”Subsystem“ 字段中PE标头 https://learn.microsoft.com/en-us/windows/win32/debug/pe-format#windows-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,
};

尽管该代码(在另一个答案中)很简单,但我们这里的情况可以大大简化。由于我们只对自己正在运行的进程(必须加载)特别感兴趣,因此您不必打开任何文件或从磁盘读取来获取子系统价值。我们的可执行映像保证已经映射到内存中。通过调用以下函数可以轻松检索任何加载的文件图像的基地址GetModuleHandleW https://learn.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-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. Bonus Discussion

出于 .NET 的目的,Subsystem也许是最——or only— 中的有用信息PE标头。但根据您对细节的容忍度,可能还有其他宝贵的花絮,并且可以轻松使用刚刚描述的技术来检索其他有趣的数据。

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

注意:为了减少混乱,enum以下使用的声明可以找到here https://pastebin.com/pZL3Y7vM

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定义,并且只包含我们认为有趣的四个字段:

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

注意:可以找到此结构的更完整版本,没有省略的字段here https://pastebin.com/EU1QJJ7i

任何互操作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. Output of the demo code

这是当console程序正在运行...


MachineType:        Amd64
Characteristics:    ExecutableImage, LargeAddressAware
Subsystem:          WindowsCui (3)
DllCharacteristics: HighEntropyVA, DynamicBase, NxCompatible, NoSeh, TSAware  

...相比GUI(WPF)应用程序:


MachineType:        Amd64
Characteristics:    ExecutableImage, LargeAddressAware
Subsystem:          WindowsGui (2)
DllCharacteristics: HighEntropyVA, DynamicBase, NxCompatible, NoSeh, TSAware  


[旧:2012 年的原始答案...]

要确定 .NET 应用程序是否在 GUI 模式下运行:

bool is_console_app = Console.OpenStandardInput(1) != Stream.Null;
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C# Windows 控制台应用程序如何判断它是否以交互方式运行 的相关文章

随机推荐

  • Angular2路由器可以激活,带参数吗?

    我已经看到了关于这个具体问题的一些问题 我最近只从事 Angular2 项目 无论如何 我现在遇到了一个问题 在已弃用的路由器中 我在路由的数据部分添加了我的用户角色 我覆盖了 routerOutlet 以便我可以在激活路由之前检查此值 目
  • 最佳实践:创建免费版和付费版时如何处理iOS App的代码差异?

    我想在 App Store 上发布我的 iOS 应用程序的两个版本 一种是付费的 另一种是免费的 到目前为止 我的付费应用程序代码已经完成 现在我想为免费应用程序的 iAd 和 InAppPurchase 添加更多代码 维护这两个版本的最佳
  • 使用 Pandas 和 spaCy 进行标记化

    我正在开发我的第一个 Python 项目 并且拥有相当大的数据集 数十万行 我需要对 5 个文本列 每个 单元格 有多个文本句子 进行一些 nlp 聚类 分类 并且一直在使用 pandas 来组织 构建数据集 我希望对所有 nlp 使用 s
  • 在 Windows 7 64 位上安装 Hg-Git

    我正在尝试安装一段时间Hg Git 插件 http hg git github com 到我的 Windows 7 操作系统 我遇到了一些困难 例如安装 Python 和其他实用程序中描述的这个博客 http blog sadphaeton
  • NoSQL 数据库中的架构迁移脚本

    我有一个一直使用 C 实体框架和 SQL Server 的活动项目 然而 随着 NoSQL 替代方案的可行性日益增加 我正在研究将项目切换为使用 MongoDB 的所有影响 显然 主要的过渡障碍是由于 无模式 造成的 找到了对 C 等语言意
  • 用于反转文件名中日期顺序的批处理脚本

    我想使用批处理脚本来重命名一堆使用以下命名方案的文件 File 2 9 pdf File 3 9 pdf File 4 9 pdf 我想反转数字 使它们变成 File 9 2 pdf File 9 3 pdf File 9 4 pdf 通常
  • 实体框架一对多 IQueryable

    所以 我一直在玩弄这个tutorial https www tutorialspoint com entity framework entity framework first example htm因为我需要优化一些查询 然而 我意识到一
  • 一起转换图像和蒙版(Keras 示例)

    此代码片段取自 Keras API 参考 数据预处理 部分 一起转换图像和蒙版的示例 link https keras io api preprocessing image https keras io api preprocessing
  • Angular 4 调用路由的函数

    我通过 Angular Web 应用程序中的菜单设置了路由 菜单中设置了 routerLinks 如下面的主页链接 a a
  • FileStream.Dispose 是否立即关闭文件?

    我有一些代码 通过使用 MemoryStream WriteTo 将 MemoryStream 保存到 FileStream 来写入文件 文件关闭后再次打开以读取一些元数据 这在 80 90 的情况下有效 另外 20 我收到一个异常 说该文
  • 如何验证 Apple APN 设备令牌 - WCF .NET

    我正在构建一个 WCF REST Web 服务 移动应用程序调用该服务将 Apple 设备令牌插入数据库 我想在插入数据库之前验证设备令牌 是否有办法验证设备令牌以了解其是否有效 我在论坛上进行了搜索 但找不到任何示例代码 在 NET 中
  • 将 UITabBarItem 图像向下移动?

    通常在每个选项卡上UITabBar您有一个小图像和一个命名该选项卡的标题 图像位于选项卡顶部 居中以容纳下面的标题 我的问题是 如果你想要一个只有图像而没有标题的 tabBar 有没有办法将图像向下移动 以便它在选项卡中更好地居中 我目前正
  • 将 iframe 插入到 React 组件中

    我有一个小问题 从服务请求数据后 我得到了一个 iframe 代码作为响应 我想将其作为道具传递给我的模态组件并显示它 但是当我简单地 this props iframe 它在渲染函数中显然将其显示为字符串 在 React 中或使用 JSX
  • 使用 PHP 从纯文本和 HTML 文本的混合创建 PDF [重复]

    这个问题在这里已经有答案了 可能的重复 使用 PHP 将 HTML CSS 转换为 PDF https stackoverflow com questions 391005 convert html css to pdf with php
  • 在 UITableView 中对齐多个运行时生成的 UILabels

    我有一个UITableView需要通过列出类似的样式来支持内容 But the tricky part is that the amount of Label will vary with each cell some may have o
  • 在 Visual Studio 2015 中从 *.ts 生成 *.js

    当我将 ts 文件添加到 Visual Studio 2015 并对其进行编译时 js 文件不是 ts 的代码隐藏文件 它仅驻留在该文件夹中 而不是 Visual Studio 项目的一部分 这是设计使然还是我破坏了项目中的某些内容 如果是
  • 评估软件最低要求

    有没有办法评估软件的最低要求 我的意思是 我怎样才能发现我的应用程序需要的最小 RAM 量 Thanks 分析器在这里不会为您提供帮助 也不会估计数据结构的大小 探查器当然可以告诉您代码在哪里花费了最多的 CPU 时间 但它不会告诉您是否未
  • 如何从包含联系人详细信息且对象不在电话簿中的对象生成 .vcf 文件

    我想为一个对象生成一个 vcf 文件 其中包含姓名 图像 电话号码 传真号码 电子邮件地址 地址等联系信息 该对象未添加到手机的通讯录中 但存储在我的地址簿中 应用 生成 vcf 文件后 我可以像这样发送此 vcard Intent i n
  • Android 上的自定义字体和自定义 Textview

    从我需要开发的应用程序中 我收到了一种特定字体 其中包含许多文件 例如 FontName Regular 字体名称 粗体 字体名称 我需要在应用程序的所有文本视图中使用它 首先我认为这是一项容易的任务 查看SO并发现一个非常好的线程 her
  • C# Windows 控制台应用程序如何判断它是否以交互方式运行

    用 C 编写的 Windows 控制台应用程序如何确定它是在非交互式环境 例如从服务或计划任务 中调用还是在能够用户交互的环境 例如命令提示符或 PowerShell 中调用 编辑 4 2021 新答案 由于 Visual Studio 调