在实际工作中有时会碰到需要修改某个dll里某个类的方法但是又没有该dll的所有工程项目的情况,而此时只要手头有目标类的相关代码并且能新编译出一个仅包含目标类的dll,就能通过在2个dll上操作把新的类替换掉原先那个类,而不需重新编译整个工程。当然更进一步,还可以做到只替换某个类的某个方法,或是只修改某个方法的某一行代码。
现有一些工具比如reflexil已经可以做到这些,但是操作起来稍微麻烦了些,这里我的目标是做一个简单的工具,输入一个命令就能把源dll的一个类合并到目标dll的同名类之中。
要完成这个目标,首先我是用monocecil来操作dll,实际用了之后发现monocecil对重建的dll限制比较多,后来就改用了dnlib,这个库比monocecil在操作dll方面要强不少,但缺点是文档比较少,实际使用中碰到不少问题都要引入dnlib源码进行调试才能解决。
要把一个dll内的类(以下叫源类)与另一个dll的同名类(以下叫目标类)合并需要解决2个问题:一个是要把源类下不同名的字段、方法、属性、事件、内部类(以下统一称成员)这些复制到目标类,目标类同名成员都可以保留;第二个就是要确保复制过去的新成员以及原先成员的所有关联类型都要调整成能正确的引用自目标类里的相关类型。只要解决了这2个问题就能得到一个合并后的打过补丁的dll。
再解决具体问题前,还要讲一下dnlib对dll的结构组织,dnlib用TypeDef来代表一个.net里的类,所以要对类的组织结构进行调整需要对TypeDef进行操作从而解决第一个问题,而dnlib又用TypeSig来真正确定类型,所以要对TypeSig进行一定的操作来解决第二个引用问题。
首先解决第一个问题:
private static void DupType(TypeDef src, TypeDef dest)
{
dest.Attributes = src.Attributes;
dest.ClassLayout = src.ClassLayout;
dest.ClassSize = src.ClassSize;
dest.Visibility = src.Visibility;
dest.BaseType = src.BaseType;
for (int i = 0; i < dest.NestedTypes.Count; ++i)
{
var dnt = dest.NestedTypes[i];
var snt = src.NestedTypes.FirstOrDefault(nt => nt.FullName == dnt.FullName);
if (snt == null)
continue;
DupType(snt, dnt);
}
var ntArray = src.NestedTypes.Where(snt => !dest.NestedTypes.Any(dnt => dnt.FullName == snt.FullName)).ToArray();
foreach (var nt in ntArray)
{
nt.DeclaringType = dest;
}
var caArray = src.CustomAttributes.Where(snt => !dest.CustomAttributes.Any(dnt => dnt.TypeFullName == snt.TypeFullName)).ToArray();
foreach (var attr in caArray)
{
dest.CustomAttributes.Add(attr);
}
var iArray = src.Interfaces.Where(snt => !dest.Interfaces.Any(dnt => dnt.Interface.FullName == snt.Interface.FullName)).ToArray();
foreach (var i in iArray)
{
dest.Interfaces.Add(i);
}
var fdArray = src.Fields.Where(snt => !dest.Fields.Any(dnt => dnt.FullName == snt.FullName)).ToArray();
foreach (var fd in fdArray)
{
fd.DeclaringType = dest;
}
for (int i = 0; i < dest.Methods.Count; ++i)
{
var dmd = dest.Methods[i];
var smd = src.Methods.FirstOrDefault(md => md.FullName == dmd.FullName);
if (smd == null)
continue;
dmd.ReturnType = smd.ReturnType;
dmd.Body = smd.Body;
dmd.MethodSig.Params.Clear();
for (int j = 0; j < smd.MethodSig.Params.Count; ++j)
{
dmd.MethodSig.Params.Add(smd.MethodSig.Params[j]);
}
}
var mdArray = src.Methods.Where(snt => !dest.Methods.Any(dnt => dnt.FullName == s