金蝶云星空供应链领域销售模块下集成了快递100和快递管家接口对接,研发平台由于网络安全限制,无法访问快递100和快递管家接口,因此无法利用Visual Studio调试接口代码,一般是将元数据及程序集构建好后将打包文件发布成外网临时补丁,再到有网络权限的外网服务器(这种测试服务器仅仅只是一个能访问公网且安装了IIS的服务器,不会安装Visual Studio)安装补丁才能测试,这种未经过调试就发布到IIS再测试接口对接情况就容易导致很多问题,且测试发现问题也不好定位。因为没有Visual Studio调试清晰。这个时候dnSpy就作用大了,一般我也是用它来代替VS调试代码的,本文将介绍dnSpy这款优秀的反编译工具如何调试IIS,并根据需要输出一些变量值来确定快递100或快递管家相关接口是否正常,以下以管家订单导入接口为例。
首先,查询IIS进程ID,CMD进入C:\windows\system32\inetsrv目录,使用命令appcmd list wp 输出IIS所有站点及站点下挂载的应用程序,其中的应用程序K3Cloud(使用的应用程序池为K3Cloud)对应的ID'17852'就是dnSpy要附加的进程ID, 如下图1-1所示:
图1-1
打开dnSpy,附加进程‘17852'’,如下图1-2所示:
图1-2
针对上面的查询哪个进程是金蝶云星空系统的,其实也可以简单地从命令行参数中查到,这样就不用命令行 appcmd list wp 查询了,直接查询w3wp.exe进程且命令行参数中有K3Cloud就是要附加调试的进程,如下图1-1.2所示:
图1-1.2
找到对应的快递100相关组件,快递100和快递管家对接的相关功能是通过动态表单‘获取电子面单’实现的,其对应的表单插件在程序集文件'Kingdee.K3.SCM.Sal.Business.PlugIn.dll'中,通过菜单‘调试’->‘窗口’->‘模块’可以加载或搜索到此程序集(如果在模块搜索不到此程序集,比如重启IIS后,需要通过登录金蝶云星空系统打开一下‘获取电子面单’功能菜单,此时程序集即可搜索到,这可能是由于IIS基于性能原因或用到才加载的原因,没有将程序集加载到类似以下目录C:\Windows\Microsoft.NET\Framework64\v4.0.30319\Temporary ASP.NET Files\中,也有可能与星空框架有关,它是插件模式,只有用到了才加载该插件),如下图1-3所示:
图1-3
定位到类‘Kingdee.K3.SCM.Sal.Business.PlugIn.GetKuaidiBillEdit, Kingdee.K3.SCM.Sal.Business.PlugIn’中的方法‘OrderImport()’,此方法是’获取电子面单‘中的‘管家订单导入‘ 的核心方法,在最后语句打上断点,如图1-4所示:
图1-4
操作星空系统上的‘获取电子面单’,选择一个单据,然后点订单导入,此时断点进入,图中可以看到一些变量的值,如下图1-5所示:
图1-5
但是大部分情况下,调试IIS会发现变量输出不了,而且有些代码点中的断点根本打不了或者调试的执行顺序容易乱跳,特别是在有抛异常的情况下,这是因为星空发版后的程序集是Release模式构建的,代码优化后变量输出就是一个问题,程序抛异常后也没有像Visual Studio调试那样清晰看到异常详情,上图1-5中能够输出快递管家订单导入接口的数据包是因为我用源代码在Debug模式重新编译且替换了最开始在IIS里面挂载的Kingdee.K3.SCM.Sal.Business.PlugIn.dll程序集(这个源代码只有金蝶总部有,客户二开的话只能看到反编译的代码,且我DEBUG模式重新编译生成的组件版本为1.0.0.0,这和IIS里面的程序集版本是不同的),然后重启了IIS。如果此时想要在Release模式生成的程序集中调试时显示被优化后的变量值,则可在对应的组件目录中添加同名的后缀为ini的文件,.ini文件内容为:
[.NET Framework Debugging Control]
GenerateTrackingInfo=1
AllowOptimize=0
要注意的是,如果要调试每个加载进Temporary ASP.NET Files 目录下的应用程序集dll文件,则对应的文件都要添加同名的 .ini文件,客户在复制上面的.ini文件内容时要手动删除空格及换行再重新输入空格和换行,这是因为HTML页面会转义空格的原因,目录结构和文本内容如下图1-5.1所示:
图1-5.1
但要注意这个.ini文件不是放在金蝶云星空安装目录 X:\Program Files (x86)\Kingdee\K3Cloud\WebSite\bin 下,而是放在IIS加载应用程序集后目录下,目录一般为C:\Windows\Microsoft.NET\Framework64\v4.0.30319\Temporary ASP.NET Files\k3cloud\7e965141\f7b8376d\assembly\dl3\ba31cdbb\00df3b7e_628ad701,其中k3cloud后面的目录名可能与我的目录是不一样的,每次重启IIS应该也是不一样的,具体目录可通过dnSpy附加进程后双击模块里的程序集显示出来,如下图1-5.2所示:
图1-5.2
下图1-5.3所示为加上.ini文件后Release版本原封不动的组件调试图,其版本号还是星空通版的版本号,它不是像图1-5那样用DEBUG模式编译后现替换组件。可以看到在.ini文件帮忙下,它可以显示出BOS平台底层的DynamicObject数据包对象里的不同字段值,这对调试IIS是非常有作用的。
图1-5.3
如下图1-6就是没有加上.ini文件Release解决方案配置生成的BOS组件调试图(本质上就是云星空系统寄宿在IIS下的最原始版本组件),可以看到方法中的很多变量输出不了。
图1-6
假设当前程序集Kingdee.K3.SCM.Sal.Business.PlugIn.dll是Release模式生成的且没有添加同名.ini文件,那么图1-5中的断点很可能是输出不了调试管家订单导入接口的数据包(方法中的postData), 如果接口有问题,如何排查,一般是通过修改程序集并替换到星空安装目录,然后重启IIS,再进行调试。这里就不那么复杂,直接演示一下如何修改程序集,将变量值输入出到D盘指定文件夹下。首先关闭调试,然后用dnSpy打开星空安装目录(一般是在X:\Program Files (x86)\Kingdee\K3Cloud\WebSite\bin)下的文件'Kingdee.K3.SCM.Sal.Business.PlugIn.dll'(注意:这里不能直接使用dnSpy调试模式下打开的文件Kingdee.K3.SCM.Sal.Business.PlugIn.dll,调试模式下的文件会被加载到C:\Windows\Microsoft.NET\Framework64\v4.0.30319\Temporary ASP.NET Files\XX\XX 临时目录下,临时目录会缺少很多依赖的程序集),两个目录是不一样的,如下图1-7所示:
图1-7
dnSpy打开星空系统安装目录下的程序集文件后,定位到类‘GetKuaidiBillEdit’中的方法‘OrderImport’,在方法内右键,在弹出的上下文菜单中点击'编辑方法(C#)...',此时会弹出编辑代码的弹出框,在指定位置添加如下代码段'System.IO.File.AppendAllText(@"D:\kuaidiguanjia.txt",postData);',然后点编译,如下图1-8所示:
图1-8
编译成功后可以看到代码修改成功,然后点击菜单‘文件’->'保存模块(M)...',在弹出的保存框中默认会指定要保存到星空系统安装目录下,点击确认,重启IIS,再依照图1-1,图1-2,图1-3的方式调试修改后的程序集。如下图1-9所示:
图1-9
调试后可以看到D盘多了一个文件'kuaidiguanjia.txt',里面包含了管家接口数据包,一般我也是分析这个数据包的格式来分析快递管家订单导入的接口问题,当然也可以用类似的方法调试管家订单导入成功后返回的数据包格式,由于管家回填的数据包在另一个组件中,这里就不演示了,如下图1-10所示就是调用快递管家订单导入的数据包格式:
图1-10
dnSpy局限性:上面的图1-8编辑代码发现有一个局限,就是方法中最好不要包含有自定义的复杂对象(比如自定义集合类,派生至泛型基类Collection<T>)的foreach循环,因为反编辑工具会把foreach语句编译成方法调用,且其中遍历的对象会莫名地赋值给另一个由编译工具生成的类的对象,而这类的命名不符合C#标识符命名规则,这个时候就修改此方法就编译不通过了,因此要避免用dnSpy编辑这些方法,如果源代码是用for语句替换foreach语句就不会有问题,如下图1-11所示方法FillKuaidiInfo():
图1-11
另外一个修改程序集代码的局限性就是BOS底层某些特定的类会有不合法的变量名(它本身在源代码里面是不存在的,可能是通过代码混淆或工具生成的),这个时候也要避免编辑这些类,如下图1-12所示的类 DataManagerImplement 中的委托字段 \u009F,这个变量名的类型应该是一个工具自动创建的委托定义:
namespace SmartAssembly.Delegates
{
// Token: 0x02000054 RID: 84
// (Invoke) Token: 0x0600029E RID: 670
public delegate string GetString(int i);
}
这个时候如果编辑 DataManagerImplement类,就会因为类中字段 \u009F 编译不通过报错。
图1-12
备注:有一些论坛说这个dnSpy工具要先添加系统环境变量‘COMPLUS_ZapDisable = 1 ’才能调试,经过测试这个好像可以不用,如果遇到问题再添加环境变更也不迟,根据相关论坛猜测这个COMPLUS_ZapDisable 变量和另一个环境变量COMPlus_ReadyToRun
是用于编译器优化选项的,不确定这两个参数能否确保上图1-6中的调试时显示变量值,有兴趣可以在环境变量中修改再调试看看变量输出情况,另外,附件中的dnSpy为V5版本的程序,此dnSpy程序依赖于.NET Framework 4.7(由于论坛附件大小限制,附件中没有.NET Framework 4.7,如果dnSpy打开后提示依赖.Net4.7,可自行网上下载安装)。
dnSpy.zip(23.16MB)