From f7272787c5f034c3a69877f7d77a122ca865fc7c Mon Sep 17 00:00:00 2001 From: LittleGuo Date: Sun, 25 Oct 2026 17:52:58 +0800 Subject: [PATCH] =?UTF-8?q?docs:=20GiDrawUtility=20=E5=86=85=E5=AD=98?= =?UTF-8?q?=E6=B3=84=E6=BC=8F/=E6=95=88=E7=8E=87/=E9=80=BB=E8=BE=91?= =?UTF-8?q?=E5=88=86=E6=9E=90=EF=BC=8C=E6=9C=AA=E6=94=B9=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- GiDrawUtility-analysis.md | 150 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 150 insertions(+) create mode 100644 GiDrawUtility-analysis.md diff --git a/GiDrawUtility-analysis.md b/GiDrawUtility-analysis.md new file mode 100644 index 0000000..db3647f --- /dev/null +++ b/GiDrawUtility-analysis.md @@ -0,0 +1,150 @@ +# GiDrawUtility 分析:内存泄漏、效率与逻辑缺陷 + +> 目标:在动手改之前,把 `GiDrawUtility`(AecRxBase 公共几何工具库)的内存泄漏、性能热点和逻辑缺陷列清楚,给出可分批落地的修复顺序。 +> +> 范围:`SourceCode/Code2026/AecRxBase/GiDrawUtility.cpp`(965 行)、`SourceCode/Code2026/inc/GiDrawUtility.h`。 +> 关联:`EarThwormDef.h`(DELETE_PTR / EQUAL_ZERO 宏)、`inc/AdsXRx/tsuperlist.h`(DELETE_PTR 定义)。 +> +> 状态(2026-10-25):仅分析,未改代码。行号以当前 main 为准。 + +## 1. 背景与影响面 + +GiDrawUtility 是全库共用的 3D 几何构建工具(DrawPipe / DrawLoft / DrawRevolve / DrawCone / DrawBox 等),以 `AECDLLIMPEXP` 导出,直接被 14+ 个模块调用: + +- `DrawPipe(ptFrom, ptTo, dRadius, dThick, nSegs)`:`AecDbEwPipe`、`AecDbHvacDuct`、`AecDbECableTray`、`AecDbSludgePipe`、`EarthWormCmd/DGGLDlg.cpp`、`stakeOper.cpp`,以及所有 `DrawCylinder`(内部转调此函数); +- `DrawPipe(pCurve, pRegion, ptCenInRgn)`:`AecDbEwPipeBase`(约 20 处)、`AecDbEwFitting`(约 40 处)、`AecDbArcStair`、`AecDbLineStair`、`AecDbClimbStair`、`AecDbGuardBar`、`AecDbFlume` 等; +- `DrawPipe(pCurve, dR)`:`AecDb3dRoadAxisLine`、`AecDbEwFitting`。 + +因此本文件的问题会随"每画一个管件/实体"重复触发,泄漏与效率问题在连续布管、explode 分解场景下会持续累积。 + +约定说明:`DELETE_PTR(p)` 宏定义为 `if (p) { delete p; p = NULL; }`(`tsuperlist.h`),delete 后置 NULL,故"DELETE_PTR 后 return 该指针"不会产生野指针;未 DELETE_PTR 的失败路径才是泄漏。`booleanOper` 成功后第二个操作数不被消耗、需调用方手动释放——本仓库 `AecDbEwPipeBase.cpp`、`AecDbEwFitting.cpp` 均循此例。 + +## 2. 内存泄漏 + +### L1 严重·高频:DrawPipe 重复建 region 且丢弃第一次结果(GiDrawUtility.cpp:677-678) + +```cpp +if (!createRegion(&Cir1)) return NULL; // 返回值(new AcDbRegion)被直接丢弃 → 泄漏 +AcDbRegion *pBig = createRegion(&Cir1); // 同样的几何又建了一遍 +``` + +`createRegion` 返回 `createFromCurves` 产出的堆对象,第一次调用**每次必漏一个 AcDbRegion**。所有画管道/圆柱的路径(含 DrawCylinder)都踩中,画一根管子漏一个 region。 + +**修法**:删掉第一行,改为 + +```cpp +AcDbRegion *pBig = createRegion(&Cir1); +if (!pBig) return NULL; +``` + +### L2 严重·同函数:pSml 两处泄漏(GiDrawUtility.cpp:684-700) + +- `dThick > dRadius` 时内圆负半径,`createRegion(&Cir2)` 失败后 `return NULL`,`pBig` 未释放; +- `booleanOper` **成功路径** `pSml` 从未释放(失败路径反而删了)。按仓库惯例成功后应 `DELETE_PTR(pSml)`。 + +### L3 DrawLoft(Curve*,Curve*,Curve*) 失败路径漏三个 clone(GiDrawUtility.cpp:238) + +```cpp +if (!pRegT || !pRegB) return NULL; // _pTop/_pBottom/_pLine 及已成功的 pRegT 全漏 +``` + +`DrawPyramid` / `DrawTydf` / `DrawCirTorus` / `DrawRectTorus` 截面非 region 时走这条。 + +### L4 DrawBox / DrawSphere 失败路径漏 pSolid(GiDrawUtility.cpp:432、449) + +`createBox` / `createSphere` 返回非 eOk(负尺寸等)时 `return NULL` 前未 delete。 + +### L5 DrawMesh 失败路径双重泄漏(GiDrawUtility.cpp:355-377) + +`createLoftedSolid` 失败时 `DELETE_PTR(pEnt)` 后直接 return——`new AcGePoint3d[columns]`(pSection)**和** `vecPLs` 中已生成的所有 polyline 全部泄漏。成功路径反而清理完整,失败路径什么都没清。 + +### L6 DrawPipe(pCurve, dR) 两平台分支不对称(GiDrawUtility.cpp:820-829) + +closed polyline 走 `createSweptSolid`:ZWCAD 分支(866 行起)失败时有 `DELETE_PTR(pReg)`,ARX 分支没有 → ARX 上扫掠失败即漏 `pReg`。 + +### L7 模式性问题:createFromCurves 只取 [0](GiDrawUtility.cpp:130-135、645-648、817-819) + +`createRegion(AcDbEntity*)`、`DrawDish`、`DrawPipe(pCurve, dR)` 均只取结果数组第一个 region,多 region 返回(自交多边形等)时其余全漏。圆/简单闭合环一般返回 1 个,概率低但存在。 + +## 3. 运行效率 + +| # | 位置 | 问题 | +| --- | --- | --- | +| E1 | 677-678 | 即 L1:每次画管道多跑一次 `createFromCurves`(ACIS 级开销) | +| E2 | 830-860 | fallback 分段递归时**每段重新建同一个圆 region**(AcDbCircle + createFromCurves + sweep)。长路径(>100000)分十几段就重复十几遍。应段外建一次模板 region,段内 `clone() + transformBy()` | +| E3 | 145-160 | `transfromRegion` 对 region 做**两次** `transformBy`(先旋转后平移)。region 是 ACIS 拓扑,每次变换触发几何重算;两条矩阵可合成一次。每条管道热路径 | +| E4 | 466-467、553-555 | `DrawPyramid`/`DrawTydf` 中 `mat.setToTranslation(ptBottom - kOrigin)` 在 `ptBottom=原点` 时是恒等变换,白做一次 transformBy;顶点可直接用 3D 点 `addVertexAt` 完全免掉 | +| E5 | 455-460 | `DrawSphere` 用 `RotateAndMove(kZAxis, origin, kZAxis, ptCenter)`——同轴 rotateTo 返回单位阵,纯平移即可 | + +## 4. 执行逻辑 + +### B1 逻辑错误:RotateAndMove 平移项不完整(GiDrawUtility.cpp:186) + +```cpp +mat = _vtOrig.rotateTo(_vtDist); // p' = R·p +mat.setTranslation(ptDist - ptOrig); // p' = R·p + (ptDist - ptOrig) ← 错 +``` + +把 ptOrig 搬到 ptDist 的正确平移项是 `ptDist - R·ptOrig`。当前实现仅在 ptOrig 为原点时正确: + +- 现有内部调用恰好都传 `kOrigin`,暂未暴露; +- **DrawTydf 的 bIsTfdy=TRUE 分支**(508-518 行先把 ptBottom 平移到 (0,0,H),562 行再 `RotateAndMove(kZAxis, ptBottom, vtMove, ptPos)`)ptOrig 非原点——**vtMove 不是 Z 轴时台体落点偏移 R·ptOrig - ptOrig**,是当前代码中唯一实际踩中的路径; +- 该函数是 AECDLLIMPEXP 公开 API,未来调用方传非原点必错。 + +**修法**一行: + +```cpp +AcGePoint3d ptTmp(ptOrig); +ptTmp.transformBy(mat); +mat.setTranslation(ptDist - ptTmp); +``` + +### B2 零向量防护是谎言(GiDrawUtility.cpp:180-183) + +`_vtOrig *= 1; // 保证长度不为0` 是 no-op。输入零向量时 `rotateTo` 行为未定义,应显式判零并回退默认轴。 + +### B3 DrawTydf 的 tfdy 分支 guide 线未随截面变换(GiDrawUtility.cpp:508-521) + +`line` 在变换**之前**构造;tfdy 分支把 `plBottom` 上移 H、`cir` 下移 H,`line` 原地不动,不再连接两截面中心。loft 的 guide rail 直接影响曲面形状;非 tfdy 分支的 line 是连接两截面中心的,行为不一致,疑似 bug(需确认是否有意为之)。 + +### B4 DrawMesh 无入参校验(GiDrawUtility.cpp:340-352) + +- `pVertexList` 为 NULL 直接解引用; +- `rows * columns == 0` 时 `pVertexList[rows * columns - 1]` 越界读 [-1]; +- `rows == 1` 时截面数组为空,`createLoftedSolid` 失败又回到 L5 泄漏路径; +- `first = 1, last = rows - 1` 中 rows 为 UInt32,rows=0 时 last 下溢为 -1(循环虽不执行,但上面 line 构造已先越界)。 + +### B5 DrawRevolve 失败时往图纸里塞红线(GiDrawUtility.cpp:283-286) + +```cpp +if (!pReg) { _pC->setColorIndex(1); AddToModelSpace(_pC); return NULL; } +``` + +工具函数产生"往模型空间加实体"的副作用,调用方无法预期也无法关闭,疑似调试代码残留。且用的是匿名 namespace 里不判 `pEnt` 空的那个重载。 + +### 其余逻辑问题 + +| # | 位置 | 问题 | +| --- | --- | --- | +| B6 | 673 | `nSegs` 死参数:签名带分段数,函数体完全没用,调用方以为能控制精度 | +| B7 | 794 | `getStartPoint(ptS)` 返回值未检查,未定义点传入 `getFirstDeriv` | +| B8 | 138 | `create3DPolyline` 无条件 `makeClosed()`:开口折线被强制闭合,回转出闭合实体;`len<=0` 不校验 | +| B9 | 636 等 | 全局无参数校验:`DrawDish` 的 `2*dH/dD` 除零;`DrawCirTorus`/`DrawRectTorus` 在 `minorRadius >= majorRadius` 时构造负半径几何直接喂 ACIS;`ptFrom==ptTo` 零长路径扫掠 | +| B10 | 405-412 | `DrawExclude(pCurve, vtPath)` 用包围盒中心作拉伸起点,凹形截面时起点在区域外,需确认是否符合预期 | +| B11 | 10-84 | 匿名 namespace 里三个 `AddToModelSpace` 重载语义分裂:4 参版返回 ErrorStatus、2 参 bool 版返回 AcDbObjectId、3 参默认值版返回 ErrorStatus 且不判 `pEnt` 空,靠重载决议分流,维护陷阱 | + +## 5. 规范问题 + +- **S1** `spliteCurve`(722 行)、`deleteContainerOfArx`(770 行)是文件级 `static`,违反 CODEBUDDY.md"文件内私有函数放匿名 namespace"约定——本文件已有匿名 namespace,应移入。 +- **S2** `transfromRegion` 函数名拼写错误(transfrom → transform),公开 API 不宜直接改名,建议加正确拼写的 wrapper。 +- **S3** `DELETE_PTR` 宏无 `do-while(0)` 包裹(`if (ptr) { delete ptr; ptr = NULL; }`),本文件现有用法均为独立语句暂无 bug,但与 if/else 搭配时是陷阱。 + +## 6. 建议修复顺序 + +1. **L1 + L2**:改动约 3 行,消掉最高频的泄漏(每画一个管件一次),收益最大; +2. **B1**:一行修复整类错位风险,需回归 DrawTydf 各分支及所有落点; +3. **L3 / L4 / L5 / L6**:失败路径按触发概率补清理; +4. **B4 / B9**:入参校验,防崩溃类; +5. 其余(B2/B3/B5~B8/B10/B11、E 系列、S 系列)按需。 + +注意事项:GiDrawUtility 是 AecRxBase 公共库、DrawPipe 系列是全库管道实体绘制的公共入口,改动后建议小步提交,编译对应 preset 并在 CAD 中实测(画管、台体、回转体、loft 分解)。 -- 2.54.0