OpenXLSX 是一个 C++ 库,仅支持C++17及以上版本。用于读取、写入、创建和修改 Microsoft Excel文件(.xlsx 格式)。在实际项目里,当你需要在 C++ 中生成报表、导出数据、读取配置表格时,OpenXLSX是一个不错的选择。
1 环境配置
为了确保代码的跨平台兼容性与可移植性,本库采用 CMake 作为核心构建系统。以下是经过严格验证的演示环境配置。
📊 环境总览
| 🖥️ 平台 | 🔧 硬件配置 | 💿 操作系统 | ⚙️ 编译器工具链 |
|---|---|---|---|
| Windows | Intel Core i5‑13490F | Windows 11 Pro | Visual Studio 2019 (MSVC v142) |
| Linux | Raspberry Pi 4B | Debian GNU/Linux 13 (trixie) | gcc/g++ |
📦 下载项目
项目地址:https://github.com/troldal/OpenXLSX
git拉取:
git clone https://github.com/troldal/OpenXLSX.git
⚙️ 编译生成库目录结构
本库采用 CMake 构建,编译完成后将生成标准的开发目录结构。包含头文件、库文件以及 CMake 配置包,可直接被其他项目 find_package引用。
└─OpenXLSX_0_5_0
├─include
│ └─OpenXLSX
│ │ OpenXLSX.hpp
│ │
│ └─headers
│ IZipArchive.hpp
│ OpenXLSX-Exports.hpp
│ ...(此处省略多个.hpp文件)
│
└─lib
│ OpenXLSX.lib
│ OpenXLSXd.lib
│
├─cmake
│ └─OpenXLSX
│ OpenXLSXConfig.cmake
│ OpenXLSXConfigVersion.cmake
│ OpenXLSXTargets-debug.cmake
│ OpenXLSXTargets-release.cmake
│ OpenXLSXTargets.cmake
│
├─OpenXLSX
│ miniz.lib
│ minizd.lib
│ nowide.lib
│ nowided.lib
│ pugixml.lib
│ pugixmld.lib
│
└─pkgconfig
miniz-OpenXLSX.pc
OpenXLSX.pc
pugixml-OpenXLSX.pc
目录说明
| 路径 | 类型 | 作用描述 |
|---|---|---|
| include/ | 头文件 | 存放 API 头文件 |
| lib/*.lib | 库文件 | 编译生成的静态库,d后缀代表 Debug 版本,无后缀为 Release版本 |
| lib/cmake/ | CMake 包 | 供 find_package(OpenXLSX)使用的配置文件,自动处理依赖 |
| lib/OpenXLSX/ | 依赖库 | 库内部打包的第三方依赖(miniz, pugixml, nowide) |
| lib/pkgconfig/ | Pkg-config | 适用于 Linux/Mac 的 .pc文件,用于编译链接参数管理 |
1.1 Windows编译安装
1️⃣ 构建
我们选择 C:/package/OpenXLSX_0_5_0 作为安装目录,并为 Debug 版本添加后缀 d,避免同名库覆盖。
cmake -S . -B build ^
-DCMAKE_INSTALL_PREFIX="C:/package/OpenXLSX_0_5_0" ^
-G "Visual Studio 16 2019" ^
-A x64 ^
-DCMAKE_DEBUG_POSTFIX="d"
说明:-DCMAKE_DEBUG_POSTFIX="d" 会在 Debug 库名字后加上 d,这是因为Windows平台下,Release 和 Debug 编译的库是无法通用的,编译的 Debug 库后面加上 d 用于区分。
关于 x86 平台的说明:如果需要为 32 位 (Win32) 程序构建库,只需在配置时将 -A 参数修改为 Win32 即可。
| 平台 | -A参数值 |
|---|---|
| x64 | -A x64 |
| x86 | -A Win32 |
2️⃣ 编译
# Debug 模式
cmake --build build --config Debug -j 8
# Release 模式
cmake --build build --config Release -j 8
3️⃣ 安装
将编译产物(头文件、库、CMake 配置文件)安装到 C:/package/OpenXLSX_0_5_0目录:
# 安装 Debug 版本
cmake --install build --config Debug
# 安装 Release 版本
cmake --install build --config Release
1.2 Linux编译安装
1️⃣ 构建
我们选择 ~/package/OpenXLSX_0_5_0 作为安装目录。
cmake -S . -B build \
-DCMAKE_INSTALL_PREFIX="~/package/OpenXLSX_0_5_0" \
-DCMAKE_BUILD_TYPE=Release
说明: Linux环境下,需要在配置阶段指定编译类型,Linux下编译的Release和Debug库是可以混用的,因此在构建库的时候,一般指定为Release即可。
2️⃣ 编译
cmake --build build -j 8
3️⃣ 安装
将编译产物(头文件、库、CMake 配置文件)安装到 ~/package/OpenXLSX_0_5_0目录:
cmake --install build
2 常用API
设计理念:Document -> Workbook -> Worksheet -> Cell,逐层访问。
所有操作必须遵循这一层级链条,不可跳级访问
📊 层级调用关系图
┌─────────────┐
│ XLDocument │ (File IO)
└──────┬──────┘
│ .workbook()
┌──────▼──────┐
│ XLWorkbook │ (Sheet Management)
└──────┬──────┘
│ .worksheet()
┌──────▼──────┐
│ XLWorksheet │ (Grid Container)
└──────┬──────┘
│ .cell()
┌──────▼──────┐
│ XLCell │ (Data R/W)
└─────────────┘
2.1 文档操作(XLDocument)
★ 职责:负责文件的生命周期(创建、加载、保存)。
| 序号 | 方法 | 说明 | 异常 |
|---|---|---|---|
| 1 | void create(const std::string& fileName, bool forceOverwrite); | 新建空文档 | forceOverwrite 如果不为 true且 fileName 已存在,create 将抛出XLException异常 |
| 2 | void open(const std::string& fileName); | 打开现有文件 | |
| 3 | void save(); | 保存到原路径 | XLException:OpenXLSX 检查失败;ZipRuntimeError:压缩文件访问失败 |
| 4 | void close(); | 关闭并释放资源 | |
| 5 | bool isOpen(); | 检查是否已打开 | |
| 6 | XLWorkbook workbook() const; | 获取工作簿对象 |
注意:在文档操作前首先要创建XLDocument文档对象,一切文档操作都是在XLDocument文档对象的基础上进行。
XLDocument doc;
用法示例:
// 新建
doc.create("test.xlsx");
// 打开
doc.open("test.xlsx");
// 保存
doc.save();
// 关闭
doc.close();
// 判断文档是否已打开
doc.isOpen()
2.2 工作簿(XLWorkbook)
★ 职责:负责 Sheet 的管理(增、删、查)。
| 序号 | 方法 | 说明 | 异常 |
|---|---|---|---|
| 1 | XLWorksheet worksheet(const std::string& sheetName); | 获取指定名称的Sheet | |
| 2 | void addWorksheet(const std::string& sheetName); | 新增 Sheet | |
| 3 | void deleteSheet(const std::string& sheetName); | 删除 Sheet | XLException: 如果尝试删除工作簿中的最后一个工作表,将抛出异常 |
⚠️ 工作簿默认存在一个名字位"Sheet1"的工作表,工作簿必须至少包含一个工作表。
注意:在工作簿操作前首先要从XLDocument创建XLWorkbook工作簿对象,一切工作簿的操作都是在XLWorkbook工作簿对象的基础上进行。
auto wbk = doc.workbook();
用法示例:
// 获取
auto wks = wbk.worksheet("Sheet1");
// 新建
wbk.addWorksheet("New");
// 删除
wbk.deleteSheet("Old");
2.3 工作表(XLWorksheet )
★ 职责:管理单元格的二维容器及区域遍历,说白了是管理如何找到想要的单元格。
| 序号 | 方法 | 说明 |
|---|---|---|
| 1 | XLCellAssignable cell(uint32_t rowNumber, uint16_t columnNumber) const; | 访问单元格(坐标方式) |
| 2 | XLCellAssignable cell(const std::string& ref) const; | 访问单元格(字符串地址) |
| 3 | XLCellRange range() const; | 获取当前数据区域 |
| 4 | XLCellRange range(std::string const& rangeReference) const; | 获取指定矩形区域 |
| 5 | uint32_t rowCount() const noexcept; | 获取工作表中的行数 |
| 6 | uint16_t columnCount() const noexcept; | 获取工作表中的列数 |
注意:在工作表操作前首先要从XLWorkbook创建XLWorksheet 工作表对象,一切工作表的操作都是在XLWorksheet工作表对象的基础上进行。
auto wks = wbk.worksheet("Sheet1");
用法示例:
// 坐标法
auto wkc = wks.cell(1, 1);
// 字符串地址法
auto wkc = wks.cell("A1");
auto wkrange = wks.range();
auto wkrange = range("A1:C3");
uint32_t row_count = rowCount();
uint16_t column_count = columnCount();
✅ 优先用坐标,不用 "A1"这种表示方法
2.4 单元格(XLCell)
★ 职责:数据的读写与类型解析。
| 序号 | 方法 | 说明 |
|---|---|---|
| 1 | XLCellValueProxy& value(); | 访问单元格(坐标方式) |
| 2 | XLValueType type() const; | 访问单元格(字符串地址) |
| 3 | .get<T>(); | 获取数据 |
注意:在单元格操作前首先要从XLWorksheet 创建XLCell单元格对象,一切单元格的操作都是在XLCell单元格对象的基础上进行。
auto wkc = wks.cell(1, 1);
1️⃣ 写数据
wkc.value() = xxx;
举例:
| 类型 | 示例 |
|---|---|
| 字符串 | wkc.value() = "Name"; |
| 整数 | wkc.value() = 18; |
| 浮点 | wkc.value() = 3.14; |
| 布尔 | wkc.value() = true; |
2️⃣ 读数据
⚠️ 核心原则:先判类型,再取值。
获取数据类型:
auto type = wkc.value().type();
✖下面的写法是错误的,OpenXLSX不支持这种写法。
auto val = wkc.value();
判断类型:
| 类型 | 枚举 |
|---|---|
| 空 | OpenXLSX::XLValueType::Empty |
| 字符串 | OpenXLSX::XLValueType::String |
| 整数 | OpenXLSX::XLValueType::Integer |
| 浮点 | OpenXLSX::XLValueType::Float |
| 布尔 | OpenXLSX::XLValueType::Boolean |
| 错误 | OpenXLSX::XLValueType::Error |
源码定义如下:
enum class XLValueType { Empty, Boolean, Integer, Float, Error, String };
根据数据类型获取数值:
auto wkc = wks.cell(1, 1);
auto type = wkc.value().type();
if (type == OpenXLSX::XLValueType::String) {
std::string s = wkc.value().get<std::string>();
}
else if (type == OpenXLSX::XLValueType::Integer) {
int64_t i = wkc.value().get<int64_t>();
}
else if (type == OpenXLSX::XLValueType::Float) {
double d = wkc.value().get<double>();
}
else if (type == OpenXLSX::XLValueType::Boolean) {
bool b = wkc.value().get<bool>();
}
else if (type == OpenXLSX::XLValueType::Empty) {
// 处理空cell ...
}
else{
// 其它处理 ...
}
3️⃣ 结合工作表方法进行遍历
auto range = wks.range();
for (uint32_t r = 1; r <= range.numRows(); ++r) {
for (uint32_t c = 1; c <= range.numColumns(); ++c) {
auto wkc = wks.cell(r, c);
// ... 数据的获取与处理
}
}
3 OpenXLSX使用示例
📁 测试代码目录结构
└─OpenXLSX
│ CMakeLists.txt
│
├─file
│ read_demo.xlsx
│
├─read
│ CMakeLists.txt
│ read_demo.cpp
│
└─write
CMakeLists.txt
write_demo.cpp
🟦 编写顶层CMake配置文件
我们根据操作系统不同,将 OpenXLSX 的 CMake 配置路径设置不同路径,实际使用时请替换为你自己的安装位置。
- Windows: C:/package/OpenXLSX_0_5_0/lib/cmake/OpenXLSX
- Linux : ~/package/OpenXLSX_0_5_0/lib/cmake/OpenXLSX
cmake_minimum_required(VERSION 3.25)
project(TestOpenXLSX VERSION 1.0.0 LANGUAGES CXX)
if(NOT WIN32)
set(LIBRARY_PATH "~/package")
else()
set(LIBRARY_PATH "C:/package")
endif()
# Find dependency OpenXLSX
set(OpenXLSX_DIR "${LIBRARY_PATH}/OpenXLSX_0_5_0/lib/cmake/OpenXLSX")
find_package(OpenXLSX CONFIG REQUIRED)
if(OpenXLSX_FOUND)
message( NOTICE "FOUND OpenXLSX" )
get_target_property(OpenXLSX_INCLUDES OpenXLSX::OpenXLSX INTERFACE_INCLUDE_DIRECTORIES)
message( NOTICE "OpenXLSX_INCLUDES is ${OpenXLSX_INCLUDES}" )
else()
message( FATAL_ERROR "MISSING DEPENDENCY OpenXLSX" )
endif()
add_subdirectory(write)
add_subdirectory(read)
2.1 写Excel示例
✍️ 写Excel CMake配置文件
cmake_minimum_required(VERSION 3.25)
project(write_demo CXX)
add_executable(${PROJECT_NAME})
target_sources(${PROJECT_NAME} PRIVATE ${PROJECT_SOURCE_DIR}/write_demo.cpp )
# Configure linkage for ${PROJECT_NAME}
target_link_libraries(${PROJECT_NAME} PRIVATE OpenXLSX::OpenXLSX)
target_include_directories(${PROJECT_NAME} PRIVATE ${OpenXLSX_INCLUDES})
target_compile_features(${PROJECT_NAME} PRIVATE cxx_std_17)
注意: OpenXLSX必须在C++17及以上才可使用。
✍️ 示例代码
下面这段代码演示了:创建一个 Excel 文档,增加删除sheet,写入不同类型的数据(浮点数、整数、字符串、布尔值),并以覆盖方式保存。代码注释标注出了与常用API的对应关系。
OpenXLSX有两种索引方式:
- cell("A1"):代表A列第1个单元格
- sheet.cell(1, 2):代表第1行第2个单元格
#include "OpenXLSX.hpp"
#include <string>
#include <vector>
#include <iostream>
void WriteExcel(const std::string& filename) {
// Create an Excel document
OpenXLSX::XLDocument doc;
// XLDocument 1 : void create(const std::string & fileName, bool forceOverwrite);
doc.create(filename, OpenXLSX::XLForceOverwrite);
// XLDocument 5 : bool isOpen();
if (!doc.isOpen()) return;
// XLDocument 6 : XLWorkbook workbook() const;
auto wbk = doc.workbook();
// XLWorkbook 2 : void addWorksheet(const std::string& sheetName);
wbk.addWorksheet("website");
// XLWorkbook 3 : void deleteSheet(const std::string& sheetName);
wbk.deleteSheet("Sheet1");
// XLWorkbook 1 : XLWorksheet worksheet(const std::string& sheetName);
auto wks = wbk.worksheet("website");
// XLWorksheet 1 : XLCellAssignable cell(uint32_t rowNumber, uint16_t columnNumber) const;
auto wkc = wks.cell(1, 1);
// XLCell 1 : XLCellValueProxy& value();
wkc.value() = "https://pgmpocket.com/";
// chain
wks.cell("A1").value() = "https://pgmpocket.com/";
wks.cell(1, 2).value() = "quanshuang";
wks.cell("C1").value() = 2026;
wks.cell(1, 4).value() = 3.14;
wks.cell(1, 5).value() = true;
// XLDocument 3 : void save();
doc.save();
// XLDocument 4 : void close();
doc.close();
}
int main() {
WriteExcel("write_demo.xlsx");
return 0;
}
2.2 读Excel示例
✍️ 读Excel CMake配置文件
cmake_minimum_required(VERSION 3.25)
project(read_demo CXX)
add_executable(${PROJECT_NAME})
target_sources(${PROJECT_NAME} PRIVATE ${PROJECT_SOURCE_DIR}/read_demo.cpp )
# Configure linkage for ${PROJECT_NAME}
target_link_libraries(${PROJECT_NAME} PRIVATE OpenXLSX::OpenXLSX)
target_include_directories(${PROJECT_NAME} PRIVATE ${OpenXLSX_INCLUDES})
target_compile_features(${PROJECT_NAME} PRIVATE cxx_std_17)
注意:OpenXLSX必须在C++17及以上才可使用。
✍️ 示例代码
下面这段代码演示了:读取一个 Excel 文档,由 main 函数接收一个传入参数,该参数为 Excel 文档路径。
与写 Excel 相同,OpenXLSX有两种索引方式:
- cell("A1"):代表A列第1个单元格
- sheet.cell(1, 2):代表第1行第2个单元格
#include "OpenXLSX.hpp"
#include <iostream>
#include <string>
struct Website {
std::string url;
std::string author;
int year;
int month;
};
void DirectReadExcel(std::string& filename) {
OpenXLSX::XLDocument doc;
// XLDocument 2 : void open(const std::string& fileName);
doc.open(filename);
// XLDocument 5 : bool isOpen();
if (!doc.isOpen()) return;
// XLDocument 6 : XLWorkbook workbook() const;
auto wbk = doc.workbook();
// XLWorkbook 1 : XLWorksheet worksheet(const std::string& sheetName);
auto wks = wbk.worksheet("website");
Website website{};
// XLWorksheet 1 : XLCellAssignable cell(uint32_t rowNumber, uint16_t columnNumber) const;
auto wkc = wks.cell("A2");
// XLWorksheet 1 : XLCellValueProxy& value();
// XLWorksheet 3 : .get\<T\>();
website.url = wkc.value().get<std::string>();
website.author = wks.cell(2, 2).value().get<std::string>();
website.year = wks.cell("C2").value().get<int>();
// XLWorksheet 2 : XLValueType type() const;
if (wks.cell(2, 4).value().type() == OpenXLSX::XLValueType::Integer) {
website.month = wks.cell(2, 4).value().get<int>();
}
// XLDocument 4 : void close();
doc.close();
std::cout << "DirectReadExcel : " << std::endl;
std::cout << "url : " << website.url << '\n'
<< "author: " << website.author << '\n'
<< "year : " << website.year << '\n'
<< "month : " << website.month << std::endl;
std::cout << std::endl;
}
void RangeReadExcel(std::string& filename) {
OpenXLSX::XLDocument doc;
// XLDocument 2 : void open(const std::string& fileName);
doc.open(filename);
// XLDocument 5 : bool isOpen();
if (!doc.isOpen()) return;
// XLDocument 6 : XLWorkbook workbook() const;
auto wbk = doc.workbook();
// XLWorkbook 1 : XLWorksheet worksheet(const std::string& sheetName);
auto wks = wbk.worksheet("website");
// XLWorksheet 3 : XLCellRange range() const;
auto range = wks.range();
std::cout << "RangeReadExcel : " << std::endl;
for (uint32_t r = 1; r <= range.numRows(); ++r) {
for (uint32_t c = 1; c <= range.numColumns(); ++c) {
auto wkc = wks.cell(r, c);
auto type = wkc.value().type();
if (type == OpenXLSX::XLValueType::String) {
std::cout << wkc.value().get<std::string>() << " ";
}
else if (type == OpenXLSX::XLValueType::Integer) {
std::cout << wkc.value().get<int64_t>() << " ";
}
}
std::cout << std::endl;
}
// XLDocument 4 : void close();
doc.close();
}
int main(int argc, char *argv[]){
if (argc != 2) {
std::cerr << "Usage:" << argv[0] << " <filename>" << std::endl;
return 1;
}
std::string filename = argv[1]; // Obtain the filename from command line arguments
DirectReadExcel(filename);
RangeReadExcel(filename);
getchar();
return 0;
}
2.3 编译运行
Windows和Linux在编译和运行上面稍有差异,因此本文会分别演示两种系统的操作流程。
2.3.1 Windows平台编译运行
首先进入项目目录:
1️⃣ 构建
cmake -S . -B build
2️⃣ 编译
可选择编译 Debug 版本或 Release版本
- Debug模式
cmake --build build --config Debug
- Release模式
cmake --build build --config Release
3️⃣ 运行
- 运行写 Excel 程序
找到write_demo.exe可执行文件,直接运行即可,运行结束后在程序目录下获得 write_demo.xlsx 文件,用 Excel 打开后将看到执行结果。 - 运行读 Excel 程序
找到read_demo.exe可执行文件,不可直接运行,需要传入 read_demo.xlsx 文件路径。./read_demo.exe xxxxxxx/read_demo.xlsx
注意:其中xxxxxxx需要替换为 read_demo.xlsx 的实际路径。
运行结束后终端会打印运行结果。
2.3.2 Linux平台编译运行
首先进入项目目录:
1️⃣ 构建
Linux平台下是在构建时选择 Release 或 Debug
- Release模式
cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug=Release
- Debug模式
cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug
2️⃣ 编译
cmake --build build
3️⃣ 运行
- 运行写 Excel 程序
找到 write_demo 可执行文件,直接运行即可,运行结束后在程序目录下获得 write_demo.xlsx 文件,用 Excel 打开后将看到执行结果(可能系统未安装Excel软件,无法正常打开,为验证效果,可将 write_demo.xlsx 文件拷贝到Windows环境下查看运行效果)。 - 运行读 Excel 程序
找到 read_demo 可执行文件,不可直接运行,需要传入 read_demo.xlsx 文件路径。./read_demo xxxxxxx/read_demo.xlsx
注意:其中xxxxxxx需要替换为 read_demo.xlsx 的实际路径。
运行结束后终端会打印运行结果。
3 总结
OpenXLSX 为 C++ 开发者提供了一种轻量、现代、跨平台的操作 Excel 文件的方式。
它彻底摆脱了对 Microsoft Excel 应用程序的依赖,仅通过一个头文件和少量静态库就能完成 .xlsx 的创建、读写与修改。配合 CMake 的 find_package 机制,它可以像标准库一样无缝集成到现有项目中,极大地降低了数据报表、记录导出等场景的开发成本。
如果你正在寻找一个 C++ 下简单可靠的 Excel 文件处理库,OpenXLSX 绝对值得一试。