C语言如何操作SQLServer生成PDF?

99ANYc3cd6
预计阅读时长 44 分钟
位置: 首页 C语言 正文

下面我将为您详细拆解这个过程,并提供一个完整的、可运行的示例。

c语言 sqlserver pdf
(图片来源网络,侵删)

整个过程可以分为三个主要步骤:

  1. C语言连接SQL Server:使用一个C语言的数据库连接库(最常用的是 ODBC)来连接到您的SQL Server数据库,并执行SQL查询语句,获取数据。
  2. 数据处理:将查询到的数据存储在C语言的数据结构中(如结构体数组),进行必要的处理和格式化。
  3. 生成PDF文件:使用一个C语言的PDF生成库(最常用的是 libharu)来创建一个PDF文档,将处理后的数据以表格或文本的形式写入PDF文件,并最终保存到磁盘。

第一步:环境准备

在开始编码之前,您需要准备好所有必需的软件和库。

SQL Server 环境

确保您有一台可以访问的SQL Server数据库(可以是本地实例或远程服务器),并且有一个测试用的表和数据。

示例表结构:

c语言 sqlserver pdf
(图片来源网络,侵删)
-- 在SQL Server中创建一个名为 Customers 的表
CREATE TABLE Customers (
    CustomerID INT PRIMARY KEY,
    CustomerName NVARCHAR(100),
    City NVARCHAR(50),
    OrderDate DATE,
    Amount DECIMAL(10, 2)
);
-- 插入一些测试数据
INSERT INTO Customers VALUES
(1, '张三', '北京', '2025-10-25', 1500.50),
(2, '李四', '上海', '2025-10-26', 2300.00),
(3, '王五', '广州', '2025-10-27', 899.99),
(4, '赵六', '深圳', '2025-10-28', 4500.00),
(5, '钱七', '北京', '2025-10-29', 3200.75);

C语言开发环境

  • 编译器: Visual Studio (Windows下最方便), GCC (Linux/MinGW), Clang 等。
  • 包管理器 (推荐): vcpkg 可以极大地简化第三方库的安装。

安装必要的库

a) ODBC 驱动

这是C语言连接SQL Server的桥梁。

  • Windows: 微软官方提供了ODBC驱动,最简单的方式是安装 ODBC Driver 17 for SQL Server,安装后,在“ODBC 数据源管理器”中可以配置DSN(数据源名称),但我们的代码将使用更灵活的“连接字符串”方式,所以配置DSN不是必须的。
  • Linux (Ubuntu/Debian):
    sudo apt-get update
    sudo apt-get install unixodbc-dev tdsodbc msodbcsql17

    msodbcsql17 是微软的官方驱动。

b) libharu 库

这是一个用于生成PDF文件的C语言库,功能强大且免费。

  • 使用 vcpkg 安装 (推荐):
    # 在vcpkg根目录下执行
    ./vcpkg install haru
  • 手动安装: 从 libharu官网 下载源码,按照其文档进行编译安装。

c) 其他依赖

  • Windows: 通常包含在Visual Studio中。
  • Linux:
    sudo apt-get install build-essential cmake

第二步:C语言代码实现

下面是一个完整的C语言示例代码,它实现了:

c语言 sqlserver pdf
(图片来源网络,侵删)
  1. 连接到SQL Server。
  2. 执行查询,获取 Customers 表的数据。
  3. 使用 libharu 将这些数据生成一个名为 report.pdf 的PDF文件。

