cad.net IExtensionApplication接口的妙用_分开写启动运行函数
原因
在cad使用netload命令加载dll后,dll自动运行的方法是通过继承IExtensionApplication接口的函数.
其中构造函数先运行,再运行Initialize方法,cad关闭的时候运行Terminate方法.
IExtensionApplication接口是不能实现一次以上的,实现了你也不会执行两次.
那么这给编写代码带来了一种不好的情况是,每次都要去修改实现这个接口的类,
如果是一个小的测试功能,你又要去动前面的核心,这样就感觉很蛋疼...
编程思维上面叫做"开闭原则":对拓展进行开放,对修改进行关闭.
所以我是这么想的,在实现 IExtensionApplication接口的 Initialize 和 Terminate 时候,
用反射来找到某个接口(仿IExtensionApplication接口的),然后搜下面接口的 Initialize 和 Terminate,然后运行这个它.
当然,你除了使用它反射接口,还可以反射特性.
20191227增加了控制加载顺序的函数.
20210507增加特性,虽然更新部分,不过南胜反射特性提取了每个命令,把它们放在cuix的菜单工具位置,秀到炸啊...所以还是建议去南胜大佬那看看 南胜博客,自动生成cuix
仿IExtensionApplication接口的接口.
namespace JoinBox
{
[Flags]
public enum Sequence : byte
{
First,// 最先
Last, // 最后
}
// 仿IExtensionApplication接口的接口.
public interface IAutoGo
{
// 控制加载顺序
Sequence SequenceId();
// 关闭cad的时候会自动执行
void Terminate();
// 打开cad的时候会自动执行
void Initialize();
}
public class JoinBoxInitialize : Attribute
{
public bool IsInitialize { get; set; }
public Sequence Sequence { get; set; }
/// <summary>
/// 自己制作的一个特性,放在函数上面用来初始化或者结束回收
/// </summary>
/// <param name="sequence">优先级</param>
/// <param name="initialize"><see cref="true"/>用于初始化,<see cref="false"/>用于结束回收</param>
public JoinBoxInitialize(Sequence sequence = Sequence.Last, bool initialize = true)
{
Sequence = sequence;
IsInitialize = initialize;
}
}
}
初始化函数
继承 IExtensionApplication 接口,cad加载这个dll,就会运行它.
为了解决IExtensionApplication在一个dll内无法多次实现接口的关系,
所以在这里反射加载所有的IAutoGo,以达到能分开写"启动运行"函数的目的.
namespace JoinBox
{
public class RunClass
{
public Sequence SequenceId { get; }
MethodInfo _methodInfo;
object? _instance;
public RunClass(MethodInfo method, Sequence sequence)
{
_methodInfo = method;
SequenceId = sequence;
var reftype = _methodInfo.ReflectedType;
if (reftype == null) return;
var fullName = reftype.FullName; //命名空间+类
if (fullName == null) return;
var type = reftype.Assembly.GetType(fullName);//通过程序集反射创建类+
if (type == null) return;
_instance = Activator.CreateInstance(type);
}
/// <summary>
/// 运行方法
/// </summary>
public void Run()
{
try
{
if (_method.IsStatic)
{
//参数数量一定要匹配,为null则参数个数不同导致报错,
//参数为stirng[],则可以传入object[]代替,其他参数是否还可以实现默认构造?
var paramInfos = methodInfo.GetParameters();
var args = new List<object> { };
for (int i = 0; i < paramInfos.Length; i++)
args.Add(null!);
_method.Invoke(null, args.ToArray());//静态调用
}
else if (_instanceObject != null)
{
//非静态,调用实例化方法
_method.Invoke(_instanceObject, null);
}
}
catch (System.Exception e)
{
Debugger.Break();
Debug.WriteLine("AutoClass.RunClass.Run出错" + e.Message);
}
}
}
public class AutoClass : IExtensionApplication
{
static List<RunClass> _InitializeList = new(); //储存方法用于初始化
static List<RunClass> _TerminateList = new(); //储存方法用于结束释放
const string _iAutoGo = "IAutoGo";
//打开cad的时候会自动执行
public void Initialize()
{
try
{
GetAttributeFunctions();
GetInterfaceFunctions(_InitializeList, "Initialize");
//按照 SequenceId 排序_升序
_InitializeList = _InitializeList.OrderBy(runClass => runClass.SequenceId).ToList();
RunFunctions(_InitializeList);
}
catch (System.Exception e)
{
Debugger.Break();
Debug.WriteLine("惊惊连盒,IExtensionApplication,AutoClass.Initialize出错::" + e.Message);
}
}
//关闭cad的时候会自动执行
public void Terminate()
{
try
{
GetInterfaceFunctions(_TerminateList, "Terminate");
//按照 SequenceId 排序_降序
_TerminateList = _TerminateList.OrderByDescending(runClass => runClass.SequenceId).ToList();
RunFunctions(_TerminateList);
}
catch (System.Exception e)
{
Debugger.Break();
Debug.WriteLine("惊惊连盒,IExtensionApplication,AutoClass.Terminate出错::" + e.Message);
}
}
/// <summary>
/// 遍历程序域下所有类型
/// </summary>
/// <param name="action"></param>
public static void AppDomainGetTypes(Action<Type> action)
{
int error = 0;
try
{
var assemblies = AppDomain.CurrentDomain.GetAssemblies();
#if !NET35
//cad2021出现如下报错
//System.NotSupportedException:动态程序集中不支持已调用的成员
assemblies = assemblies.Where(p => !p.IsDynamic).ToArray();
#endif
//主程序域
for (int ii = 0; ii < assemblies.Length; ii++)
{
var assembly = assemblies[ii];
Type[]? types = null;
try
{
//获取类型集合,反射时候还依赖其他的dll就会这个错误
if (Path.GetFileName(assembly.Location) == "AcInfoCenterConn.dll")
continue;
types = assembly.GetTypes();
}
catch (ReflectionTypeLoadException) { continue; }
if (types is null)
continue;
for (int jj = 0; jj < types.Length; jj++)
{
var type = types[jj];
if (type is not null)
{
++error;
action(type);
}
}
}
}
catch (System.Exception e)
{
Debugger.Break();
Debug.WriteLine($"出错:AppDomainGetTypes;计数{error};错误信息:{e.Message}");
}
}
/// <summary>
/// 收集接口下的函数
/// </summary>
/// <param name="runClassList">储存要运行的方法</param>
/// <param name="methodName"></param>
/// <returns></returns>
void GetInterfaceFunctions(List<RunClass> runClassList, string methodName = "Initialize")
{
string JoinBoxSequenceId = nameof(Sequence) + "Id";
AppDomainGetTypes(type => {
//获取接口集合
var inters = type.GetInterfaces();
for (int ii = 0; ii < inters.Length; ii++)
{
if (inters[ii].Name == _iAutoGo)//找到接口的函数
{
Sequence? sequence = null;
MethodInfo? initialize = null;
//获得它的成员函数
var mets = type.GetMethods();
for (int jj = 0; jj < mets.Length; jj++)
{
var method = mets[jj];
if (method.Name == JoinBoxSequenceId)
{
var obj = method.Invoke();
if (obj != null)
sequence = (Sequence)obj;
continue;
}
else if (method.Name == methodName)
{
initialize = method;
}
if (initialize is not null && sequence is not null)
break;
}
if (initialize is not null)
{
RunClass runc;
if (sequence is not null)
runc = new RunClass(initialize, sequence.Value);
else
runc = new RunClass(initialize, Sequence.Last);
runClassList.Add(runc);
}
break;
}
}
});
}
/// <summary>
/// 收集特性下的函数
/// </summary>
void GetAttributeFunctions()
{
AppDomainGetTypes(type => {
// if (type.IsClass && type.Name == "我们来测试一个初始化特性并且运行一下吧调试的时候记得反注释这里看看什么效果")
{
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 JoinBoxInitialize jjAtt)
{
var runc = new RunClass(method, jjAtt.Sequence);
if (jjAtt.IsInitialize)
_InitializeList.Add(runc);
else
_TerminateList.Add(runc);
break;//特性只会出现一次
}
}
}
}
});
}
/// <summary>
/// 执行收集到的函数
/// </summary>
/// <param name="runClassList"></param>
void RunFunctions(List<RunClass> runClassList)
{
for (int i = runClassList.Count - 1; i >= 0; i--)
{
runClassList[i].Run();
runClassList.RemoveAt(i);
}
}
}
}
封存上面的,之后也不用动了....
调用
调用接口
任何需要实现启动运行的函数,都去实现 :IAutoGo
public class Test : IAutoGo
{
public Sequence SequenceId()
{
//最好只使用一次,用于初始化工具集的路径之类的优先项
return Sequence.First;
//其余均使用last
//return Sequence.Last;
}
public void Initialize()
{
Editor ed = Application.DocumentManager.MdiActiveDocument.Editor;//命令栏交互
ed.WriteMessage("惊惊博客是 https://www.cnblogs.com/JJBox/ ");
}
public void Terminate()
{
//可以用,但是界面消除了就没了
//MessageBox.Show("哇哇哇哇哇");
//不可以用,此时的cad界面都已经关闭了...涉及内存释放的问题
//Editor ed = Acap.DocumentManager.MdiActiveDocument.Editor;
}
}
调用特性
如果你觉得接口很麻烦,那么就用特性吧,它似乎比较简单,
但是注意一下释放时执行顺序,我采用了降序进行,初始化是Sequence.First的就会降到后面进行,以免获取不到插件路径之类的..
namespace JoinBox
{
public class 我们来测试一个初始化特性并且运行一下吧调试的时候记得反注释这里看看什么效果
{
[JoinBoxInitialize]
public void 用于初始化时候执行()
{
MessageBox.Show("用于初始化时候执行");
}
[JoinBoxInitialize(false)]
public void 用于释放时候执行()
{
MessageBox.Show("用于释放时候执行.");
MessageBox.Show("你会发现虽然执行了,但是不弹窗,因为主窗口已经回收了");
}
}
}
(完)