透過 OLE 自動化,C++Builder 應用程式可啟動 Word 97、建立/開啟文件、插入文字、圖片、繪圖物件,實現文件自動化產出。本文完整解析 ComObj.hpp 封裝的 Variant 操作介面、Word 物件模型對應關係、實戰代碼架構與常見坑點。
| C++Builder 封裝 | Word VBA 對應 | 用途 |
|---|---|---|
CreateOleObject("Word.Application") | CreateObject("Word.Application") | 啟動/取得 Word 應用程式物件 |
GetActiveOleObject("Word.Application") | GetObject(, "Word.Application") | 連接已執行的 Word 實例 |
Variant::OlePropertyGet/Set(name, val) | obj.Property = val / val = obj.Property | 存取屬性 |
Variant::OleProcedure(name, args...) | obj.Method args | 呼叫無傳回值方法 |
Variant::OleFunction(name, args...) | Set obj = obj.Method(args) | 呼叫有傳回值方法(回傳子物件) |
Application
├─ Documents (集合)
│ ├─ Add() → Document (新文件)
│ └─ Open(FileName) → Document (開啟檔案)
├─ ActiveDocument → Document (目前作用中文件)
└─ WordBasic (相容舊版巨集介面)
Document
├─ Paragraphs (集合)
│ ├─ Add() → Paragraph (新增段落)
│ └─ Item(Index) → Paragraph
├─ Range(Start, End) → Range (文字範圍)
├─ Shapes (集合)
│ ├─ AddLine(x1,y1,x2,y2) → Shape (直線)
│ └─ AddPicture(FileName, ...) → Shape (圖片)
├─ ComputeStatistics(wdStatisticPages/Paragraphs) → Long
└─ Password (屬性) 設定開啟密碼
#ifndef Unit1H
#define Unit1H
#include <Classes.hpp>
#include <Controls.hpp>
#include <StdCtrls.hpp>
#include <Forms.hpp>
#include <ComObj.hpp> // 關鍵:OLE 自動化支援
#include <Dialogs.hpp>
#include <ExtCtrls.hpp>
class TForm1 : public TForm {
__published:
TButton *Button1; // 開啟檔案
TOpenDialog *OpenDialog1;
TButton *Button2; // 新建文件
TMemo *Memo1; // 輸入文字
TButton *Button3; // 插入文字到游標段落
TButton *Button5; // 插入直線與圖片
TLabel *Label1, *Label2, *Label3;
TBevel *Bevel1, *Bevel2;
void __fastcall Button1Click(TObject *Sender);
void __fastcall Button2Click(TObject *Sender);
void __fastcall Button3Click(TObject *Sender);
void __fastcall Button5Click(TObject *Sender);
private:
Variant V, Vdocuments, Vdocument1, Vdocument2, Vactive; // Word 物件參考
int tag; // 追蹤段落索引
Variant __fastcall GetWordObject(); // 取得/啟動 Word
void __fastcall Add(int paraIndex); // 插入文字到指定段落
public:
__fastcall TForm1(TComponent* Owner);
};
extern PACKAGE TForm1 *Form1;
#endif
#include <vcl.h>
#pragma hdrstop
#include "Unit1.h"
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
__fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner) {
tag = 1; // 段落計數器起始值
}
// 取得 Word.Application:優先連接現有實例,否則啟動新實例
Variant __fastcall TForm1::GetWordObject() {
Variant Obj;
AnsiString AppName = "Word.Application";
HWND hPrevApp = ::FindWindow(NULL, "Microsoft Word");
if (!hPrevApp)
Obj = CreateOleObject(AppName); // 啟動新實例
else
Obj = GetActiveOleObject(AppName); // 連接現有實例
Obj.OlePropertySet("Visible", true); // 顯示 Word 視窗
return Obj;
}
// 開啟現有文件並顯示頁數
void __fastcall TForm1::Button1Click(TObject *Sender) {
V = GetWordObject();
Vdocuments = V.OleFunction("Documents"); // 取得 Documents 集合
if (OpenDialog1->Execute()) {
Vdocument1 = Vdocuments.OleFunction("Open", OpenDialog1->FileName);
int pages = Vdocument1.OleFunction("ComputeStatistics", 2); // wdStatisticPages = 2
Label1->Caption = "檔案 " + Vdocument1.OlePropertyGet("Name") + " 頁數: " + IntToStr(pages);
}
}
// 新建文件並設定密碼
void __fastcall TForm1::Button2Click(TObject *Sender) {
V = GetWordObject();
Vdocuments = V.OleFunction("Documents");
Vdocument2 = Vdocuments.OleFunction("Add"); // 新建空白文件
Vdocument2.OlePropertySet("Password", "12345"); // 設定開啟密碼
}
// 在目前作用中文件新增段落並寫入 Memo1 文字
void __fastcall TForm1::Button3Click(TObject *Sender) {
Vactive = V.OleFunction("ActiveDocument");
int paras = Vactive.OleFunction("ComputeStatistics", 4); // wdStatisticParagraphs = 4
Vactive.OleFunction("Paragraphs").OleFunction("Add"); // 新增段落
Add(paras + tag); // 寫入新段落
Memo1->Clear();
tag++;
}
// 共用:向指定段落索引寫入格式化文字
void __fastcall TForm1::Add(int i) {
Variant range = ((Vactive.OleFunction("Paragraphs")).OleFunction("Item", i)).OleFunction("Range");
range.OleFunction("Font").OlePropertySet("Italic", true);
range.OleFunction("Font").OlePropertySet("Size", 18);
range.OleFunction("Font").OlePropertySet("ColorIndex", 6); // wdBlue
range.OlePropertySet("Text", Memo1->Text);
}
// 插入直線與圖片
void __fastcall TForm1::Button5Click(TObject *Sender) {
Variant Vactive1 = V.OleFunction("ActiveDocument");
Variant Vshape = Vactive1.OleFunction("Shapes");
// AddLine(Left, Top, Right, Bottom) - 單位為 Points
Vshape.OleFunction("AddLine", 90, 80, 400, 80);
if (OpenDialog1->Execute()) {
// AddPicture(FileName, LinkToFile, SaveWithDocument, Left, Top, Width, Height)
Vshape.OleFunction("AddPicture", OpenDialog1->FileName,
false, true, 50, 50, 80, 100);
}
}
| 問題 | 原因 | 解決方案 |
|---|---|---|
| 第二次啟動程式無法連接 Word | FindWindow 找到視窗但 GetActiveOleObject 失敗 | 檢查 Word 是否以相同權限執行(UAC);嘗試 Obj = V.OleFunction("Application") 取得父應用程式 |
| 中文路徑/檔名開啟失敗 | AnsiString 編碼與 Word 期望不符 | 使用 WideString 或 OleVariant 傳遞 Unicode 路徑 |
| 插入圖片後檔案過大 | AddPicture 預設 LinkToFile=false, SaveWithDocument=true 嵌入完整圖片 | 若需連結不嵌入,設 LinkToFile=true;但移動檔案會斷連 |
| 段落索引計算錯誤 | ComputeStatistics 回傳總段落數,新增後索引為 n+1 | 如代碼所示:先取得現有段落數,Add 後寫入 paras + tag |
| 程式結束 Word 仍殘留進程 | 未正確釋放 Variant 參考,或 Word 有未儲存提示對話框阻塞 | 結束前呼叫 V.OleProcedure("Quit");確保所有 Variant 變數離開作用域 |
| 執行緒模型衝突 (CO_E_NOTINITIALIZED) | VCL 主執行緒為 STA,若在背景執行緒呼叫 OLE 需 CoInitializeEx | 所有 OLE 操作留在主執行緒;或背景執行緒自行 CoInitializeEx(NULL, COINIT_APARTMENTTHREADED) |
libreoffice --headless --convert-to pdf 命令列轉換DocX (NuGet) 純 .NET 建立 .docx,無需 WordOpenXML SDK 底層操作 .docx XML 結構Aspose.Words / GemBox.Document 商業庫功能完整TMS Office Bridge、CppInterop 封裝 .NET 庫,或直接呼叫 LibreOffice UNO API。C++Builder 的 ComObj.hpp + Variant 提供了極薄的 OLE 自動化封裝,核心邏輯在於:
CreateOleObject/GetActiveOleObject 取得 Application 根物件OleFunction 走訪集合(Documents、Paragraphs、Shapes)OlePropertyGet/Set 存取屬性,OleProcedure 執行動作Variant 生命週期,確保 Word 正常退出雖然 Word 97 早已退休,但此物件模型在 Office 365 中大體相容,掌握此架構即可遷移至新版本或替代方案。