代码文件:generate_report.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sql.h>
#include <sqlext.h> // 包含 SQLExtendedFetch 等扩展函数
// libharu 头文件
#include <hpdf.h>
// 定义客户结构体,用于存储从数据库获取的数据
typedef struct {
    int id;
    char name[101];
    char city[51];
    char order_date[11]; // YYYY-MM-DD
    double amount;
} Customer;
// ODBC 相关变量
SQLHENV      henv;   // 环境句柄
SQLHDBC      hdbc;   // 连接句柄
SQLHSTMT     hstmt;  // 语句句柄
// libharu 相关变量
HPDF_Doc     pdf;
HPDF_Page    page;
HPDF_Font    font;
HPDF_Font    bold_font;
// 函数声明
void connect_to_db();
void disconnect_from_db();
void fetch_data_from_db(Customer **customers, int *count);
void generate_pdf_report(Customer *customers, int count);
void handle_odbc_error(const char *function, SQLHANDLE handle, SQLSMALLINT type);
int main() {
    Customer *customers = NULL;
    int customer_count = 0;
    printf("开始连接数据库...\n");
    connect_to_db();
    printf("数据库连接成功,\n");
    printf("正在从数据库获取数据...\n");
    fetch_data_from_db(&customers, &customer_count);
    printf("成功获取 %d 条客户记录,\n", customer_count);
    if (customer_count > 0) {
        printf("正在生成PDF报表...\n");
        generate_pdf_report(customers, customer_count);
        printf("PDF报表 'report.pdf' 生成成功!\n");
    } else {
        printf("没有数据可生成报表,\n");
    }
    // 释放内存
    free(customers);
    printf("正在断开数据库连接...\n");
    disconnect_from_db();
    printf("数据库连接已断开,\n");
    return 0;
}
// 连接到 SQL Server
void connect_to_db() {
    SQLRETURN ret;
    // 分配环境句柄
    ret = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &henv);
    if (!SQL_SUCCEEDED(ret)) {
        handle_odbc_error("SQLAllocHandle (ENV)", henv, SQL_HANDLE_ENV);
        exit(1);
    }
    // 设置ODBC版本为3.0
    SQLSetEnvAttr(henv, SQL_ATTR_ODBC_VERSION, (SQLPOINTER)SQL_OV_ODBC3, 0);
    // 分配连接句柄
    ret = SQLAllocHandle(SQL_HANDLE_DBC, henv, &hdbc);
    if (!SQL_SUCCEEDED(ret)) {
        handle_odbc_error("SQLAllocHandle (DBC)", hdbc, SQL_HANDLE_DBC);
        exit(1);
    }
    // 连接字符串 - 请根据您的实际情况修改
    // Server=.; 表示本地默认实例
    // UID=sa; PWD=your_password; 请替换为您的用户名和密码
    SQLCHAR conn_str[] = "DRIVER={ODBC Driver 17 for SQL Server};SERVER=.;DATABASE=YourDBName;UID=sa;PWD=your_password;";
    // 连接数据库
    ret = SQLDriverConnect(hdbc, NULL, conn_str, SQL_NTS, NULL, 0, NULL, SQL_DRIVER_COMPLETE);
    if (!SQL_SUCCEEDED(ret)) {
        handle_odbc_error("SQLDriverConnect", hdbc, SQL_HANDLE_DBC);
        exit(1);
    }
}
// 断开连接
void disconnect_from_db() {
    if (hdbc) SQLDisconnect(hdbc);
    if (hstmt) SQLFreeHandle(SQL_HANDLE_STMT, hstmt);
    if (hdbc) SQLFreeHandle(SQL_HANDLE_DBC, hdbc);
    if (henv) SQLFreeHandle(SQL_HANDLE_ENV, henv);
}
// 从数据库获取数据
void fetch_data_from_db(Customer **customers, int *count) {
    SQLRETURN ret;
    SQLCHAR sql[] = "SELECT CustomerID, CustomerName, City, OrderDate, Amount FROM Customers";
    SQLLEN ind_or_len; // 用于获取字符串或二进制数据的长度
    // 分配语句句柄
    ret = SQLAllocHandle(SQL_HANDLE_STMT, hdbc, &hstmt);
    if (!SQL_SUCCEEDED(ret)) {
        handle_odbc_error("SQLAllocHandle (STMT)", hstmt, SQL_HANDLE_STMT);
        exit(1);
    }
    // 执行SQL查询
    ret = SQLExecDirect(hstmt, sql, SQL_NTS);
    if (!SQL_SUCCEEDED(ret)) {
        handle_odbc_error("SQLExecDirect", hstmt, SQL_HANDLE_STMT);
        exit(1);
    }
    // 绑定列到变量
    // SQLBindCol 的参数顺序是: 句柄, 列号, C数据类型, 变量地址, 变量长度, 长度/指示器指针
    SQLBindCol(hstmt, 1, SQL_C_LONG, &(*customers)[0].id, 0, NULL);
    SQLBindCol(hstmt, 2, SQL_C_CHAR, (*customers)[0].name, sizeof((*customers)[0].name), &ind_or_len);
    SQLBindCol(hstmt, 3, SQL_C_CHAR, (*customers)[0].city, sizeof((*customers)[0].city), &ind_or_len);
    SQLBindCol(hstmt, 4, SQL_C_CHAR, (*customers)[0].order_date, sizeof((*customers)[0].order_date), &ind_or_len);
    SQLBindCol(hstmt, 5, SQL_C_DOUBLE, &(*customers)[0].amount, 0, NULL);
    // 先获取总行数 (一种简单方法,并非所有驱动都支持,这里我们采用循环获取的方式)
    // 更稳健的方法是先分配一个初始数组,然后不够时realloc
    // 为了简化,我们这里先查询一次得到行数,然后再分配内存
    ret = SQLRowCount(hstmt, (SQLLEN*)count);
    if (!SQL_SUCCEEDED(ret) || *count <= 0) {
        *count = 0; // 如果无法获取行数或为0,则设为0
    }
    // 分配内存
    *customers = (Customer*)malloc(*count * sizeof(Customer));
    if (*customers == NULL) {
        fprintf(stderr, "内存分配失败\n");
        exit(1);
    }
    // 重新绑定,因为数组地址变了
    SQLBindCol(hstmt, 1, SQL_C_LONG, &(*customers)[0].id, 0, NULL);
    SQLBindCol(hstmt, 2, SQL_C_CHAR, (*customers)[0].name, sizeof((*customers)[0].name), &ind_or_len);
    SQLBindCol(hstmt, 3, SQL_C_CHAR, (*customers)[0].city, sizeof((*customers)[0].city), &ind_or_len);
    SQLBindCol(hstmt, 4, SQL_C_CHAR, (*customers)[0].order_date, sizeof((*customers)[0].order_date), &ind_or_len);
    SQLBindCol(hstmt, 5, SQL_C_DOUBLE, &(*customers)[0].amount, 0, NULL);
    // 获取数据
    int i = 0;
    while (SQL_SUCCEEDED(SQLFetch(hstmt))) {
        if (i >= *count) { // 防止越界,理论上不会发生
            break;
        }
        // SQLBindCol 已经将数据拷贝到结构体中了,这里只需要递增索引
        i++;
    }
    *count = i; // 更新实际获取的行数
    // 释放语句句柄
    SQLFreeHandle(SQL_HANDLE_STMT, hstmt);
    hstmt = NULL;
}
// 生成PDF报表
void generate_pdf_report(Customer *customers, int count) {
    HPDF_Doc  pdf;
    HPDF_Page page;
    HPDF_Font font;
    HPDF_Font bold_font;
    HPDF_REAL page_height;
    HPDF_REAL y_pos;
    const char *filename = "report.pdf";
    int i;
    // 1. 创建PDF文档
    pdf = HPDF_New(NULL, NULL);
    if (!pdf) {
        printf("错误: 无法创建PDF文档\n");
        return;
    }
    // 2. 添加一页
    page = HPDF_AddPage(pdf);
    HPDF_Page_SetSize(page, HPDF_PAGE_SIZE_A4, HPDF_PAGE_PORTRAIT);
    // 3. 加载字体
    // libharu自带了Helvetica等字体,Windows系统下也可以加载本地字体
    font = HPDF_GetFont(pdf, "Helvetica", NULL);
    bold_font = HPDF_GetFont(pdf, "Helvetica-Bold", NULL);
    if (!font || !bold_font) {
        printf("错误: 无法加载字体\n");
        HPDF_Free(pdf);
        return;
    }
    page_height = HPDF_Page_GetHeight(page);
    // 4. 绘制内容
    y_pos = page_height - 50; // 从顶部50像素开始
    // 标题
    HP_Page_SetTextLeading(page, 20);
    HPDF_Page_SetFontAndSize(page, bold_font, 18);
    HPDF_Page_BeginText(page);
    HPDF_Page_TextOut(page, 50, y_pos, "客户销售报表");
    HPDF_Page_EndText(page);
    y_pos -= 40; // 标题下移
    // 表头
    HPDF_Page_SetFontAndSize(page, bold_font, 12);
    HPDF_Page_BeginText(page);
    HPDF_Page_TextOut(page, 50, y_pos, "ID");
    HPDF_Page_TextOut(page, 100, y_pos, "客户名称");
    HPDF_Page_TextOut(page, 220, y_pos, "城市");
    HPDF_Page_TextOut(page, 300, y_pos, "订单日期");
    HPDF_Page_TextOut(page, 420, y_pos, "金额");
    HPDF_Page_EndText(page);
    y_pos -= 20; // 表头下移
    // 绘制一条横线
    HPDF_Page_SetLineWidth(page, 1);
    HPDF_Page_MoveTo(page, 50, y_pos);
    HPDF_Page_LineTo(page, 550, y_pos);
    HPDF_Page_Stroke(page);
    y_pos -= 5; // 线条下移,为数据留出空间
    // 表格数据
    HPDF_Page_SetFontAndSize(page, font, 12);
    for (i = 0; i < count; i++) {
        HPDF_Page_BeginText(page);
        char amount_str[32];
        sprintf(amount_str, "%.2f", customers[i].amount);
        HPDF_Page_TextOut(page, 50, y_pos, customers[i].id);
        HPDF_Page_TextOut(page, 100, y_pos, customers[i].name);
        HPDF_Page_TextOut(page, 220, y_pos, customers[i].city);
        HPDF_Page_TextOut(page, 300, y_pos, customers[i].order_date);
        HPDF_Page_TextOut(page, 420, y_pos, amount_str);
        HPDF_Page_EndText(page);
        y_pos -= 20; // 每行数据下移
        // 检查是否需要换页
        if (y_pos < 50) {
            HPDF_Page_EndText(page);
            page = HPDF_AddPage(pdf);
            y_pos = page_height - 50;
            HPDF_Page_SetFontAndSize(page, font, 12);
            HPDF_Page_BeginText(page);
        }
    }
    // 5. 保存PDF文件
    HPDF_SaveToFile(pdf, filename);
    HPDF_Free(pdf);
}
// ODBC 错误处理函数
void handle_odbc_error(const char *function, SQLHANDLE handle, SQLSMALLINT type) {
    SQLCHAR sqlstate[6], message[SQL_MAX_MESSAGE_LENGTH];
    SQLINTEGER native;
    SQLSMALLINT len, i = 1;
    fprintf(stderr, "错误在函数: %s\n", function);
    while (SQLGetDiagRec(type, handle, i, sqlstate, &native, message, sizeof(message), &len) == SQL_SUCCESS) {
        fprintf(stderr, "SQLSTATE: %s, Message: %s\n", sqlstate, message);
        i++;
    }
    exit(1);
}

