既有 Excel 巨集(VBA)封裝複雜邏輯(如試算表試算、格式化、外部資料抓取),C# 應用需觸發執行、傳遞參數、取得返回值,並決定是否顯示 Excel 視窗。
Microsoft.Office.Interop.Excel 組件。Application.Run("MacroName", args...) 支援參數與返回值。Module1),ThisWorkbook、工作表類別模組中的巨集無法被 Run 識別。Workbook.Save(),否則 VBA 修改不會持久化。Marshal.ReleaseComObject + GC.Collect() 避免 Excel 殭屍進程。
using System;
using System.IO;
using System.Reflection;
using Excel = Microsoft.Office.Interop.Excel;
public class ExcelMacroHelper {
public void RunExcelMacro(
string excelFilePath,
string macroName,
object[] parameters,
out object returnValue,
bool visible = false) {
if (!File.Exists(excelFilePath))
throw new FileNotFoundException("Excel 檔案不存在", excelFilePath);
if (string.IsNullOrWhiteSpace(macroName))
throw new ArgumentException("巨集名稱不可為空");
object missing = Missing.Value;
object[] runArgs = parameters == null
? new object[] { macroName }
: Combine(new object[] { macroName }, parameters);
Excel.Application app = null;
Excel.Workbooks books = null;
Excel._Workbook book = null;
try {
app = new Excel.ApplicationClass { Visible = visible };
books = app.Workbooks;
book = books.Open(excelFilePath, missing, missing, missing, missing, missing,
missing, missing, missing, missing, missing, missing, missing);
// 透過反射呼叫 Application.Run
returnValue = app.GetType().InvokeMember("Run",
BindingFlags.Default | BindingFlags.InvokeMethod,
null, app, runArgs);
book.Save(); // 關鍵:持久化 VBA 修改
}
catch (TargetInvocationException tie) {
throw tie.InnerException ?? tie; // 保留原始 VBA 錯誤堆疊
}
finally {
// 依序釋放 COM 物件(逆序)
ReleaseCom(book);
ReleaseCom(books);
if (app != null) {
app.Quit();
ReleaseCom(app);
}
GC.Collect();
GC.WaitForPendingFinalizers();
}
}
private static object[] Combine(object[] first, object[] second) {
var result = new object[first.Length + second.Length];
Array.Copy(first, result, first.Length);
Array.Copy(second, 0, result, first.Length, second.Length);
return result;
}
private static void ReleaseCom(object obj) {
if (obj != null) {
try { Marshal.ReleaseComObject(obj); } catch { }
}
}
}
' 無參數、無返回值
Sub RefreshAllPivots()
ActiveWorkbook.RefreshAll
End Sub
' 帶參數
Sub SetHeaderTitle(ByVal title As String)
Sheets("Report").Range("A1").Value = title
End Sub
' 有返回值
Function CalculateTotal(ByVal rate As Double, ByVal qty As Long) As Double
CalculateTotal = rate * qty * 1.05 ' 含稅
End Function
var helper = new ExcelMacroHelper();
object ret;
// 1. 無參數、顯示視窗
helper.RunExcelMacro(@"D:\Report.xlsm", "RefreshAllPivots", null, out ret, true);
// 2. 帶參數、隱藏執行
helper.RunExcelMacro(@"D:\Report.xlsm", "SetHeaderTitle", new object[] { "2024 年度報表" }, out ret, false);
// 3. 有返回值
helper.RunExcelMacro(@"D:\Report.xlsm", "CalculateTotal", new object[] { 100.0, 50 }, out ret, false);
Console.WriteLine($"總計:{ret}"); // 輸出 5250
| 現象 | 原因 | 解決 |
|---|---|---|
| 找不到巨集 | 巨集寫在 ThisWorkbook/Sheet 類別模組 | 移至標準模組(插入 → 模組) |
| 參數型別不符 | VBA Variant vs C# 強型別 | C# 傳 object 陣列,確保數值型別對應 |
| Excel 進程殘留 | COM 物件未釋放 | 嚴格 ReleaseComObject + Quit() + GC.Collect() |
| 儲存失敗/唯讀 | 檔案被佔用/唯讀屬性 | 檢查檔案鎖定、權限;考慮 SaveAs 覆寫 |
| 中文路徑/參數亂碼 | 編碼不一致 | 確保專案與 VBA 編輯器編碼一致(UTF-8 / 系統預設) |