使用 C# 添加、修改和删除 Excel VBA 宏 (无需 Microsoft Office Interop)

在一些企业内部系统中,Excel 宏仍然很常见,例如报表模板、财务表格、审批表、数据整理工具等。对于 C# 开发者来说,有时不仅需要生成普通 Excel 文件,还需要向 Excel 文件中写入 VBA 宏,或者读取、修改、删除已有文件中的宏代码。

通常我们会想到使用 Microsoft Excel Interop,但 Interop 需要本机安装 Excel,更适合桌面端自动化场景。在 Web 服务、后台任务、容器或服务器环境中,使用 Interop 可能会带来部署、权限和稳定性方面的问题。

本文介绍一种不依赖 Excel Interop 的处理方式,演示如何在 C# 中操作 Excel VBA 宏,主要内容包括:

目录

[一、Excel VBA 宏工程和模块简介](#一、Excel VBA 宏工程和模块简介)

[二、Excel 宏文件格式说明](#二、Excel 宏文件格式说明)

三、安装所需库

[四、使用 C# 在 Excel 中添加 VBA 宏](# 在 Excel 中添加 VBA 宏)

[五、使用 C# 读取 Excel 中的 VBA 宏](# 读取 Excel 中的 VBA 宏)

[六、使用 C# 修改 Excel 中的 VBA 宏代码](# 修改 Excel 中的 VBA 宏代码)

[七、使用 C# 删除 Excel 中的 VBA 宏](# 删除 Excel 中的 VBA 宏)

[八、C# 操作 Excel 宏的实用建议](# 操作 Excel 宏的实用建议)

九、总结


一、Excel VBA 宏工程和模块简介

在 Excel VBA 中,宏代码并不是直接存放在工作表单元格中,而是存放在 VBA 工程(VBA Project)中的不同模块里。不同模块适合不同类型的代码场景。

1. 标准模块

标准模块是最常用的 VBA 模块类型。平时录制宏、编写通用工具代码、自定义函数时,通常都会使用标准模块。

标准模块常用于:

  • 存放普通 Sub 宏过程

  • 编写通用数据处理逻辑

  • 编写报表生成或格式化代码

  • 编写可在单元格中调用的自定义函数

例如:

复制代码
Sub FormatReport()
    MsgBox "开始格式化报表"
End Sub

如果希望宏能够在 Excel 的"宏"窗口中直接查看和运行,通常会把它放在标准模块中。

2. 其他 VBA 模块类型

除了标准模块,Excel VBA 中还包括其他模块类型。本文不会重点展开这些模块,但在实际开发中需要了解它们的用途。

模块类型 常见用途
类模块(Class Module) 用于封装对象、属性、方法和事件,适合较复杂的 VBA 逻辑
工作表模块(Worksheet Module) 用于编写 Worksheet_ChangeWorksheet_SelectionChange 等工作表事件。这类模块是 Excel 自带且无法删除的。
工作簿模块(Workbook Module) 用于编写 Workbook_OpenWorkbook_BeforeClose 等工作簿事件。这类模块是 Excel 自带且无法删除的。
用户窗体模块(UserForm Module) 用于处理窗体和控件事件,例如按钮点击、文本框输入等

如果只是添加一个可手动运行的普通宏,通常优先使用标准模块。如果要让某段代码在工作表数据变化时自动触发,才需要考虑工作表模块。


二、Excel 宏文件格式说明

保存带 VBA 宏的 Excel 文件时,必须使用支持宏的文件格式。常见格式如下:

  • .xlsm:现代 Excel 宏启用工作簿,推荐使用

  • .xls:Excel 97-2003 工作簿,旧版格式

  • .xlsb:Excel 二进制工作簿,适合较大的文件

  • .xltm:Excel 宏启用模板

不要将包含 VBA 宏的文件保存为 .xlsx.xlsx 格式不支持 VBA 宏代码,如果保存格式不正确,宏代码可能无法保留。

本文示例统一使用 .xlsm 格式保存文件。


三、安装所需库

要在 C# 中添加、读取、修改和删除 Excel VBA 宏,可以安装 Spire.XLS for .NET。该库支持在 .NET 程序中创建、读取和编辑 Excel 文件,不依赖 Microsoft Excel Interop,因此运行环境中无需安装 Microsoft Excel。

可以通过 NuGet 安装:

复制代码
Install-Package Spire.XLS

VBA 宏工程操作支持于 Spire.XLS for .NET 16.3.6 及以上版本。如果你使用的是较早版本,请先升级到最新版本:

复制代码
Update-Package Spire.XLS

除了通过 NuGet 安装,也可以手动添加 DLL 引用。下载并解压 Spire.XLS for .NET 安装包后,在 Visual Studio 中右键项目,选择 添加引用,然后选择与项目目标框架匹配的 DLL 文件。


四、使用 C# 在 Excel 中添加 VBA 宏

下面的示例演示如何创建一个 Excel 工作簿,并添加一个 VBA 标准模块及宏代码。

示例中的标准模块名为 ReportTools,其中包含一个 FormatSalesReport 宏。这个宏用于格式化当前工作表中的销售报表,例如设置标题行样式、格式化销售额列、自动调整列宽并生成销售额合计。

假设工作表中的数据结构如下:

A 列 B 列 C 列 D 列
日期 客户 产品 销售额

示例代码如下:

cs 复制代码
using Spire.Xls;
using System;

namespace AddVbaMacro
{
    internal class Program
    {
        static void Main(string[] args)
        {
            string outputFile = "添加宏.xlsm";

            Workbook workbook = new Workbook();

            // 设置工作表名称,便于用户打开文件后查看
            Worksheet worksheet = workbook.Worksheets[0];
            worksheet.Name = "SalesReport";

            // 写入简单测试数据,便于打开文件后直接运行宏查看效果
            worksheet.Range["A1"].Text = "日期";
            worksheet.Range["B1"].Text = "客户";
            worksheet.Range["C1"].Text = "产品";
            worksheet.Range["D1"].Text = "销售额";

            worksheet.Range["A2"].Text = "2026/06/01";
            worksheet.Range["B2"].Text = "客户A";
            worksheet.Range["C2"].Text = "产品A";
            worksheet.Range["D2"].NumberValue = 1200;

            worksheet.Range["A3"].Text = "2026/06/02";
            worksheet.Range["B3"].Text = "客户B";
            worksheet.Range["C3"].Text = "产品B";
            worksheet.Range["D3"].NumberValue = 1850;

            worksheet.Range["A4"].Text = "2026/06/03";
            worksheet.Range["B4"].Text = "客户C";
            worksheet.Range["C4"].Text = "产品C";
            worksheet.Range["D4"].NumberValue = 960;

            // 获取 VBA 工程
            IVbaProject vbaProject = workbook.VbaProject;
            vbaProject.Name = "SalesReportVbaProject";

            // 设置代码页,避免中文注释或中文提示出现乱码
            vbaProject.CodePage = 936;

            // 添加标准模块
            IVbaModule module = vbaProject.Modules.Add("ReportTools", VbaModuleType.Module);

            // 写入普通宏过程
            module.SourceCode = @"
Sub FormatSalesReport()
    Dim ws As Worksheet
    Dim lastRow As Long

    Set ws = ActiveSheet

    ' 根据 A 列获取最后一行
    lastRow = ws.Cells(ws.Rows.Count, 1).End(xlUp).Row

    If lastRow < 2 Then
        MsgBox ""当前工作表没有可处理的数据。"", vbExclamation, ""提示""
        Exit Sub
    End If

    ' 设置标题行格式
    With ws.Range(""A1:D1"")
        .Font.Bold = True
        .Interior.Color = RGB(220, 230, 241)
        .HorizontalAlignment = xlCenter
    End With

    ' 设置销售额列格式
    ws.Range(""D2:D"" & lastRow).NumberFormat = ""#,##0.00""

    ' 自动调整列宽
    ws.Columns(""A:D"").AutoFit

    ' 添加汇总信息
    ws.Range(""F1"").Value = ""销售额合计""
    ws.Range(""F2"").Formula = ""=SUM(D2:D"" & lastRow & "")""
    ws.Range(""F1:F2"").Font.Bold = True

    MsgBox ""销售报表格式化完成。"", vbInformation, ""完成""
End Sub";

            // 保存为支持宏的 xlsm 格式
            workbook.SaveToFile(outputFile, FileFormat.Xlsm);
            workbook.Dispose();

            Console.WriteLine("已创建包含 VBA 宏的 Excel 文件。");
        }
    }
}

运行代码后,会生成一个名为 添加宏.xlsm 的文件。打开该文件后,可以进入 VBA 编辑器查看 ReportTools 模块代码,也可以在 Excel 的"宏"窗口中直接运行 FormatSalesReport


五、使用 C# 读取 Excel 中的 VBA 宏

读取宏时,比较常见的需求是导出 VBA 工程信息和标准模块代码,便于代码审查、版本归档或批量检查。

下面的示例会读取工作簿中的 VBA 工程信息,并导出所有标准模块的名称、类型和源代码。

cs 复制代码
using Spire.Xls;
using System;
using System.IO;

namespace ReadVbaMacro
{
    internal class Program
    {
        static void Main(string[] args)
        {
            string inputFile = "添加宏.xlsm";
            string outputFile = "VbaModules.txt";

            Workbook workbook = new Workbook();
            workbook.LoadFromFile(inputFile);

            IVbaProject vbaProject = workbook.VbaProject;

            string text = string.Empty;

            text += "VBA 工程信息" + Environment.NewLine;
            text += "工程名称:" + vbaProject.Name + Environment.NewLine;
            text += "工程描述:" + vbaProject.Description + Environment.NewLine;
            text += "是否受保护:" + vbaProject.IsProtected + Environment.NewLine;
            text += "代码页:" + vbaProject.CodePage + Environment.NewLine;

            text += Environment.NewLine + "标准模块代码" + Environment.NewLine;

            foreach (IVbaModule module in vbaProject.Modules)
            {
                if (module.Type == VbaModuleType.Module)
                {
                    text += Environment.NewLine;
                    text += "模块名称:" + module.Name + Environment.NewLine;
                    text += "模块类型:" + module.Type + Environment.NewLine;
                    text += "源代码:" + Environment.NewLine;
                    text += module.SourceCode + Environment.NewLine;
                }
            }

            File.WriteAllText(outputFile, text);

            workbook.Dispose();

            Console.WriteLine("VBA 标准模块代码已导出。");
        }
    }
}

读取结果:

这个示例读取的是标准模块,而不是工作表模块。对于普通宏代码,例如 Sub FormatSalesReport()Sub ExportData() 这类过程,通常都可以通过遍历标准模块来读取。

如果需要读取工作表事件代码,例如 Worksheet_Change,才需要获取具体工作表对应的对象模块。参考代码:

cs 复制代码
Worksheet ws = wb.Worksheets[0];
IVbaProject vbaProject = wb.VbaProject;

// 获取指定工作表关联的 VBA 模块
IVbaModule mod = vbaProject.Modules.GetWorksheetModule(ws);

// 获取模块代码等详细信息......

六、使用 C# 修改 Excel 中的 VBA 宏代码

修改宏时,比较常见的场景是:模板中已经存在某个标准模块,现在需要更新其中的宏逻辑。

下面的示例会打开已有的 .xlsm 文件,查找名为 ReportTools 的标准模块。如果该模块存在,就替换其中的宏代码;如果不存在,则新建一个同名标准模块。

更新后的模块中包含两个宏:

  • FormatSalesReport:格式化销售报表

  • CheckMissingSalesAmount:检查销售额列是否存在空值,并标记出来

cs 复制代码
using Spire.Xls;
using System;

namespace ModifyVbaMacro
{
    internal class Program
    {
        static void Main(string[] args)
        {
            string inputFile = "添加宏.xlsm";
            string outputFile = "修改宏.xlsm";
            string moduleName = "ReportTools";

            Workbook workbook = new Workbook();
            workbook.LoadFromFile(inputFile);

            IVbaProject vbaProject = workbook.VbaProject;

            // 更新 VBA 工程说明,便于后续维护
            vbaProject.Description = "用于格式化和检查销售报表的宏";

            // 查找标准模块
            IVbaModule module = FindStandardModule(vbaProject, moduleName);

            // 如果模块不存在,则新建一个标准模块
            if (module == null)
            {
                module = vbaProject.Modules.Add(moduleName, VbaModuleType.Module);
            }

            // 替换模块中的宏代码
            module.SourceCode = @"
Sub FormatSalesReport()
    Dim ws As Worksheet
    Dim lastRow As Long

    Set ws = ActiveSheet

    lastRow = ws.Cells(ws.Rows.Count, 1).End(xlUp).Row

    If lastRow < 2 Then
        MsgBox ""当前工作表没有可处理的数据。"", vbExclamation, ""提示""
        Exit Sub
    End If

    With ws.Range(""A1:D1"")
        .Font.Bold = True
        .Interior.Color = RGB(217, 225, 242)
        .HorizontalAlignment = xlCenter
    End With

    ws.Range(""D2:D"" & lastRow).NumberFormat = ""#,##0.00""
    ws.Columns(""A:D"").AutoFit

    ws.Range(""F1"").Value = ""销售额合计""
    ws.Range(""F2"").Formula = ""=SUM(D2:D"" & lastRow & "")""
    ws.Range(""F1:F2"").Font.Bold = True

    MsgBox ""销售报表格式化完成。"", vbInformation, ""完成""
End Sub

Sub CheckMissingSalesAmount()
    Dim ws As Worksheet
    Dim lastRow As Long
    Dim i As Long
    Dim missingCount As Long

    Set ws = ActiveSheet
    lastRow = ws.Cells(ws.Rows.Count, 1).End(xlUp).Row

    If lastRow < 2 Then
        MsgBox ""当前工作表没有可检查的数据。"", vbExclamation, ""提示""
        Exit Sub
    End If

    For i = 2 To lastRow
        If Len(Trim(CStr(ws.Cells(i, 4).Value))) = 0 Then
            missingCount = missingCount + 1
            ws.Cells(i, 4).Interior.Color = RGB(255, 230, 153)
        End If
    Next i

    If missingCount > 0 Then
        MsgBox ""发现 "" & missingCount & "" 行缺少销售额,已用颜色标记。"", vbExclamation, ""检查结果""
    Else
        MsgBox ""销售额数据检查完成,未发现缺失值。"", vbInformation, ""检查结果""
    End If
End Sub";

            workbook.SaveToFile(outputFile, FileFormat.Xlsm);
            workbook.Dispose();

            Console.WriteLine("VBA 标准模块已更新。");
        }

        private static IVbaModule FindStandardModule(IVbaProject vbaProject, string moduleName)
        {
            foreach (IVbaModule module in vbaProject.Modules)
            {
                if (module.Name.Equals(moduleName, StringComparison.OrdinalIgnoreCase)
                    && module.Type == VbaModuleType.Module)
                {
                    return module;
                }
            }

            return null;
        }
    }
}

修改结果:


七、使用 C# 删除 Excel 中的 VBA 宏

删除宏时,建议按模块名称删除,而不是按索引删除。按索引删除虽然简单,但模块顺序可能会变化,容易误删其他模块。

下面的示例会检查是否存在名为 ReportTools 的标准模块。如果存在,则删除该模块;如果不存在,则保留文件并输出提示信息。

cs 复制代码
using Spire.Xls;
using System;

namespace DeleteVbaMacro
{
    internal class Program
    {
        static void Main(string[] args)
        {
            string inputFile = "添加宏.xlsm";
            string outputFile = "删除宏.xlsm";
            string moduleName = "ReportTools";

            Workbook workbook = new Workbook();
            workbook.LoadFromFile(inputFile);

            IVbaProject vbaProject = workbook.VbaProject;

            IVbaModule module = FindStandardModule(vbaProject, moduleName);

            if (module != null)
            {
                vbaProject.Modules.Remove(module.Name);
                Console.WriteLine("已删除标准模块:" + module.Name);
            }
            else
            {
                Console.WriteLine("未找到指定标准模块:" + moduleName);
            }

            workbook.SaveToFile(outputFile, FileFormat.Xlsm);
            workbook.Dispose();

            Console.WriteLine("文件已保存。");
        }

        private static IVbaModule FindStandardModule(IVbaProject vbaProject, string moduleName)
        {
            foreach (IVbaModule module in vbaProject.Modules)
            {
                if (module.Name.Equals(moduleName, StringComparison.OrdinalIgnoreCase)
                    && module.Type == VbaModuleType.Module)
                {
                    return module;
                }
            }

            return null;
        }
    }
}

如果确实需要清除整个 VBA 工程中的模块,也可以使用 vbaProject.Modules.Clear()。不过在实际业务中应谨慎使用,因为这会删除工程中的所有模块代码。对于模板文件或历史报表文件,建议先备份,再执行批量删除操作。


八、C# 操作 Excel 宏的实用建议

1. 文件保存格式要正确

包含宏的文件应保存为 `.xlsm`、`.xls`、`.xlsb` 或 `.xltm`。如果保存为 `.xlsx`,宏代码不会被保留。

推荐 .xlsm:

cs 复制代码
workbook.SaveToFile("output.xlsm", FileFormat.Xlsm);

如果确实需要保存为旧版 `.xls`,可以使用:

cs 复制代码
workbook.SaveToFile("output.xls", FileFormat.Version97to2003);

2. 中文内容建议设置 CodePage

如果 VBA 代码中包含中文注释、中文字符串或中文提示信息,可以设置:

cs 复制代码
vbaProject.CodePage = 936;

常见编码包括:

  • 936:简体中文
  • 950:繁体中文
  • 932:日文
  • 949:韩文
  • 1252:西欧语言

3. 配置宏安全规则

C# 写入 VBA 宏代码后,并不表示 Excel 会自动运行宏。用户打开文件时,Excel 可能会根据宏安全设置阻止宏运行。

在企业环境中,如果宏文件来自不受信任位置,Excel 可能会提示启用内容。实际使用时,应根据企业安全策略配置受信任位置、数字签名或宏安全规则。

6. 操作宏代码不等于执行宏

本文示例主要演示的是 VBA 宏工程的创建、读取、修改和删除。也就是说,C# 代码是在处理宏内容,而不是执行宏逻辑。如果需要运行宏,通常需要在 Excel 中手动运行,或者在支持执行 Excel 宏的环境中处理。


九、总结

通过 C#,开发者可以在不依赖 Microsoft Excel Interop 的情况下,对 Excel 文件中的 VBA 宏进行自动化处理。本文介绍了添加、读取、修改和删除 VBA 宏代码的基本方法,并补充说明了 VBA 模块类型、宏启用文件格式以及实际开发中的注意事项。掌握这些内容后,就可以更灵活地在后台服务、批处理任务或自动化系统中管理 Excel 宏文件。

相关推荐
影寂ldy3 小时前
C# 多接口、同名冲突、显式实现、接口继承 完整笔记
java·笔记·c#
诸葛大钢铁3 小时前
如何降低Word文件的体积?压缩Word文件的三种方法
开发语言·c#
专注VB编程开发20年3 小时前
阿里通义灵码插件安装失败
开发语言·ide·c#·visual studio
OliverH-yishuihan4 小时前
Excel中把一列数据转换成逗号隔开的一行
excel
影寂ldy4 小时前
C# 泛型方法
java·前端·c#
caimouse4 小时前
Godot 4.7 内嵌 C# 模块切换到 .NET 9.0 编译指南
c#·.net·godot
z落落12 小时前
C# 泛型方法(原理、类型推断、多泛型参数)+泛型效率(普通类型 VS Object装箱 VS 泛型)
开发语言·c#
rockey62714 小时前
基于AScript的SQL脚本语言发布啦!
sql·c#·.net·script·expression·动态脚本
z落落15 小时前
C# 四种特殊类:抽象类、密封类、静态类、部分类
开发语言·c#