• 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);
            }
        }
    }
    

    缺陷

    Fody很方便,也有不方便的时候

    无法使用非自己写的特性进行注入,

    或者是我了解不够...因为我没看全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()
            {
            }
        }
    }
    

    (完)

  • 相关阅读:
    axis2调用webservice
    JSON: Property 'xxx' has no getter method的解决办法
    JDK中工具类的使用
    Java权限讲解
    JSON的使用
    策略模式
    Tomcat虚拟目录的设置
    extends 与 implements 的区别
    利用正则表达式分割字符串
    给面板添加背景图片
  • 原文地址:https://www.cnblogs.com/JJBox/p/16157578.html
Copyright © 2020-2023  润新知