第三步:编译与运行

在 Windows (Visual Studio) 中编译

  1. 创建一个新的C++空项目(即使你写的是C代码,用C++项目编译兼容性更好)。
  2. generate_report.c 文件添加到项目中。
  3. 配置项目属性:
    • 配置属性 -> 链接器 -> 输入 -> 附加依赖项: 添加 odbc32.libodbccp32.lib
    • 配置属性 -> C/C++ -> 常规 -> 附加包含目录: 如果libharu是手动编译的,添加其 include 目录路径。
    • 配置属性 -> 链接器 -> 常规 -> 附加库目录: 添加libharu的 lib 目录路径。
  4. 确保你的项目配置为使用 Release 模式,并选择正确的平台(如 x64)。
  5. F5Ctrl+F5 编译并运行。

在 Linux (GCC) 中编译

假设你使用了 vcpkg 安装了依赖,编译命令会非常简单。

# 编译命令
# -I 指定头文件目录
# -L 指定库文件目录
# -l 指定要链接的库
# $(pkg-config --cflags --libs odbc) 会自动帮你找到ODBC的编译和链接参数
gcc generate_report.c -o generate_report \
    -I/path/to/vcpkg/installed/x64-linux/include \
    -L/path/to/vcpkg/installed/x64-linux/lib \
    -lharu \
    $(pkg-config --cflags --libs odbc)
