# CadFlex **Repository Path**: JJbox/cad-flex ## Basic Information - **Project Name**: CadFlex - **Description**: 代理CAD全部版本的API使得大家可以更方便使用 - **Primary Language**: C# - **License**: MIT - **Default Branch**: main - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2025-12-18 - **Last Updated**: 2026-01-04 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # cad-flex 我用mono反编译dll为C#代码,然后实现代理转发,保证用户原本的代码不改变,然后进行隔离。 已经构造了架构,现在剩下发送函数的问题没有解决。想要发送函数名和参数过去,但是C#支持out/ref/返回值,虽然可以写一个接收器socket不断接收要触发函数,但是这样很慢。这样存在序列化,无论是字符串还是二进制序列化都太慢了。因此我要弄一些更好的加速方案。 ``` 原始代码(完全不变) ↓ 编织器(Weaver)← 运行时重写 ↓ 共享内存代理类(复刻cad的) 类转结构体. ↓ 共享内存 ←→ 远程执行环境,持有资源 ``` ## 项目结构 ### 1. FlexServer (类库, 通讯包读取, 被cad加载) - acad每个net版本一个,会跟net版本绑定,也就是多个dll,由我负责,插件层就不需要处理多版本了 - **功能**: - socket通讯,也就是网络插件。但是存在性能问题,可以备选 - 进行不断扫描共享内存里面的环形队列,每个线程一个,异步扫描 - 构造编织器(Weaver),运行时重写 - 遍历自身acad公开的函数指针,然后实现一个funcMapArray,握手阶段 - socket就把公开函数给暴露出去 - **测试**:用acad多个版本加载和测试 ### 2. FlexProxy (类库, 通讯包写入, 被插件引用) - 这是被插件引用的net8.0的dll项目 - **注意**:acadcode文件夹内是FlexReflector项目生成过来的代码,不能改,要改就去FlexReflector项目内改生成逻辑 - **功能**: - 启动之后不断往共享内存的`环形队列`写入函数指针和参数 - 需要每个线程一个环形队列 - 握手阶段:把共享内存funcMapArray转为自己内存的map - 生成代码:每个函数写入共享内存环形队列,写的是服务端的函数指针 ### 3. PluginExample (用户负责) - 用户插件,用户自己有自己的名称 - 我只写一个例子,需要引用FlexProxy ### 4. FlexReflector (控制台, 反射生成代码) - mono反射dll生成代码 - 里面有资源文件夹(acad08的dll,acad10的...),是会拷贝到输出目录 - 输出到FlexProxy-acadcode项目内 - **功能**:生成CAD类的代理代码,实现类转结构体 ### 5. FlexMemoryPool (类库, 共享内存分配器) - 共享内存分配器,仿照堆分配 - 其实我们是没有清零的,表示速度快得很 - 由于做了对齐内存,申请内存时候是指针的前一个块的,[填充...][块首,块尾][∧指针,对齐16字节] - **测试**:test文件夹内放控制台测试 ### 6. FlexCircularQueue (类库, 环形队列) - 共享内存无锁环形队列 - 在共享内存上面找一个位置 - **测试**:test文件夹内放控制台测试 ### 7. FlexClient (类库) - 客户端通讯组件,用于与FlexServer进行交互 ## 核心设计思路 ### 资源问题 由于我想实现资源零拷贝,因此我使用共享内存模拟堆分配,已经实现了在项目的 FlexMemoryPool 分配器中,但是只能写入结构体。因此我必须要在 FlexReflector 项目上面实现输出`类转结构体`,需要放在一个FlexStruct文件夹内,这样就可以两边读写是同一个内存的资源,而且由于生成了私有结构体,方便我们检查代码。 ### 未完成 反射cad的每个类,都需要创建一个影子结构体(私有的) 写在另一个文件夹下面,例如 acdbmgd 就生成 acdbmgd_shadow 文件夹 我目前创建 类/结构体 的转发逻辑是错误的, 需要遍历全部的CAD类生成私有结构体 我希望是每次创建类资源的时候,背后都是申请了一个结构体写入共享内存。 然后,我生成的类代码的属性=>指向共享内存的结构体。 那么acad的结构体怎么办呢? new struct 的话岂不是也可以直接映射到共享内存? 反射本类全部公共字段和属性.致密排序.得到内存长度()生成一个影子结构体. 把类型加入映射表来给后面的重复检测提供类型指针: map[classType, structType] 例子: ```csharp // 不要创建新工程实现,因为实现了大量的过滤和处理,你需要在原有代码上面进行修改指向. // 需要创建新的文件夹写结构体. // 实际上还是会有类头的,也仅仅只有类头那一点点字节. public class 类名aa { // 放共享内存的结构体指针在这里. private Intptr _p; // 类的属性指向共享内存了,这样就0开销了 public int 属性名 { get => _p.get属性名; set => _p.set属性名 = value; } public void 类名aa { // 这里添加一个共享内存的方式申请方法,用FlexMemoryPool申请 _p = Helpers.ProxyForwarder.Alloc<结构体aa>(); } void Dispose() { // 需要释放共享内存的p指针,如果用户代码没有释放,那就内存泄露,让用户自己保证. Helpers.ProxyForwarder.Free(_p); } } // 生成的影子 结构体aa,全部是internal,但是属性是可以访问的 internal class 结构体aa { public int 属性名; public int 属性名2; public int 属性名3; } ``` ### 函数收发 cad服务端: - 用核心个线程数和核心绑定(貌似异步就好了啊),每个线程跳转到各自的共享内存位置环形队列 - 然后开始不断读取并执行:通过`map[函数名, 本地指针]` - 函数指针-读取参数-操作完成解锁 - 参数如果是不定参数,那么还需要获取不定参数的堆位置,解析出来,也就是二次跳转 插件端代理类: - 不断写入共享内存中 - 跳转共享内存的线程栈的上下文-队列如果无锁就上锁-参数压入-函数指针转换表 ### 环形队列 代理一直写 固定栈帧(函数指针+参数列表) cad就一直追着执行... ``` 分块环形缓冲区,每个分块有版本号表示是否已经执行, 防止追尾,像麦克风驱动程序一样,这样可以用到CPU预读机制. 每个函数会高频写入,也就是存在写到一半容量,就被设置为读取, 然后写入就会去写下一个块,如果撞车,那么写入期间有锁,所以无法读取. ``` ### 握手阶段 函数是一等公民: `函数(this,剩余参数...)` 如果send函数名太有问题了,要转为函数指针,否则会导致字符串拷贝. 不定长参数要用共享内存的堆分配器进行分配. 加载插件之后, 服务端和客户端(插件)需要握手,同步双方函数指针. 服务端触发是触发cad自己的函数指针, cad内部遍历一次自己的类,把函数指针提取出来,转数组写入共享内存: `funcMap[函数名,函数指针].ToArray()` 共享内存是难以用map的,所以要转数组. 客户端插件: 需要把共享内存funcMapArray转为自己内存的map,生成代码:每个函数写入共享内存环形队列,写的是服务端的函数指针! 所以我们选择了写入慢,读取快. 要做: 转非托管函数指针,并且钉着托管函数,避免GC回收造成脱钩. ### 结构体传参计算/不定参数计算 考虑参数传递结构体,岂不是要动态栈帧,反射cad每个参数列表, 预先计算cad的每个结构体长度,求得参数集合最长的帧长,就可以只需要一个固定帧. 不定长度参数呢? 把这部分放到共享堆,这样就是一个`void*`指针长度了. ## 技术亮点 1. **零拷贝设计**:通过共享内存实现资源的直接访问,避免了序列化和反序列化的开销 2. **高性能通讯**:使用无锁环形队列实现高频函数调用 3. **跨版本兼容**:FlexServer针对不同CAD .NET版本提供支持,插件层无需处理多版本问题 4. **透明代理**:用户原始代码无需修改,通过编织器实现运行时重写 5. **类转结构体**:实现CAD类到结构体的转换,便于在共享内存中高效传输 6. **无锁编程**:采用无锁设计提高并发性能 ## 总结 cad-flex项目旨在实现CAD插件的高效隔离和跨版本兼容,通过共享内存、无锁编程和运行时编织等技术,实现了高性能的插件执行环境。项目结构清晰,各组件职责明确,便于维护和扩展。 ## 注意事项 - 目前的事件没有add和remove,无法通过编译 - 类里面有字符串这种引用类型需要特殊处理 - 类数量满过共享内存会出现分配异常OOM - 客户端和服务端之间存在乒乓pingpong效应,可能影响CPU预取 ## 未来展望 1. 完善事件处理机制 2. 优化字符串等引用类型的共享内存处理 3. 实现更高效的内存管理策略 4. 优化环形队列设计,减少乒乓效应 5. 提供更完善的测试用例和文档