我该如何判断两个.NET DLL是否相同?

13
我有这个DLL的源代码,还有一个编译过的版本在某个地方。
如果我编译这个源代码,它将具有与已编译版本不同的日期。
我该如何判断它们是否实际上是相同的,并且仅仅是在不同的时间被编译?

字节数量?虽然我猜这不是最安全的方法,但我不确定。 - Bas
如果您想要检查某个dll文件中是否有特定的更改,可以使用.NET反编译器;除此之外,可以参考Kangkan的回复。 - David Hedlund
@Bas:这可能是最好的方法。 @David:.net反编译器会如何帮助? - CJ7
正如我所说,如果你有一个具体的变化想法。例如,如果你想确定某个特定功能是否已从阶段到生产环境推出,这就是你想知道两个DLL是否相同的原因,那么简单地检查该功能是否存在将是完全确定的最佳方法,而这正是.NET反编译器的用途。我完全意识到这可能不是你的情况,我只是提供这个工具,以防万一它是你的情况,而你不知道这个工具... - David Hedlund
5个回答

19

为了比较两个 .dll 文件,您可以使用 ildasm 或任何其他获取 IL 代码的工具。 我创建了一个示例,其中嵌入了 ildasm,以便您可以在每台机器上使用它。当我们反汇编装配时,我们检查执行装配文件夹中是否存在 ildasm.exe 文件,如果不存在,则从我们的 dll 文件中提取该文件到那里。 使用 ildasm 文件,我们获取 IL 代码并将其保存到临时文件中。 然后,我们需要删除以下三行:

MVID - 就像我之前写过的,这是一个唯一的 GUID,每次构建时都会生成

Image Base(图像基址告诉我们程序将由 Windows 加载器加载到内存的位置。) - 这也是每次构建都不同的

时间戳-运行 ildasm 时的日期和时间

因此,我们读取临时文件内容,使用正则表达式删除这些行,然后将文件内容保存到同一文件中。 这是反汇编器类:

using System;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Diagnostics;
using System.Text.RegularExpressions;

namespace FileHasher
{
    public class Disassembler
    {
        public static Regex regexMVID = new Regex("//\\s*MVID\\:\\s*\\{[a-zA-Z0-9\\-]+\\}", RegexOptions.Multiline | RegexOptions.Compiled);
        public static Regex regexImageBase = new Regex("//\\s*Image\\s+base\\:\\s0x[0-9A-Fa-f]*", RegexOptions.Multiline | RegexOptions.Compiled);
        public static Regex regexTimeStamp = new Regex("//\\s*Time-date\\s+stamp\\:\\s*0x[0-9A-Fa-f]*", RegexOptions.Multiline | RegexOptions.Compiled);

        private static readonly Lazy<Assembly> currentAssembly = new Lazy<Assembly>(() =>
        {
            return MethodBase.GetCurrentMethod().DeclaringType.Assembly;
        });

        private static readonly Lazy<string> executingAssemblyPath = new Lazy<string>(() =>
        {
            return Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
        });

        private static readonly Lazy<string> currentAssemblyFolder = new Lazy<string>(() =>
        {
            return Path.GetDirectoryName(currentAssembly.Value.Location);
        });

        private static readonly Lazy<string[]> arrResources = new Lazy<string[]>(() =>
        {
            return currentAssembly.Value.GetManifestResourceNames();
        });

        private const string ildasmArguments = "/all /text \"{0}\"";

        public static string ILDasmFileLocation
        {
            get
            {
                return Path.Combine(executingAssemblyPath.Value, "ildasm.exe");
            }
        }

        static Disassembler()
        {
            //extract the ildasm file to the executing assembly location
            ExtractFileToLocation("ildasm.exe", ILDasmFileLocation);
        }

        /// <summary>
        /// Saves the file from embedded resource to a given location.
        /// </summary>
        /// <param name="embeddedResourceName">Name of the embedded resource.</param>
        /// <param name="fileName">Name of the file.</param>
        protected static void SaveFileFromEmbeddedResource(string embeddedResourceName, string fileName)
        {
            if (File.Exists(fileName))
            {
                //the file already exists, we can add deletion here if we want to change the version of the 7zip
                return;
            }
            FileInfo fileInfoOutputFile = new FileInfo(fileName);

            using (FileStream streamToOutputFile = fileInfoOutputFile.OpenWrite())
            using (Stream streamToResourceFile = currentAssembly.Value.GetManifestResourceStream(embeddedResourceName))
            {
                const int size = 4096;
                byte[] bytes = new byte[4096];
                int numBytes;
                while ((numBytes = streamToResourceFile.Read(bytes, 0, size)) > 0)
                {
                    streamToOutputFile.Write(bytes, 0, numBytes);
                }

                streamToOutputFile.Close();
                streamToResourceFile.Close();
            }
        }

        /// <summary>
        /// Searches the embedded resource and extracts it to the given location.
        /// </summary>
        /// <param name="fileNameInDll">The file name in DLL.</param>
        /// <param name="outFileName">Name of the out file.</param>
        protected static void ExtractFileToLocation(string fileNameInDll, string outFileName)
        {
            string resourcePath = arrResources.Value.Where(resource => resource.EndsWith(fileNameInDll, StringComparison.InvariantCultureIgnoreCase)).FirstOrDefault();
            if (resourcePath == null)
            {
                throw new Exception(string.Format("Cannot find {0} in the embedded resources of {1}", fileNameInDll, currentAssembly.Value.FullName));
            }
            SaveFileFromEmbeddedResource(resourcePath, outFileName);
        }

