C# Windows 控制台应用程序如何判断它是否以交互方式运行
用 C# 编写的 Windows 控制台应用程序如何确定它是在非交互式环境(例如从服务或计划任务)中调用还是从能够用户交互的环境(例如命令提示符或 PowerShell)调用?
How can a Windows console application written in C# determine whether it is invoked in a non-interactive environment (e.g. from a service or as a scheduled task) or from an environment capable of user-interaction (e.g. Command Prompt or PowerShell)?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(6)
我还没有测试过,但是 Environment.UserInteractive 看起来很有希望。
I haven't tested it, but Environment.UserInteractive looks promising.
Glenn Slayden 解决方案的可能改进:
A possible improvement of Glenn Slayden's solution:
要在交互式控制台中提示用户输入,但在没有控制台的情况下运行或输入已重定向时不执行任何操作:
To prompt for user input in an interactive console, but do nothing when run without a console or when input has been redirected:
如果您想要做的只是确定程序退出后控制台是否继续存在(例如,您可以在程序退出前提示用户按
Enter
退出),那么您所要做的就是检查您的进程是否是唯一连接到控制台的进程。 如果是,那么当您的进程退出时,控制台将被销毁。 如果还有其他进程附加到控制台,那么控制台将继续存在(因为您的程序不会是最后一个)。例如*:
(*) 改编自此处的代码。
If all you're trying to do is to determine whether the console will continue to exist after your program exits (so that you can, for example, prompt the user to hit
Enter
before the program exits), then all you have to do is to check if your process is the only one attached to the console. If it is, then the console will be destroyed when your process exits. If there are other processes attached to the console, then the console will continue to exist (because your program won't be the last one).For example*:
(*) Adapted from code found here.
Environment.UserInteractive 属性
Environment.UserInteractive Property
[编辑:4/2021 - 新答案...]
由于 Visual Studio 调试器最近发生了更改,我原来的答案在调试时停止正常工作。 为了解决这个问题,我提供了一种完全不同的方法。 原始答案的文本包含在底部。
<强>
1. Just the code, please...
要确定 .NET 应用程序是否在 GUI 模式下运行:
这会检查 PE 标头。 对于控制台应用程序,该值将为
3
而不是2
。<强>
2. Discussion
正如相关问题中所述,最可靠的指标GUI vs. console 是 PE 标头 可执行映像。 以下 C#
enum
列出了允许的(已记录的)值:尽管该代码(在另一个答案中)很简单,但我们这里的情况可以大大简化。 由于我们只对自己正在运行的进程(必须加载)特别感兴趣,因此您不必打开任何文件或从磁盘读取来获取子系统值。 我们的可执行映像保证已经映射到内存中。 通过调用
GetModuleHandleW
函数:虽然我们可能为此函数提供文件名,但事情变得更容易,我们不必这样做。 传递
null
,或者在本例中为default(IntPtr.Zero)
(与IntPtr.Zero
相同),返回基地址当前进程的虚拟内存映像。 这消除了必须获取入口程序集及其Location
属性等的额外步骤(前面提到过)。言归正传,这里是新的简化代码:[正式结束新答案]
3. Bonus Discussion
就 .NET 而言,
Subsystem
可能是PE 标头中最有用的信息(或唯一)。 但根据您对细节的容忍度,可能还有其他宝贵的花絮,并且可以轻松使用刚刚描述的技术来检索其他有趣的数据。显然,通过更改之前使用的最终字段偏移量(
0x5C
),您可以访问 COFF 或 PE 标头中的其他字段。 下一个片段说明了Subsystem
(如上所述)以及三个附加字段及其各自的偏移量。为了改进访问非托管内存中的多个字段时的情况,必须定义一个覆盖
struct
。 这允许使用 C# 进行直接、自然的托管访问。 对于运行示例,我将相邻的 COFF 和 PE 标头合并到以下 C#struct
定义中,并且仅包含我们认为有趣的四个字段:像这样的任何互操作
struct
都必须在运行时正确设置,并且有很多选项可以实现此目的。 理想情况下,通常最好将struct
覆盖“原位”直接施加在非托管内存上,这样就不需要发生内存复制。 然而,为了避免进一步延长此处的讨论,我将展示一种涉及复制的更简单的方法。<强>
4. Output of the demo code
这是控制台程序运行时的输出...
...与GUI (WPF)应用程序相比:
[OLD:2012年的原始答案.. .]
要确定 .NET 应用程序是否在 GUI 模式下运行:
[EDIT: 4/2021 - new answer...]
Due to a recent change in the Visual Studio debugger, my original answer stopped working correctly when debugging. To remedy this, I'm providing an entirely different approach. The text of the original answer is included at the bottom.
1. Just the code, please...
To determine if a .NET application is running in GUI mode:
This checks the
Subsystem
value in the PE header. For a console application, the value will be3
instead of2
.2. Discussion
As noted in a related question, the most reliable indicator of GUI vs. console is the "
Subsystem
" field in the PE header of the executable image. The following C#enum
lists the allowable (documented) values:As easy as that code (in that other answer) is, our case here can be vastly simplified. Since we are only specifically interested in our own running process (which is necessarily loaded), you don't have to open any file or read from the disk to obtain the subsystem value. Our executable image is guaranteed to be already mapped into memory. And it is simple to retrieve the base address for any loaded file image by calling the
GetModuleHandleW
function:Although we might provide a filename to this function, again things are easier and we don't have to. Passing
null
, or in this case,default(IntPtr.Zero)
(which is the same asIntPtr.Zero
), returns the base address of the virtual memory image for the current process. This eliminates the extra steps (alluded to earlier) of having to fetch the entry assembly and itsLocation
property, etc. Without further ado, here is the new and simplified code:[official end of the new answer]
3. Bonus Discussion
For the purposes of .NET,
Subsystem
is perhaps the most—or only—useful piece of information in the PE Header. But depending on your tolerance for minutiae, there could be other invaluable tidbits, and it's easy to use the technique just described to retrieve additional interesting data.Obviously, by changing the final field offset (
0x5C
) used earlier, you can access other fields in the COFF or PE header. The next snippet illustrates this forSubsystem
(as above) plus three additional fields with their respective offsets.To improve things when accessing multiple fields in unmanaged memory, it's essential to define an overlaying
struct
. This allows for direct and natural managed access using C#. For the running example, I merged the adjacent COFF and PE headers together into the following C#struct
definition, and only included the four fields we deemed interesting:Any interop
struct
such as this has to be properly setup at runtime, and there are many options for doing so. Ideally, its generally better to impose thestruct
overlay "in-situ" directly on the unmanaged memory, so that no memory copying needs to occur. To avoid prolonging the discussion here even further however, I will instead show an easier method that does involve copying.4. Output of the demo code
Here is the output when a console program is running...
...compared to GUI (WPF) application:
[OLD: original answer from 2012...]
To determine if a .NET application is running in GUI mode: