背景:客户反馈系统出现一个Bug,花了1天时间搞定,但是准备下发程序的时候发现我编译后的dll比目前正在使用的DLL要小70K ,正常的处理是我这边修正好了程序,现场的实施同事做好测试即可下发使用,这次客户发飙了,还直接闹到客户领导那里去了。无奈,要到客户现场去了,必须要找到原因,给客户一个解释。本文就当时这几天处理这个问题的一次回顾和总结吧。

该情况一到手上,我初步判断可能的原因是:svn上的最新的源代码并非对应客户正在使用的Dll,有可能有同事在自己机器上(不受版本控制)对程序进行了修改且下发到科室了。因此首先找到当时负责维护改项目的几个同事,找到了最后离场的同事,询问是否有未提交的变更,但得到的答案是否定的。但我保留对这个回复的怀疑权。

看来只有找到两个程序集之间的差异才能最终知道是否有变更,变更了哪里,首先想到使用Reflector反编译,把反编译的代码和现在svn上的源码进行比对,但这真是一个苦逼且低效率的活,此Dll有8M,8M的代码各位看官可以想象得有多少行。。。。找了几个可能存在的变更类,并未发现任何蛛丝马迹,所看到的的代码均一致。想想这样对代码,把眼睛对瞎了都很难找到有代码变更的位置,因此做如下思考:按理来讲,70k的代码,至少是有增加很多类型,类、事件、委托、字段、方法、接口等,因此考虑自己写一个小工具把客户目前使用的Dll中的类型全部导出,只导出类型的定义即可;然后再把现在的svn上的源码编译成Dll,同样使用该工具导出全部类型,然后通过文本比对工具对比出两个程序集之间类型定义的差异。(嗯,看来这个方法不错,信心满满了)。具体操作:

 

选择程序集和存放的路径之后,点击“获取程序集类型”按钮,如下:

 private void btnGetTypes_Click(object sender, EventArgs e)
        {
            this.lblMsg.Text = "正在导出,请稍后...";
            this.Refresh();
         this.btnGetTypes.Enabled = false;
            try
            {
                string dllPath = txtSelectedAssembly.Text;
                string savePath = txtAssemblyPath.Text;
                if (Directory.Exists(savePath))
                {
                    Directory.Delete(savePath, true);
                }
                Directory.CreateDirectory(savePath);
                getMember(dllPath, savePath);
            }
            catch
            {
                MessageBox.Show("转换出错:" + txtSelectedAssembly.Text);
            }
            this.lblMsg.Text = "转换结束。";
        }

getMember(dllPath, savePath)方法如下:

public void getMember(string dllPath, string savePath)
        {
            string assemblyStr = dllPath;
            Assembly assembly = Assembly.LoadFrom(assemblyStr);
            System.IO.FileStream[] fileStream = assembly.GetFiles();
            Type[] type = assembly.GetExportedTypes();
            //BindingFlags bf =  BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public;
            foreach (Type t in type)
            {
                if (t.IsClass)
                {
                    string filePath = savePath + "\\" + t.FullName + ".txt";
                    if (File.Exists(filePath))
                    {
                        File.Delete(filePath);
                    }
                    FileStream f = File.Create(filePath);
                    f.Dispose();//释放刚创建的txt文档资源
                    string str = string.Empty;
                    str += t.ToString() + "\r\n";
                    foreach (MemberInfo mi in t.GetMembers())
                    {
                        string miType = string.Empty;
                        if (mi is FieldInfo)
                        {
                            miType = "  FieldInfo";
                        }
                        else if (mi is MethodInfo)
                        {
                            miType = "  MethodInfo";
                        }
                        else if (mi is EventInfo)
                        {
                            miType = "  EventInfo";
                        }
                        else if (mi is ConstructorInfo)
                        {
                            miType = "  ConstructorInfo";
                        }
                        else if (mi is PropertyInfo)
                        {
                            miType = "  PropertyInfo";
                        }
                        str += mi.ToString() + "  " + miType + "\r\n";
                    }
                    WriteMemberInfo(str, filePath);
                }

            }
        }

WriteMemberInfo(str,filePath)方法是将程序集中的信息写入记事本,如下:

public void WriteMemberInfo(string strMsg, string filePath)
        {
            StreamWriter sw = null;
            try
            {
                sw = new StreamWriter(filePath, true, Encoding.GetEncoding("gb2312"));
                string ifo = string.Empty;//DateTime.Now.ToString() + ":\r\n";
                ifo += strMsg;
                sw.WriteLine(ifo);
                sw.Close();
            }
            catch (Exception ex)
            {
                if (sw != null)
                {
                    sw.Close();
                    sw.Dispose();
                }
            }
        }

 至此,程序集类型导出的就完成了,很简单的一个功能,看看导出的效果吧,为了避嫌,就不导公司的Dll了,咱就导System.Data.dll吧:

 找一个类看看具体导出的内容:

字段、属性、方法、事件、构造函数都已经导出。

于是赶紧将两个Dll各自执行一遍,使用比对工具Beyond Compare进行批量比对,

 

 

整个程序集有150个类,内容比较我们使用二进制比较。最终发现只有5个类是不一致的:以为这些终找到元凶了,双击进去一看,让人大跌眼镜:其他几个类基本也是这种情况,这尼玛哪里是什么不同,就一个在上一行,一个在下一行。我只能再次陷入沉思了。

这个结果证明:两个程序集之间在类型上不存在不一致(这里面没有考虑接口,由于公司的接口基本不动,因此此处就没去考虑),也就是说两个程序集之间差异的这70k的大小并没有增加类,没有增加方法,没有定义字段,没有写事件,没有写委托(由于公司的事件和委托基本不是和类同级,而是包含在类中,因此我这里只是导出类中的成员,特此说明,以免遭遇强烈拍砖)。难道这70k的代码全部写在了方法里面???这该动了多少方法啊?!心里有点汗颜了。

没办法,还是只能老老实实的去对代码了,但是这种事情叫人工去做,我只想说这真的会死人。

皱眉之际,还是想到老朋友好工具Reflector了,Reflector后来多了一个插件,叫做Reflector.FileDisassembler,可以将程序集中的类导出为一个源代码文件,果断尝试,如下:

 

导出的类文件如下:

在VS中查看,如下:

到这里,不管问题能不能解决,我不得不赞叹这个利器,由衷的敬意油然而生,中国人什么时候也能写出这种无与伦比的工具软件呢?哎,不管这些了,赶紧解决手里的事情才最重要,找了几个类,赶紧对比一下,结果让我大吃一惊,见图:

 

经过多个文件的比较,很显然,左边的代码是被动过对,或者说是被优化过的,如:

 if ((this.treeView1.SelectedNode == null) || (this.treeView1.SelectedNode == this.treeView1.Nodes[0]))
                return null;
 return this.treeView1.SelectedNode.FullPath.Substring(5);

被变成了:

  if ((this.treeView1.SelectedNode != null) && (this.treeView1.SelectedNode != this.treeView1.Nodes[0]))
                    return this.treeView1.SelectedNode.FullPath.Substring(5);
  return null;

再如:

DataSet nodePath = Function.IntegrateEPR.GetNodePath();

被变成:

DataSet nodePath = Function.get_IntegrateEPR().GetNodePath();

属性全部变成方法了, 明显的,这都不是人干的,是编译器做了转换了。我恍然大悟,原来少的这70k就是这样少的。但这又是为什么呢?难道是编译环境不一样造成?带着这个怀疑,我用win7xp系统分别编译同一个项目的代码,发现确实存在差异,win7下编译的代码更大,xp环境下编译的代码要小。为什么xpwin7环境编译的dll大小不一致,请各位发表下自己的观点和看法,多谢!

posted on 2013-05-15 22:27  意识与存在  阅读(342)  评论(0编辑  收藏  举报