        public static string GetDisassembledFile(string assemblyFilePath)
        {
            if (!File.Exists(assemblyFilePath))
            {
                throw new InvalidOperationException(string.Format("The file {0} does not exist!", assemblyFilePath));
            }

            string tempFileName = Path.GetTempFileName();
            var startInfo = new ProcessStartInfo(ILDasmFileLocation, string.Format(ildasmArguments, assemblyFilePath));
            startInfo.WindowStyle = ProcessWindowStyle.Hidden;
            startInfo.CreateNoWindow = true;
            startInfo.UseShellExecute = false;
            startInfo.RedirectStandardOutput = true;

            using (var process = System.Diagnostics.Process.Start(startInfo))
            {
                string output = process.StandardOutput.ReadToEnd();
                process.WaitForExit();

                if (process.ExitCode > 0)
                {
                    throw new InvalidOperationException(
                        string.Format("Generating IL code for file {0} failed with exit code - {1}. Log: {2}",
                        assemblyFilePath, process.ExitCode, output));
                }

                File.WriteAllText(tempFileName, output);
            }

            RemoveUnnededRows(tempFileName);
            return tempFileName;
        }

        private static void RemoveUnnededRows(string fileName)
        {
            string fileContent = File.ReadAllText(fileName);
            //remove MVID
            fileContent = regexMVID.Replace(fileContent, string.Empty);
            //remove Image Base
            fileContent = regexImageBase.Replace(fileContent, string.Empty);
            //remove Time Stamp
            fileContent = regexTimeStamp.Replace(fileContent, string.Empty);
            File.WriteAllText(fileName, fileContent);
        }

        public static string DisassembleFile(string assemblyFilePath)
        {
            string disassembledFile = GetDisassembledFile(assemblyFilePath);
            try
            {
                return File.ReadAllText(disassembledFile);
            }
            finally
            {
                if (File.Exists(disassembledFile))
                {
                    File.Delete(disassembledFile);
                }
            }
        }
    }
}

现在,您可以比较这两个IL代码的内容。另一个选项是生成这些文件的哈希码并进行比较。这里有一个HashCalculator类: using System; using System.IO; using System.Reflection;

namespace FileHasher
{
    public class HashCalculator
    {
        public string FileName { get; private set; }

        public HashCalculator(string fileName)
        {
            this.FileName = fileName;
        }

        public string CalculateFileHash()
        {
            if (Path.GetExtension(this.FileName).Equals(".dll", System.StringComparison.InvariantCultureIgnoreCase)
                || Path.GetExtension(this.FileName).Equals(".exe", System.StringComparison.InvariantCultureIgnoreCase))
            {
                return GetAssemblyFileHash();
            }
            else
            {
                return GetFileHash();
            }
        }

        private string GetFileHash()
        {
            return CalculateHashFromStream(File.OpenRead(this.FileName));
        }

        private string GetAssemblyFileHash()
        {
            string tempFileName = null;
            try
            {
                //try to open the assembly to check if this is a .NET one
                var assembly = Assembly.LoadFile(this.FileName);
                tempFileName = Disassembler.GetDisassembledFile(this.FileName);
                return CalculateHashFromStream(File.OpenRead(tempFileName));
            }
            catch(BadImageFormatException)
            {
                return GetFileHash();
            }
            finally
            {
                if (File.Exists(tempFileName))
                {
                    File.Delete(tempFileName);
                }
            }
        }

        private string CalculateHashFromStream(Stream stream)
        {
            using (var readerSource = new System.IO.BufferedStream(stream, 1200000))
            {
                using (var md51 = new System.Security.Cryptography.MD5CryptoServiceProvider())
                {
                    md51.ComputeHash(readerSource);
                    return Convert.ToBase64String(md51.Hash);
                }
            }
        }
    }
}

你可以在我的博客上找到完整的应用程序源代码,链接如下 - 在程序中自动比较两个dll文件


你的所有帖子都是指向你的博客链接。 - Andrew Barber
1
不确定为什么这个被投票否决了 - 这是这里唯一有效的答案。 - Q 4
当我使用它时,我发现相同的dll仍然具有不同的guid。忽略所有guid的更改是安全的吗? - Daniel
@vasil-trifonov 时间戳是指可执行文件/动态链接库构建的时间,而不是我们运行Idasm.exe的时间,对吗? - Ram
@Ram 我不太确定,我现在记不起来了,你可能是对的,但是当我们比较文件时,我们不需要那个时间。 - Vasil Trifonov

3

无论是NDepend还是Reflector的插件都可以让您比较程序集。


1
你可以使用.NET Reflector来反汇编dll并将其与您最后一次代码更改进行比较,以查看它们是否相同。如果它们是相同的,那么您就知道它们基于相同的代码。

0

基本的比较是 DLL 的版本和大小。此外,您还可以检查任何文件是否具有修改日期超出已编译 DLL 的日期。


看我的问题。如果我编译源代码,它将与已编译版本有不同的日期。 - CJ7
@Craig:如果你仔细阅读,我提到源代码文件的最后修改日期不应超过原始dll的日期。当然,如果代码已经保存并进行了任何非功能性更改,那么修改日期也会更改。可以确定的是,如果编译新的dll,它将具有不同的日期。这很琐碎。 - Kangkan
好的,我现在明白你的观点了。是的,查看源文件的修改日期是个不错的主意。 - CJ7

-1

以二进制模式比较文件。从命令行运行以下命令:

fc /b file1.dll file2.dll

这将让您知道文件是否相同,但除非它们在完全相同的条件下编译,否则它们可能不会相同,但由于您拥有源代码,这是可能的。


1
这是不正确的,因为编译器总是会将时间戳和随机MVID插入到二进制文件中。而且基地址很可能会改变。 - Ramon de Klein

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