确定程序集是否为GUI应用程序。

3
我正在尝试确定一个C#程序集是GUI还是控制台应用程序,以构建一个工具,该工具将自动重新创建丢失的快捷方式。
目前,我有一个例程,它递归地遍历Program Files(和x86目录)中的所有目录。
对于它找到的每个EXE文件,该工具调用IsGuiApplication,传递EXE的名称。
从那里,我使用LoadFrom创建一个Assembly对象。我想检查这个程序集是否有GUI输出,但我不确定如何在C#中进行测试。
我的当前想法是使用GetStdHandle,但我不知道如何将其应用于运行中的应用程序之外的程序集。
由于我在C#反射方面的经验有限,因此任何帮助都将不胜感激。

using Microsoft.Win32;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;

namespace BatchShortcutBuild
{
    class Program
    {
        //I'm uncertain that I need to use this method
        [DllImport("kernel32.dll", SetLastError = true)]
        static extern IntPtr GetStdHandle(int nStdHandle);

        static void Main(string[] args) {
            BuildShortcuts();
            Console.ReadLine();
        }

        public static void BuildShortcuts() {
            String dirRoot = "C:\\Program Files\\";
            processRoot(dirRoot);

            dirRoot = "C:\\Program Files (x86)\\";
            processRoot(dirRoot);

            Console.WriteLine("Finished enumerating files");
            Console.ReadLine();
        }


        public static void processRoot(String path) {
            try {
                foreach (String theDir in Directory.EnumerateDirectories(path)) {
                    processRoot(theDir);
                }

                foreach (String theFile in Directory.EnumerateFiles(path, "*.exe")) {
                    if (IsGuiApplication(theFile)) {
                        //I would generate a shortcut here
                    }
                }
            } catch { }
        }

        public static bool IsGuiApplication(String filePath) {
            Console.WriteLine(filePath);
            Assembly a = Assembly.LoadFrom(filePath);
            //How to get the program type from the assembly?
            return false;
        }

    }
}


嗯,有很多种类型的_GUI_。您可以检查程序集是否包含从System.Windows.Forms.Form派生的类,这将是它包含Windows Forms GUI的提示。我不确定如何检查程序集是否包含WPF GUI。 - cbr
1
你应该能够检查可执行文件的PE头中的“Subsystem”字段。这里有一个例子 - Jester
@Jester,谢谢,这正是我在寻找的。我刚刚在你的建议中发现了你添加的同一页作为示例的页面。 - Alex
请注意,反射仅适用于.NET应用程序。这对您来说是否仍然足够好? - Luaan
5个回答

4

仅仅为了保险起见,@Killany和@Nissim建议的方法并不是100%准确的,因为控制台应用程序可能会引用System.Windows.* dlls(可能是错误引用或者需要使用“System.Windows”程序集提供的其他功能)。

我不确定是否存在一个100%的方法,因为有些应用程序可以被给予参数以静默运行或者带UI运行。


这就是为什么我在考虑 GetStdHandle 选项。如果我能确定标准输出不是控制台应用程序,但它具有 EXE 签名,那么可以安全地假定它是基于 GUI 的。我认为我需要做很多工作才能让它正常运行... - Alex
@Alex 当应用程序在运行时很容易 - 您可以尝试向应用程序发送Windows消息,如果它是GUI应用程序,则会响应。 在未运行的应用程序上检测这一点是棘手的。 - Luaan

3

正如之前多次提到的,您可以读取子系统字段。

    private PEFileKinds GetFileType(string inFilename)
    {
        using (var fs = new FileStream(inFilename, FileMode.Open, FileAccess.Read))
        {
            var buffer = new byte[4];
            fs.Seek(0x3C, SeekOrigin.Begin);
            fs.Read(buffer, 0, 4);
            var peoffset = BitConverter.ToUInt32(buffer, 0);
            fs.Seek(peoffset + 0x5C, SeekOrigin.Begin);
            fs.Read(buffer, 0, 1);
            if (buffer[0] == 3)
            {
                return PEFileKinds.ConsoleApplication;
            }
            else if (buffer[0] == 2)
            {
                return PEFileKinds.WindowApplication;
            }
            else
            {
                return PEFileKinds.Dll;
            }
        }
    }

感谢您的出色答案。在此方法的启发下,我发现对于任何已加载到当前“AppDomain”的**.exe.dll**映像,无需定位、打开或读取任何磁盘文件。您可以在任何已加载的图像文件的内存映射基地址(VA)上直接使用相同的解析代码和PE偏移量,只需使用程序集名称(而不是路径)调用GetModuleHandleW(...)即可轻松获得。有关更多详细信息,请参见我的这里的答案 - Glenn Slayden

0
使用GetReferencedAssemblies()获取所有引用的程序集,并查找system.windows.forms程序集。
    AssemblyName[] referencedAssemblies = assm.GetReferencedAssemblies();
    foreach (var assmName in referencedAssemblies)
    {
      if (assmName.Name.StartsWith("System.Windows"))
       //bingo
    }

0
检测GUI应用程序的基本方法是,GUI应用程序始终使用汇编System.Windows.*
bool isGui(Assembly exeAsm) {
   foreach (var asm in exeAsm.GetReferencedAssemblies()) {
       if (asm.FullName.Contains("System.Windows"))
          return true;
   }
   return false;
}

这将检测所有的.NET应用程序,无论是Windows窗体还是WPF。


你知道这个工具是否也能检测非.NET的WinForms应用程序吗?我希望这个工具能够处理所有GUI应用程序,包括那些用C++/Pas/VB 6编写的应用程序,这些应用程序不一定是.NET的。我还不确定如何处理非WinForms应用程序(例如使用其自己的UI库的Borland C++ Builder)。 - Alex
因此,如果该exe使用本机Windows函数(例如在消息循环中使用的TranslateMessage()GetMessage()),则需要查找这些函数。我认为您可以将exe作为字符串阅读(例如使用记事本),然后您会发现这些函数已列出。 - user586399

0
你可以检查文件的PE头中的.subsystem。如果你在ILDASM中打开文件并检查清单,你会看到这一点,如果它使用Windows GUI子系统:

enter image description here

我认为在Assembly类中没有检查此内容的方法,因此您可能需要检查文件本身。

另一种检查方法是遍历程序集中的类型,并查看它们是否从System.Windows.Forms.Form(Windows窗体)或System.Windows.Window(WPF)派生:

private static bool HasGui(Assembly a)
{
    return a.DefinedTypes
        .Any(t => typeof(System.Windows.Forms.Form).IsAssignableFrom(t) ||
                  typeof(System.Windows.Window).IsAssignableFrom(t));
}

请注意,您需要添加对 System.Windows.Forms.dllPresentationFramework.dll 的引用才能访问这些类型。
您可以使用 Assembly.LoadFrom(string) 加载程序集。我自己测试了这种方法,它似乎有点慢,因此也许您可以通过涉及 Parallel.ForEach 来加快速度。

作为一个注释,控制台应用程序仍然可以具有GUI,所以.subsystem检查不是完美的。我见过相当多的应用程序在控制台中用于调试输出和类似的东西,而主要应用程序则在winforms中。 - Luaan
@Luaan 不错的观点。关于第二个 HasGui 方法,我想到的另一件事是,一个类不需要从这些类派生来创建它们的实例。嗯,我猜你需要检查一个类是否创建了这些类的实例,才能确定它是否使用 GUI。 - cbr

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接