# 运行
./generate_report

注意: 请将 /path/to/vcpkg/... 替换为你实际的vcpkg安装路径。

运行结果

成功运行程序后,你会在程序所在目录下找到一个名为 report.pdf 的文件,用PDF阅读器打开它,你会看到一个格式良好的客户销售报表。

总结与最佳实践

  • 错误处理: 代码中的 handle_odbc_error 是一个基本的错误处理模板,在生产环境中,你需要更健壮的错误处理和日志记录机制。
  • 内存管理: 使用 malloc 分配的内存,一定要记得用 free 释放,避免内存泄漏。
  • 安全性: 连接字符串中直接包含用户名和密码,在实际应用中,应该使用更安全的方式(如配置文件、环境变量或密钥管理服务)来存储敏感信息。
  • PDF高级功能: libharu 支持更多高级功能,如:
    • 表格线: 绘制完整的表格边框。
    • 图片: 将Logo等图片嵌入PDF。
    • 页眉页脚: 添加页码、公司名称等。
    • 字体: 支持加载TrueType字体(如宋体、黑体)以更好地支持中文。
    • 加密: 设置PDF的打开密码和权限密码。

这个完整的示例为你提供了一个坚实的基础,你可以根据实际业务需求进行修改和扩展。

-- 展开阅读全文 --
头像
dede5.7模板新闻源码安全吗?好用吗?
« 上一篇 昨天
dede tag路径如何改为拼音?
下一篇 » 昨天

相关文章

取消
微信二维码
支付宝二维码

目录[+]