使用VS在Debug模式下生成Dll工程时都会生成对应的pdb调试文件,pdb调试文件中记录了源文件路径,行号等信息。而Unity使用mono的mdb格式的调试文件,将dll和对应的pdb调试文件拷到Assets目录下,Unity会生成对应的mdb调试文件。也可以在Unity安装目录中找到pdb2mdb程序,可以将vs生成的pdb文件转换成mdb文件,不过在windows环境下或者高版本的vs生成的pdb文件,使用该工具转换会有很多问题,可以参考下面链接解决
http://www.robinryf.com/blog/2016/04/10/convert-pdb-to-mdb.html
现在我们有了Dll程序集文件和对应的mdb调试文件,如果获取到了一个定义在该程序集中的类型,如何获取到定义该类型所在的源文件呢
查看Mono.Cecil提供的API可以发现,在函数定义类MethodDefinition中的函数体属性MethodBody中的每条IL代码结构Instruction中都有个SequencePoint属性,SequencePoint类中的Document属性中的Url属性记录了源文件的路径
实现步骤
1. 读取Dll和对应的调试文件
设要读取的Dll文件路径为string dllPath;
var readPar = new ReaderParameters { ReadSymbols = true };
AssemblyDefinition ad = AssemblyDefinition.ReadAssembly(dllPath, readPar);
这里调用的上几个示例中读取程序集函数的重载版本,加了一个是否读取调试文件的参数,该参数为true时,读取程序集时会在程序集所在的文件夹下查找同名的.mdb或.pdb调试文件并加载
2. 读取指定类型
设要读取的类型为Type type;
TypeDefinition tr = ad.MainModule.GetType(type.FullName);
3. 获取源文件路径
设源文件路径string scriptPath = "";
foreach (var method in tr.Methods)
{
if (method.HasBody)
{
var body = method.Body;
foreach (var il in body.Instructions)
{
if (il.SequencePoint != null)
{
scriptPath = il.SequencePoint.Document.Url;
break;
}
}
}
if (!string.IsNullOrEmpty(scriptPath))
{
break;
}
}
该方法在类型中没有函数和属性时无法获取到对应的源文件路径
可以将上面的代码封装成函数
public static string GetDllScriptPath(string dllPath,Type type)
{···}
使用示例
public class DllScriptPathTest
{
[MenuItem("CONTEXT/MonoBehaviour/OpenScript", false, 606)]
static void OpenScript(MenuCommand command)
{
MonoScript script = MonoScript.FromMonoBehaviour(command.context as MonoBehaviour);
string path = AssetDatabase.GetAssetPath(script);
if (path.ToLower().EndsWith(".cs"))
{
AssetDatabase.OpenAsset(script);
return;
}
Type t = command.context.GetType();
string scriptPath = GetDllScriptPath(path, t);
if(File.Exists(scriptPath))
{
Debug.Log(scriptPath);
}
}
[OnOpenAssetAttribute]
static bool OnOpenScript(int instanceID, int line)
{
Object asset = EditorUtility.InstanceIDToObject(instanceID);
if (asset == null || asset.GetType() != typeof(MonoScript))
{
return false;
}
Type type = (asset as MonoScript).GetClass();
string assetPath = AssetDatabase.GetAssetPath(instanceID);
string scriptPath = GetDllScriptPath(assetPath, type);
if (File.Exists(scriptPath))
{
Debug.Log(scriptPath);
return true;
}
return false;
}
}
该示例在双击Dll脚本时或在Dll脚本组件右上方点击菜单“Open Script”时,会将Dll脚本对应的cs源文件路径打印到了日志窗口上
其中带有MenuItem("CONTEXT/MonoBehaviour/OpenScript", false, 606)特性的静态函数给所有继承自MonoBehaviour的组件添加了菜单Open Script,参见
https://docs.unity3d.com/ScriptReference/MenuItem.html
其中带有OnOpenAssetAttribute特性的静态函数在Unity中打开文件时被Unity调用,参见
https://docs.unity3d.com/ScriptReference/Callbacks.OnOpenAssetAttribute.html