cad.net AOP注入代码
AOP
几个包说明:
Mono.Cecil 它不能动态注入,只可以选择一个dll然后注入.
Fody 能反射自己的特性来进行注入
Harmony2 能反射人家并且动态注入,
但是我发现被注入的函数不能断点了,而注入的头尾两个函数可以,真奇怪...估计是注入行为已经破坏了调试文件,但是它没有把调试文件也顺带处理...
另见此文
若你的dll有签名的话,注意实践起来的问题.
Mono.Cecil
引用nuget
<ItemGroup Condition="'$(TargetFramework)' != 'net35'">
<PackageReference Include="Mono.Cecil" Version="0.11.4" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net35'">
<PackageReference Include="Mono.Cecil" Version="0.9.4" />
</ItemGroup>
代码
using Autodesk.AutoCAD.Runtime;
using Acap = Autodesk.AutoCAD.ApplicationServices.Application;
using System;
using System.IO;
using System.Reflection;
/*
* 警告: 此处测试若同时使用 nuget:Mono.Cecil 和 0Harmony.dll 将导致冲突
* 此处为 nuget:Mono.Cecil 的测试
*/
using AssemblyDef = Mono.Cecil.AssemblyDefinition;
using OpCodes = Mono.Cecil.Cil.OpCodes;//0Harmony.dll 把这个类设置为内部的!
/*
* 旧例子
* https://www.cnblogs.com/RicCC/archive/2010/03/21/mono-cecil.html
* https://www.cnblogs.com/whitewolf/archive/2011/07/28/2119969.html
* 新例子:
* https://blog.csdn.net/ZslLoveMiwa/article/details/82192522
* https://blog.csdn.net/lee576/article/details/38780889
* AssemblyFactory 0.6版本被移除,改用 AssemblyDefinition
* CilWorker 类被移除,改用 GetILProcessor()
* Import 过时,改用 ImportReference
*
*/
namespace JoinBox
{
public class TestMonoClass
{
[CommandMethod("TestMono")]
public void TestMono()
{
var dm = Acap.DocumentManager;
var doc = dm.MdiActiveDocument;
var ed = doc.Editor;
ed.WriteMessage("Hello, World!");
}
}
public class Program
{
[JoinBoxInitialize()]
public static void Main(string[] args)
{
string fullName = AutoGo.ConfigPathAll;
if (fullName == null || !File.Exists(fullName))
{
Console.WriteLine("没有此路径文件:" + fullName);
return;
}
//Mono.Cecil 只能读取非占用文件来改函数,所以用改用 Lib.Harmony
//没有文件流就不可以保存
var file = new FileStream(fullName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
var assembly = AssemblyDef.ReadAssembly(file);
if (assembly == null)
return;
//构造Console方法
var tt = typeof(System.Diagnostics.Debug).GetMethod("WriteLine", new Type[] { typeof(string) });
foreach (var type in assembly.MainModule.Types)
{
if (type.Name == "<Module>")
continue;
foreach (var method in type.Methods)
{
if (method.Name != nameof(TestMonoClass.TestMono))//函数名
continue;
var worker = method.Body.GetILProcessor();
//开头插入
var ins = method.Body.Instructions[0];
//定义字符串变量
worker.InsertBefore(ins, worker.Create(OpCodes.Ldstr,
"Method start…"));
//定义方法
worker.InsertBefore(ins, worker.Create(OpCodes.Call,
Helper.ImRef(assembly.MainModule, tt)));
//尾巴插入
ins = method.Body.Instructions[method.Body.Instructions.Count - 1];
//定义字符串变量
worker.InsertBefore(ins, worker.Create(OpCodes.Ldstr,
"Method finish…"));
//定义方法
worker.InsertBefore(ins, worker.Create(OpCodes.Call,
Helper.ImRef(assembly.MainModule, tt)));
break;
}
}
assembly.Write(fullName);
}
}
public static class Helper
{
public static Mono.Cecil.MethodReference ImRef(Mono.Cecil.ModuleDefinition module, MethodInfo methodInfo)
{
#if NET35
return module.Import(methodInfo);
#else
return module.ImportReference(methodInfo);
#endif
}
}
}
Fody
https://blog.csdn.net/qq_28448587/article/details/120067843
https://www.cnblogs.com/cqgis/p/6360231.html
FodyWeavers.xml 编译的时候自动生成,所以不需要准备
引用nuget
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework>
<Platforms>AnyCPU;x64</Platforms>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Costura.Fody" Version="5.8.0-alpha0098" PrivateAssets="All" />
<PackageReference Include="MethodDecorator.Fody" Version="1.1.1" PrivateAssets="All" />
<!--WPF的属性绑定-->
<PackageReference Include="PropertyChanged.Fody" Version="3.4.0" PrivateAssets="All" />
</ItemGroup>
</Project>
属性注入(WPF用得多)
实现了之后,Fody它会自动找INotifyPropertyChanged
接口,然后把这个类下的属性上注入到通知接口
代码
public class Person : INotifyPropertyChanged
{
//实现事件来进行通知
public event PropertyChangedEventHandler PropertyChanged;
//不触发事件通知
[PropertyChanged.DoNotNotify]
public string GivenNames { get; set; }
//触发事件通知
[PropertyChanged.DependsOn("GivenName", "FamilyName")] //依赖属性
public string FamilyName { get; set; }
public string FullName => $"{GivenNames} {FamilyName}";
}
public static void Main(string[] args)
{
var person = new Person();
person.PropertyChanged += (sender, e) => {
//没有新旧值拦截(修改前,修改后) 有要求就改用:https://github.com/canton7/PropertyChanged.SourceGenerator
System.Console.WriteLine(sender);//来源 Person
System.Console.WriteLine(e.PropertyName);//触发通知的属性 FamilyName
};
person.FamilyName = "vvvvv";
}
缺陷
没有新旧值拦截(修改前,修改后)
改用: https://github.com/canton7/PropertyChanged.SourceGenerator
函数注入
代码
注册类 AssemblyInfo.cs
//新建一个 AssemblyInfo.cs
using TestDemo;
[module: FodyAtt]
namespace TestDemo
{
public class AssemblyInfo
{
}
}
主函数 Main
public static void Main(string[] args)
{
//0x02 调用测试类
var f = new FodyCmd();
f.DoSomething();
}
测试类 FodyTest.cs
using System;
namespace TestDemo
{
[FodyAtt]//写在方法上就是方法注入,写在类上就是全部方法注入
public class FodyCmd
{
public FodyCmd()
{
//注意,默认构造函数也会执行
Console.WriteLine("构造函数");
}
public void DoSomething()
{
Console.WriteLine("执行了:" + nameof(DoSomething));
}
}
}
特性
using System;
using System.Reflection;
namespace TestDemo
{
[AttributeUsage(AttributeTargets.All)]
public class FodyAtt : Attribute
{
protected object InitInstance;
protected MethodBase InitMethod;
protected object[] Args;
public void Init(object instance, MethodBase method, object[] args)
{
InitMethod = method;
InitInstance = instance;
Args = args;
}
public void OnEntry()
{
Console.WriteLine("进入函数之前");
}
public void OnExit()
{
Console.WriteLine("进入函数之后");
}
public void OnException(Exception exception)
{
Console.WriteLine("例外" + exception.Message);
}
}
}
缺陷
无法使用非自己写的特性进行注入,
或者是我了解不够...因为我没看全API...(我只是个引路人,具体大家深入研究...)
Harmony2
基本上跟fody差不多,但是它可以反射搜索函数,不需要写特性
Demo https://stackoverflow.com/questions/7299097/dynamically-replace-the-contents-of-a-c-sharp-method
https://harmony.pardeike.net/articles/basics.html
引用nuget
Lib.Harmony
控制台注入例子
官方还有一个例子不是很好懂,因此不展示,上面的链接有.
using HarmonyLib;
using System;
namespace AOP注入测试
{
public class Program
{
static void Main(string[] args)
{
var harmony = new Harmony(nameof(Program));
var mPrefix = SymbolExtensions.GetMethodInfo(() => JoinBoxCmdAddFirst());
var mPostfix = SymbolExtensions.GetMethodInfo(() => JoinBoxCmdAddLast());
var mp1 = new HarmonyMethod(mPrefix);
var mp2 = new HarmonyMethod(mPostfix);
var mOriginal = AccessTools.Method(typeof(Program), nameof(DD));
var newMet = harmony.Patch(mOriginal, mp1, mp2);
DD();
Console.WriteLine("Main");
}
public static void JoinBoxCmdAddFirst()
{
Console.WriteLine("JoinBoxCmdAddFirst");
}
public static void JoinBoxCmdAddLast()
{
Console.WriteLine("JoinBoxCmdAddLast");
}
public static void DD()
{
Console.WriteLine("Hello World!");//此处无法断点
}
}
}
注入cad命令
它可以反射处理所有的cad命令特性,然后前面注入事务,后面提交事务,中间就不用写事务了.
因为类库用户肯定是想少写一个"事务特性"到命令上面的,
没人想写了几百个命令再去每个命令上补个特性的,
阿惊认为这是代码侵入的方式,非常恶心...因此才放弃fody的注入方式.
而本类库默认情况不侵入用户的命令,
用户想侵入需要手动进行 AOP.Run(nameSpace) 侵入到指定的命名空间,
而启动策略之后,自动将事务侵入命名空间下的命令,此时有拒绝特性的策略保证括免.
所以只是用一个模块就可以改变其他模块行为,实现修改少,作用大.
启用策略
public class AutoAOP
{
[JoinBoxInitialize]//见 https://www.cnblogs.com/JJBox/p/10850000.html#_label3_0_2_1
public void Initialize()
{
//在所有的命令末尾注入清空事务栈函数
AOP.Run(nameof(TestNameSpace));
}
}
注入和反射
using HarmonyLib;
public class JoinBoxRefuseInjectionTransaction : Attribute
{
/// <summary>
/// 拒绝注入事务
/// </summary>
public JoinBoxRefuseInjectionTransaction()
{
}
}
public class AOP
{
/// <summary>
/// 在此命名空间下的命令末尾注入清空事务栈函数
/// </summary>
public static void Run(string nameSpace)
{
Dictionary<string, (CommandMethodAttribute Cmd, Type MetType, MethodInfo MetInfo)> cmdDic = new();
AutoClass.AppDomainGetTypes(type => { //见 https://www.cnblogs.com/JJBox/p/10850000.html
if (type.Namespace != nameSpace)
return;
//类上面特性
if (type.IsClass)
{
var attr = type.GetCustomAttributes(true);
if (RefuseInjectionTransaction(attr))
return;
}
//函数上面特性
var mets = type.GetMethods();//获得它的成员函数
for (int ii = 0; ii < mets.Length; ii++)
{
var method = mets[ii];
//找到特性,特性下面的方法要是Public,否则就被编译器优化掉了.
var attr = method.GetCustomAttributes(true);
for (int jj = 0; jj < attr.Length; jj++)
if (attr[jj] is CommandMethodAttribute cmdAtt)
{
if (!RefuseInjectionTransaction(attr))
cmdDic.Add(cmdAtt.GlobalName, (cmdAtt, type, method));
}
}
});
if (cmdDic.Count == 0)
return;
var harmony = new Harmony(nameSpace);
var mPrefix = SymbolExtensions.GetMethodInfo(() => JoinBoxCmdAddFirst());//进入函数前
var mPostfix = SymbolExtensions.GetMethodInfo(() => JoinBoxCmdAddLast());//进入函数后
var mp1 = new HarmonyMethod(mPrefix);
var mp2 = new HarmonyMethod(mPostfix);
foreach (var item in cmdDic)
{
//原函数执行(所处类type,函数名)
var mOriginal = AccessTools.Method(item.Value.MetType, item.Value.MetInfo.Name);
//mOriginal.Invoke();
//新函数执行:创造两个函数加入里面
var newMet = harmony.Patch(mOriginal, mp1, mp2);
//newMet.Invoke();
}
}
/// <summary>
/// 含有拒绝注入事务
/// </summary>
/// <param name="attr">特性集合</param>
/// <returns></returns>
private static bool RefuseInjectionTransaction(object[] attr)
{
bool refuseInjectionTransaction = false;
for (int kk = 0; kk < attr.Length; kk++)
{
if (attr[kk] is JoinBoxRefuseInjectionTransaction)
{
refuseInjectionTransaction = true;
break;
}
}
return refuseInjectionTransaction;
}
public static Transaction Transaction;
//命令开启打开事务
public static void JoinBoxCmdAddFirst()
{
Transaction = db.TransactionManager.StartTransaction();
}
//命令结束提交事务
public static void JoinBoxCmdAddLast()
{
if(Transaction.IsDisposed)
return;
if(Transaction.TransactionManager.NumberOfActiveTransactions != 0)
{
Transaction.Commit();
Transaction.Dispose();
}
}
}
拒绝策略
namespace TestNameSpace
{
public class AopTestClass
{
//类不拒绝,这里拒绝
[JoinBoxRefuseInjectionTransaction]
[CommandMethod("JoinBoxRefuseInjectionTransaction")]
public void JoinBoxRefuseInjectionTransaction()
{
}
//不拒绝
[CommandMethod("InjectionTransaction")]
public void InjectionTransaction()
{
//怎么用事务呢? 直接 AOP.Transaction
}
}
//拒绝注入事务,写类上,则方法全都拒绝
[JoinBoxRefuseInjectionTransaction]
public class AopTestClassRefuseInjection
{
//此时这个也是拒绝的..这里加特性是多余
[JoinBoxRefuseInjectionTransaction]
[CommandMethod("JoinBoxRefuseInjectionTransaction2")]
public void JoinBoxRefuseInjectionTransaction2()
{
//拒绝注入就要自己开事务,通常用在循环提交事务上面.
//另见 报错0x02 https://www.cnblogs.com/JJBox/p/10798940.html
//就自己新建啊~
}
[CommandMethod("InjectionTransaction2")]
public void InjectionTransaction2()
{
}
}
}
(完)