承接上篇,完成幾何元素基類 CMyBase、圓弧 CMyArc、多段線 CMyPolyLn、形狀容器 CShape 的完整實作,並解決跨 DLL 邊界的 new/delete 記憶體管理問題。
struct MYXYZ { double x, y, z; };
class CMyXYZ : public MYXYZ {
public:
CMyXYZ() : x(0), y(0), z(0) {}
CMyXYZ(const MYXYZ& xyz) : x(xyz.x), y(xyz.y), z(xyz.z) {}
CMyXYZ(double x_, double y_, double z_) : x(x_), y(y_), z(z_) {}
virtual ~CMyXYZ() {}
operator MYXYZ() const { return *this; }
const CMyXYZ& operator=(const MYXYZ& xyz) { x=xyz.x; y=xyz.y; z=xyz.z; return *this; }
};
提供拷貝建構子、指派運算子、隱性轉換,支援 Line(CMyXYZ(0,0,0), CMyXYZ(100,100,0)) 等臨時物件語法。
class CMyBase {
protected:
CMyBase() {}
public:
virtual int GetType() = 0; // ISARC=1, ISPOLYLN=2, ISSHAPE=3
virtual int Draw(long lDevice) = 0; // lDevice = (long)HDC
virtual ~CMyBase() {}
};
protected 建構子防止直接實例化。long lDevice 而非 HDC,提升跨平台擴充性。class CMyArc : public CMyBase {
public:
CMyArc(MYXYZ pc, MYXYZ ps, MYXYZ pe) : m_pc(pc), m_ps(ps), m_pe(pe) {}
virtual int GetType() { return ISARC; }
virtual int Draw(long lDevice) {
double dR = sqrt(pow(m_ps.x-m_pc.x,2)+pow(m_ps.y-m_pc.y,2)+pow(m_ps.z-m_pc.z,2));
::Arc((HDC)lDevice,
(int)(m_pc.x-dR), (int)(m_pc.y-dR),
(int)(m_pc.x+dR), (int)(m_pc.y+dR),
(int)m_ps.x, (int)m_ps.y, (int)m_pe.x, (int)m_pe.y);
return 0;
}
virtual ~CMyArc() {}
MYXYZ m_pc, m_ps, m_pe; // 圓心、起點、終點
};
#pragma warning(disable:4786 4251 4273) // 抑制 STL 匯出警告
#include <list>
using namespace std;
typedef list<MYXYZ*> MYPOLYLN; // 存指標避免拷貝開銷
class CMyPolyLn : public CMyBase {
public:
CMyPolyLn() { m_PolyLn.clear(); }
virtual int GetType() { return ISPOLYLN; }
virtual int AddPoint(MYXYZ* pxyz) {
MYXYZ* p = new MYXYZ(*pxyz);
m_PolyLn.insert(m_PolyLn.end(), p);
return 0;
}
virtual int Draw(long lDevice) {
HDC hdc = (HDC)lDevice;
if (m_PolyLn.empty()) return 0;
auto it = m_PolyLn.begin();
MoveToEx(hdc, (int)(*it)->x*100+100, (int)-(*it)->y*100+200, NULL);
for (; it != m_PolyLn.end(); ++it) {
LineTo(hdc, (int)(*it)->x*100+100, (int)-(*it)->y*100+200);
}
return 0;
}
virtual ~CMyPolyLn() {
for (auto p : m_PolyLn) delete p;
m_PolyLn.clear();
}
MYPOLYLN m_PolyLn;
};
class CShape : public CMyBase {
public:
CShape() { m_List.clear(); }
virtual int GetType() { return ISSHAPE; }
virtual int Draw(long lDevice) {
for (auto p : m_List) p->Draw(lDevice);
return 0;
}
virtual ~CShape() {
for (auto p : m_List) delete p;
m_List.clear();
}
list<CMyBase*> m_List;
};
void CShapeView::OnDraw(CDC* pDC) {
CShape shape;
shape.m_List.push_back(new CMyArc(CMyXYZ(100,100,0), CMyXYZ(100,20,0), CMyXYZ(180,100,0)));
CMyPolyLn* poly = new CMyPolyLn;
poly->AddPoint(new MYXYZ{1,1,0});
poly->AddPoint(new MYXYZ{2,2,0});
poly->AddPoint(new MYXYZ{2,1,0});
shape.m_List.push_back(poly);
shape.Draw((long)pDC->m_hDC);
}
問題:主程式 new 配置物件,DLL 中 delete 釋放 → Heap Corruption / Assertion Failure,因為 EXE 與 DLL 各自擁有獨立 CRT 堆疊。
| 方案 | 做法 | 優點 | 缺點 |
|---|---|---|---|
| 1. GlobalAlloc/GlobalFree | 用 GlobalAlloc(GMEM_FIXED, size) 替代 new,GlobalFree 替代 delete | 共享系統堆疊,跨 DLL 安全 | 需手動呼叫建構子/解構子,繁瑣 |
| 2. 統一 CRT 版本 (推薦) | EXE 與 DLL 專案設定 → C/C++ → Code Generation → Multithreaded DLL (Release) / Debug Multithreaded DLL (Debug) | 共享同一 CRT 堆疊,new/delete 自然配對 | 所有模組必須用相同 CRT 版本、相同編譯器版本 |
| 3. 介面式釋放 | DLL 提供 DestroyShape(IShape*),內部呼叫 delete | 封裝記憶體管理,呼叫端無需知曉配置器 | 需定義介面,增加複雜度 |
本專案採用方案 2,最符合現代 VC++ 開發規範,且零程式碼修改即可解決。
CMyPen/CMyBrush 成員)。HitTest、BoundingBox 支援選取/編輯。