登录
注册
开源
企业版
高校版
搜索
帮助中心
使用条款
关于我们
开源
企业版
高校版
私有云
模力方舟
AI 队友
登录
注册
轻量养虾,开箱即用!低 Token + 稳定算力,Gitee & 模力方舟联合出品的 PocketClaw 正式开售!点击了解详情
代码拉取完成,页面将自动刷新
捐赠
捐赠前请先登录
取消
前往登录
扫描微信二维码支付
取消
支付完成
支付提示
将跳转至支付宝完成支付
确定
取消
Watch
不关注
关注所有动态
仅关注版本发行动态
关注但不提醒动态
18
Star
27
Fork
14
InspireFunction
/
CadLabelBar
代码
Issues
3
Pull Requests
0
Wiki
统计
流水线
服务
质量分析
Jenkins for Gitee
腾讯云托管
腾讯云 Serverless
悬镜安全
阿里云 SAE
Codeblitz
SBOM
开发画像分析
我知道了,不再自动展开
更新失败,请稍后重试!
移除标识
内容风险标识
本任务被
标识为内容中包含有代码安全 Bug 、隐私泄露等敏感信息,仓库外成员不可访问
关于卸载的一些猜想
待办的
#IAI0ZZ
liuqihong
拥有者
创建于
2024-08-06 07:26
# Loadx的架构 在[惊惊博客](https://www.cnblogs.com/JJBox/p/13833350.html#_lab2_6_2)上面写了: 其实现在卸载是成功的,但是关闭cad程序的时候弹出报错: `System.ArgumentException:“无法跨 AppDomain 传递 GCHandle。”` 下面有[小专题](https://gitee.com/inspirefunction/CadLabelBar/issues/IAI0ZZ#note_30817538_link)讨论它 下面的全部工作降低其他错误的出现可能. NetLoad命令加载Loadx,Loadx利用自动接口加载所有用户插件. 形成三层架构:CAD,Loadx(加载层),Plugin(用户层). Loadx是不能卸载的,如果有耦合的就靠它进行信息转发,重点就是Loadx内部有两层. ## 对接用户层 以下工作需要修改用户层代码,这样使得用户层不是爱怎么写就怎么写.并且每一步都需要验证,有点难受. 1,改名 用户层不能让cad检查到接口和命令特性, 这意味着我们需要把用户层**接口**和**命令特性**都改名... 因为cad直接用命令netload会反射命令特性和执行自动接口,它是否存在利用反射自动巡查机制?(见下面的动态编译你就会产生怀疑了) 因此能避免则避免,需要全部改名. 2,动态编译 利用自动接口动态编译256个颜色命令能够直接被cad识别命令,因此是编译在主程序域的. 并且发生时机是在netload之后,这说明了是动态巡查的,存在巡查命令时机. a,cad会在找不到命令时候自动反射主程序域内部. 能够通过实现程序域事件验证吗? b,cad能够直接监控动态编译事件来获取动态编译区的代码? 没发现有动态编译前后事件,更为相信cad是通过第一点来实现的. 那么需要动态编译到新的程序域,然后跨域事件进行命令(改名)调用. 卸载前也要跑去这里清空静态变量和Dispose和卸载域.(其实Loadx是跑去每个程序域,应该能够自动完成这一步) loadx触发--用户层的自动接口实现动态编译,编译到新域--新域的命令(改名)需要被收集到loadx命令栈... 3,写析构和Dispose接口 Loadx层卸载程序域前会遍历用户层全部类型, 有Dispose就执行,否则设置为null. 因此用户层要写好这些释放函数, 尤其是static的释放. 尤其是class上面有cad的点集/各种c++集合的,要清空他们. 本意就是卸载此dll程序域前执行某些功能,令它安全退出,尚且不知道程序域卸载事件,是主程序域执行还是写入用户层中. 4,停止标记 用户层只能用线程池,并制作一个全局停止标记给Loadx层卸载时候用. 因为这是线程安全退出的方式,所以必须要用此方式,不要依赖程序域卸载自动关闭线程这种不安全退出,避免文件损坏 全部线程退出之后,把标记改成Stop,并且会await,一直等待全部线程死亡. 事件停止: ```c# var doneEvent = new ManualResetEvent(false); ThreadPool.QueueUserWorkItem(state =>{ while (!doneEvent.WaitOne(0)) // 非阻塞等待 { /*执行任务逻辑*/ } }); doneEvent.Set();// 当需要停止线程时 ``` task停止: ```c# using System; using System.Threading; using System.Threading.Tasks; class Program { static async Task Main(string[] args) { // 创建取消令牌源 var cts = new CancellationTokenSource(); CancellationToken token = cts.Token; // 启动线程或任务 Task task = Task.Run(() => DoWork(token), token); // 假设线程运行了3秒,然后发出取消 await Task.Delay(3000); cts.Cancel(); // 等待任务完成或抛出异常 try { await task; } catch (OperationCanceledException) { Console.WriteLine("任务已取消"); } } static void DoWork(CancellationToken token) { for (int i = 0; i < 100; i++) { // 定期检查取消请求,然后抛异常(这个不太环保,可以循环检测 token.IsCancellationRequested) token.ThrowIfCancellationRequested(); // 模拟工作 Console.WriteLine($"执行工作 {i + 1}"); Thread.Sleep(100); // 模拟工作延迟 } // 标记为停止,实际中这里可以是清理工作 Console.WriteLine("工作已停止"); } } ``` 当前程序域卸载事件,执行把标记全部停止: ```c# AppDomain.CurrentDomain.DomainUnload += OnDomainUnload; 异常:常用于线程和AOP拦截 try { /* 执行一些操作 */ } catch (AppDomainUnloadedException) { /* 程序域正在被卸载 */ } ``` 如果某个程序域无法释放,那么可以通过监控来查看资源的问题,监控程序域的状态变化AppDomain.MonitoringIsEnabled 属性来确定是否启用了监控,然后通过 AppDomain.MonitoringTotalAllocatedMemorySize 来获取当前域的内存使用情况. [相关接口](https://www.cnblogs.com/PurpleTide/archive/2011/01/07/1929566.html) [msdn:应用程序域资源监控](https://learn.microsoft.com/zh-cn/dotnet/standard/garbage-collection/app-domain-resource-monitoring) 5,弱事件 弱事件标记,停止执行事件.. acap全局事件怎么处理?全部转为弱事件吗? https://www.cnblogs.com/MuNet/p/6687823.html 6,yield和async/await.卸载的影响? 7,winform/WPF线程呢? 通过子类化拦截吗?直接调用Dispose? 那么线程有没有影响? 他们的资源dll呢?例如图标这些的卸载. 8,跨域的数据,事务,点集合处理?共享内存传输? 跨程序域事务 创建Entity并通过事务提交给数据库,我还可以继续操控它ent.Dispose(),其实Dispose内部处理了double free,你并没有真正释放它. 而提交事务是写了一个副本给数据库(不同的数据库,实现不同的序列化),不是穿透到数据库内部执行的. 不过能进去ent说明本地内存并没有释放,一旦再使用ent是报错的.(发生时机是另一个事务读取时候) a,这可能是因为cad采取指针所有权转移来代替序列化. b,也可能是提交事务时候,把事务id写入entity,它本身也是序列化提交到数据库的. 无论cad如何实现上面两点,你都不能把ent通过List(外部声明等)偷到事务外使用. 事务外使用要用id,如果想再次处理需要再开新事务. 因为没有断开和数据库的链接的机制,所以要视为每次事务都是一次数据库链接. 如果cad是采取转移指针所有权,那么跨程序域提交事务,这可能强关联了程序域内存,从而导致卸载失败. 因此需要包装一个传输功能MarshalByRefObject,通过跨域方式序列化给Loadx主程序域,再去创建事务提交,避免其他程序域不能释放. 跨程序域的时候,类型名相同,那么is type它们也是不相同的.通过Type.AssemblyQualifiedName 完全限定名称,来进行判断为同一个. ## 对接CAD ### 加载层实现命令栈. 遍历获取用户层自动接口(改名后)和命令特性(改名后),执行自动接口函数(改名的)以及缓存命令. class cmd { 命令名, 命令函数指针, // 程序集(强引用), 程序集名, // 程序域(强引用), dll路径 } var cmdMap = map<命令名,cmd> 卸载程序集记得来这里移除. 全部强引用的单独一个map,然后卸载时候也要移除. 通过"未知命令事件"进行跨域调用命令. ### 创建dll程序域 为每个用户dll各自创建程序域. map<dllFile,AppDomain> 执行的时候没找到,就通过跨域事件搜索依赖图,要在还没有才加载,加载时候又放入新程序域,并且调用.(这一步是异步的,放入到消息队列的任务中,在结束事件后再搜索依赖图,不然加载时候搜不到岂不是嵌套调用事件了) (注意:不进行链式加载) #### 依赖图 任何dll都可能已经卸载,如果设计成用完就卸载,会面临一个功能上面反反复复加载卸载. 多个相互引用的dll会出现菱形依赖,此时无法卸载,需要确定依赖关系.因此它是有向图,需要广度/深度优先去确定依赖关系. 找到目标的叶子节点开始卸载程序域,直到目标...如果叶子也产生菱形依赖呢? 需要把整个菱形都移除.(弱引用的概念) 幻影依赖:继承引用/菱形引用. 例如:J引用IFox,此时A引用了J,就能使用IFox.一旦J升级了IFox,那么A也要升级. js那边包管理是利用pnpm安装包(代替npm),它是利用了文件系统把依赖图全部拍扁到一个文件夹,再通过快捷方式到包文件夹,由此解决了幻影依赖问题. 快捷方式:硬链接(Unix-like系统)或软链接(Windows系统). 包管理器在一个项目上面扁平化包,实现了共享储存,剔除了同版本冲突.再通过package.json知道当前项目用什么包版本.再提取全部子项目package.json维护一份总的映射表,内部子项目就可以引用不同版本旧包和新包. ### 卸载dll程序域 1,停止标记:跨域事件,线程池,单线程.停止所有事件?卸载事件?acap事件? 2,遍历全局静态变量执行Dispose. 3,按照dll依赖图进行卸载程序域. 4,通过子类化阻塞cad主线程. 5,卸载命令栈map缓存的跨域信息. 6,JIT特化,函数影响内联,使得卸载失败? 参考微软的[如何在 .NET Core 中使用和调试程序集可卸载性](https://www.bookstack.cn/read/dotnet/18b062d94c6a58fa.md) 7,async/await和return yield,会不会状态机阻碍卸载?停止线程应该都停止了吧. 8,内存泄露FFI/COM/数据库事务提交/加载单例 9,菱形依赖 10,COM问题: “不能通过GCHandle通过AppDomains" https://cloud.tencent.com/developer/ask/sof/111002318 11,FFI问题: a,发现之前动态查找acad.exe的接口,然后转为委托指针并且static作为缓存了, 这个行为可能导致卸载失败,要写弱引用吗?怎么写呢? b,Dllimport会不会引起耦合导致卸载失败呢? 链式加载用LoadLibraryEx, https://blog.csdn.net/xiaoyao961/article/details/135096688 c,Point3dCollection这种和c++通讯的,写了GC.KeepAlive是否存在影响?它和GCHandle是存在关系的,要跨域处理COM问题而不是内存共享. 12,加载单例: 动态加载dll两次导致事件被加载两次,然后执行时候会弹两次打印. 先-=再+=其实不对的,因为这两次就是不同程序域. a,如果卸载是成功的,先卸载再加载就好. b,可以通过管道通讯获取共享内存,共享内存上面对于所有事件更改为弱事件.acap+=都要自己在Loadx层实现一次. #### 已经做过了 1,c#unload的可能: https://m.jb51.net/article/221129.htm 2,这里面提及了,先构造程序域,参数是开启影相复制,再进行loadbyte和调用,不然卸载不掉. https://blog.csdn.net/xiansenLee/article/details/114831641 3,编译后无法删除dll是因为vs.exe会进行mmap, 要进行程序集影相复制domain.SetShadowCopyFiles() https://developer.aliyun.com/article/331951 实际上也是不行的,这样才成功:需要复制到其他路径后删除原本,让vs读取不到. (完)
# Loadx的架构 在[惊惊博客](https://www.cnblogs.com/JJBox/p/13833350.html#_lab2_6_2)上面写了: 其实现在卸载是成功的,但是关闭cad程序的时候弹出报错: `System.ArgumentException:“无法跨 AppDomain 传递 GCHandle。”` 下面有[小专题](https://gitee.com/inspirefunction/CadLabelBar/issues/IAI0ZZ#note_30817538_link)讨论它 下面的全部工作降低其他错误的出现可能. NetLoad命令加载Loadx,Loadx利用自动接口加载所有用户插件. 形成三层架构:CAD,Loadx(加载层),Plugin(用户层). Loadx是不能卸载的,如果有耦合的就靠它进行信息转发,重点就是Loadx内部有两层. ## 对接用户层 以下工作需要修改用户层代码,这样使得用户层不是爱怎么写就怎么写.并且每一步都需要验证,有点难受. 1,改名 用户层不能让cad检查到接口和命令特性, 这意味着我们需要把用户层**接口**和**命令特性**都改名... 因为cad直接用命令netload会反射命令特性和执行自动接口,它是否存在利用反射自动巡查机制?(见下面的动态编译你就会产生怀疑了) 因此能避免则避免,需要全部改名. 2,动态编译 利用自动接口动态编译256个颜色命令能够直接被cad识别命令,因此是编译在主程序域的. 并且发生时机是在netload之后,这说明了是动态巡查的,存在巡查命令时机. a,cad会在找不到命令时候自动反射主程序域内部. 能够通过实现程序域事件验证吗? b,cad能够直接监控动态编译事件来获取动态编译区的代码? 没发现有动态编译前后事件,更为相信cad是通过第一点来实现的. 那么需要动态编译到新的程序域,然后跨域事件进行命令(改名)调用. 卸载前也要跑去这里清空静态变量和Dispose和卸载域.(其实Loadx是跑去每个程序域,应该能够自动完成这一步) loadx触发--用户层的自动接口实现动态编译,编译到新域--新域的命令(改名)需要被收集到loadx命令栈... 3,写析构和Dispose接口 Loadx层卸载程序域前会遍历用户层全部类型, 有Dispose就执行,否则设置为null. 因此用户层要写好这些释放函数, 尤其是static的释放. 尤其是class上面有cad的点集/各种c++集合的,要清空他们. 本意就是卸载此dll程序域前执行某些功能,令它安全退出,尚且不知道程序域卸载事件,是主程序域执行还是写入用户层中. 4,停止标记 用户层只能用线程池,并制作一个全局停止标记给Loadx层卸载时候用. 因为这是线程安全退出的方式,所以必须要用此方式,不要依赖程序域卸载自动关闭线程这种不安全退出,避免文件损坏 全部线程退出之后,把标记改成Stop,并且会await,一直等待全部线程死亡. 事件停止: ```c# var doneEvent = new ManualResetEvent(false); ThreadPool.QueueUserWorkItem(state =>{ while (!doneEvent.WaitOne(0)) // 非阻塞等待 { /*执行任务逻辑*/ } }); doneEvent.Set();// 当需要停止线程时 ``` task停止: ```c# using System; using System.Threading; using System.Threading.Tasks; class Program { static async Task Main(string[] args) { // 创建取消令牌源 var cts = new CancellationTokenSource(); CancellationToken token = cts.Token; // 启动线程或任务 Task task = Task.Run(() => DoWork(token), token); // 假设线程运行了3秒,然后发出取消 await Task.Delay(3000); cts.Cancel(); // 等待任务完成或抛出异常 try { await task; } catch (OperationCanceledException) { Console.WriteLine("任务已取消"); } } static void DoWork(CancellationToken token) { for (int i = 0; i < 100; i++) { // 定期检查取消请求,然后抛异常(这个不太环保,可以循环检测 token.IsCancellationRequested) token.ThrowIfCancellationRequested(); // 模拟工作 Console.WriteLine($"执行工作 {i + 1}"); Thread.Sleep(100); // 模拟工作延迟 } // 标记为停止,实际中这里可以是清理工作 Console.WriteLine("工作已停止"); } } ``` 当前程序域卸载事件,执行把标记全部停止: ```c# AppDomain.CurrentDomain.DomainUnload += OnDomainUnload; 异常:常用于线程和AOP拦截 try { /* 执行一些操作 */ } catch (AppDomainUnloadedException) { /* 程序域正在被卸载 */ } ``` 如果某个程序域无法释放,那么可以通过监控来查看资源的问题,监控程序域的状态变化AppDomain.MonitoringIsEnabled 属性来确定是否启用了监控,然后通过 AppDomain.MonitoringTotalAllocatedMemorySize 来获取当前域的内存使用情况. [相关接口](https://www.cnblogs.com/PurpleTide/archive/2011/01/07/1929566.html) [msdn:应用程序域资源监控](https://learn.microsoft.com/zh-cn/dotnet/standard/garbage-collection/app-domain-resource-monitoring) 5,弱事件 弱事件标记,停止执行事件.. acap全局事件怎么处理?全部转为弱事件吗? https://www.cnblogs.com/MuNet/p/6687823.html 6,yield和async/await.卸载的影响? 7,winform/WPF线程呢? 通过子类化拦截吗?直接调用Dispose? 那么线程有没有影响? 他们的资源dll呢?例如图标这些的卸载. 8,跨域的数据,事务,点集合处理?共享内存传输? 跨程序域事务 创建Entity并通过事务提交给数据库,我还可以继续操控它ent.Dispose(),其实Dispose内部处理了double free,你并没有真正释放它. 而提交事务是写了一个副本给数据库(不同的数据库,实现不同的序列化),不是穿透到数据库内部执行的. 不过能进去ent说明本地内存并没有释放,一旦再使用ent是报错的.(发生时机是另一个事务读取时候) a,这可能是因为cad采取指针所有权转移来代替序列化. b,也可能是提交事务时候,把事务id写入entity,它本身也是序列化提交到数据库的. 无论cad如何实现上面两点,你都不能把ent通过List(外部声明等)偷到事务外使用. 事务外使用要用id,如果想再次处理需要再开新事务. 因为没有断开和数据库的链接的机制,所以要视为每次事务都是一次数据库链接. 如果cad是采取转移指针所有权,那么跨程序域提交事务,这可能强关联了程序域内存,从而导致卸载失败. 因此需要包装一个传输功能MarshalByRefObject,通过跨域方式序列化给Loadx主程序域,再去创建事务提交,避免其他程序域不能释放. 跨程序域的时候,类型名相同,那么is type它们也是不相同的.通过Type.AssemblyQualifiedName 完全限定名称,来进行判断为同一个. ## 对接CAD ### 加载层实现命令栈. 遍历获取用户层自动接口(改名后)和命令特性(改名后),执行自动接口函数(改名的)以及缓存命令. class cmd { 命令名, 命令函数指针, // 程序集(强引用), 程序集名, // 程序域(强引用), dll路径 } var cmdMap = map<命令名,cmd> 卸载程序集记得来这里移除. 全部强引用的单独一个map,然后卸载时候也要移除. 通过"未知命令事件"进行跨域调用命令. ### 创建dll程序域 为每个用户dll各自创建程序域. map<dllFile,AppDomain> 执行的时候没找到,就通过跨域事件搜索依赖图,要在还没有才加载,加载时候又放入新程序域,并且调用.(这一步是异步的,放入到消息队列的任务中,在结束事件后再搜索依赖图,不然加载时候搜不到岂不是嵌套调用事件了) (注意:不进行链式加载) #### 依赖图 任何dll都可能已经卸载,如果设计成用完就卸载,会面临一个功能上面反反复复加载卸载. 多个相互引用的dll会出现菱形依赖,此时无法卸载,需要确定依赖关系.因此它是有向图,需要广度/深度优先去确定依赖关系. 找到目标的叶子节点开始卸载程序域,直到目标...如果叶子也产生菱形依赖呢? 需要把整个菱形都移除.(弱引用的概念) 幻影依赖:继承引用/菱形引用. 例如:J引用IFox,此时A引用了J,就能使用IFox.一旦J升级了IFox,那么A也要升级. js那边包管理是利用pnpm安装包(代替npm),它是利用了文件系统把依赖图全部拍扁到一个文件夹,再通过快捷方式到包文件夹,由此解决了幻影依赖问题. 快捷方式:硬链接(Unix-like系统)或软链接(Windows系统). 包管理器在一个项目上面扁平化包,实现了共享储存,剔除了同版本冲突.再通过package.json知道当前项目用什么包版本.再提取全部子项目package.json维护一份总的映射表,内部子项目就可以引用不同版本旧包和新包. ### 卸载dll程序域 1,停止标记:跨域事件,线程池,单线程.停止所有事件?卸载事件?acap事件? 2,遍历全局静态变量执行Dispose. 3,按照dll依赖图进行卸载程序域. 4,通过子类化阻塞cad主线程. 5,卸载命令栈map缓存的跨域信息. 6,JIT特化,函数影响内联,使得卸载失败? 参考微软的[如何在 .NET Core 中使用和调试程序集可卸载性](https://www.bookstack.cn/read/dotnet/18b062d94c6a58fa.md) 7,async/await和return yield,会不会状态机阻碍卸载?停止线程应该都停止了吧. 8,内存泄露FFI/COM/数据库事务提交/加载单例 9,菱形依赖 10,COM问题: “不能通过GCHandle通过AppDomains" https://cloud.tencent.com/developer/ask/sof/111002318 11,FFI问题: a,发现之前动态查找acad.exe的接口,然后转为委托指针并且static作为缓存了, 这个行为可能导致卸载失败,要写弱引用吗?怎么写呢? b,Dllimport会不会引起耦合导致卸载失败呢? 链式加载用LoadLibraryEx, https://blog.csdn.net/xiaoyao961/article/details/135096688 c,Point3dCollection这种和c++通讯的,写了GC.KeepAlive是否存在影响?它和GCHandle是存在关系的,要跨域处理COM问题而不是内存共享. 12,加载单例: 动态加载dll两次导致事件被加载两次,然后执行时候会弹两次打印. 先-=再+=其实不对的,因为这两次就是不同程序域. a,如果卸载是成功的,先卸载再加载就好. b,可以通过管道通讯获取共享内存,共享内存上面对于所有事件更改为弱事件.acap+=都要自己在Loadx层实现一次. #### 已经做过了 1,c#unload的可能: https://m.jb51.net/article/221129.htm 2,这里面提及了,先构造程序域,参数是开启影相复制,再进行loadbyte和调用,不然卸载不掉. https://blog.csdn.net/xiansenLee/article/details/114831641 3,编译后无法删除dll是因为vs.exe会进行mmap, 要进行程序集影相复制domain.SetShadowCopyFiles() https://developer.aliyun.com/article/331951 实际上也是不行的,这样才成功:需要复制到其他路径后删除原本,让vs读取不到. (完)
评论 (
10
)
登录
后才可以发表评论
状态
待办的
待办的
进行中
已完成
已关闭
负责人
未设置
标签
未设置
标签管理
里程碑
未关联里程碑
未关联里程碑
Pull Requests
未关联
未关联
关联的 Pull Requests 被合并后可能会关闭此 issue
分支
未关联
分支 (
-
)
标签 (
-
)
开始日期   -   截止日期
-
置顶选项
不置顶
置顶等级:高
置顶等级:中
置顶等级:低
优先级
不指定
严重
主要
次要
不重要
参与者(1)
C#
1
https://gitee.com/inspirefunction/CadLabelBar.git
git@gitee.com:inspirefunction/CadLabelBar.git
inspirefunction
CadLabelBar
CadLabelBar
点此查找更多帮助
搜索帮助
Git 命令在线学习
如何在 Gitee 导入 GitHub 仓库
Git 仓库基础操作
企业版和社区版功能对比
SSH 公钥设置
如何处理代码冲突
仓库体积过大,如何减小?
如何找回被删除的仓库数据
Gitee 产品配额说明
GitHub仓库快速导入Gitee及同步更新
什么是 Release(发行版)
将 PHP 项目自动发布到 packagist.org
评论
仓库举报
回到顶部
登录提示
该操作需登录 Gitee 帐号,请先登录后再操作。
立即登录
没有帐号,去注册