Apache APR 构建并运行示例应用指南

Apache APR 示例应用构建与运行指南:给出内存池与字符串、文件读写、UUID 生成与解析三个完整 C 示例的源码、apr-1-config/apu-1-config 编译命令与真实运行回显,并附统一 Makefile 构建与共享库链接排障实录,全程在 Ubuntu 22.04 实测。

Apache APRApache APR2026/10/12

Apache APR 构建并运行示例应用指南

目标:从零构建并运行三个基于 Apache APR 1.7.6 / apr-util 1.6.5 的 C 语言示例程序——内存池与字符串 API(demo_pool)、文件读写(demo_file)、UUID 生成与解析(demo_uuid),给出完整源码、编译命令与真实运行回显,并沉淀「编译通过但运行时报共享库找不到」的链接排障实录与统一 Makefile 构建方式。所有命令与回显均在 Ubuntu 22.04.5 实测通过。

一、环境信息

  • 操作系统:Ubuntu 22.04.5 LTS(VERSION_CODENAME=jammy)
  • 架构:x86_64(内核 6.8.0-138-generic)
  • 编译器:gcc 11.4.0
  • APR 版本:apr-1.7.6 + apr-util-1.6.5(安装前缀 /usr/local/apr,安装方法见《Apache APR 安装指南》)
  • 示例工程目录:/root/apr-demo/
  • 演示账号:root

二、前置条件

  1. 已按《Apache APR 安装指南》完成 apr-1.7.6 与 apr-util-1.6.5 源码编译安装,并完成共享库注册(/etc/ld.so.conf.d/apr.conf + ldconfig)。
  2. 验证编译参数查询工具可用:
/usr/local/apr/bin/apr-1-config --version
/usr/local/apr/bin/apu-1-config --version

实测回显:

1.7.6
1.6.5
  1. 创建示例工程目录:
mkdir -p /root/apr-demo

三、示例工程总览

三个示例各对应一个独立 C 源文件,覆盖 APR 最常用的三类能力:

/root/apr-demo/
├── demo_pool.c      # 示例1:内存池 + 字符串 API
├── demo_file.c      # 示例2:apr_file_open 文件读写
└── demo_uuid.c      # 示例3:apr-util 的 apr_uuid
示例依赖库覆盖 API
demo_poollibapr-1apr_initialize / apr_pool_create / apr_pstrcat / apr_psprintf / apr_pstrdup / apr_palloc
demo_filelibapr-1apr_file_open / apr_file_write / apr_file_read / apr_file_info_get / apr_file_rename / apr_file_remove
demo_uuidlibapr-1 + libaprutil-1apr_uuid_get / apr_uuid_format / apr_uuid_parse

💡 说明:编译 APR 程序的参数不手工拼写,统一由 apr-1-config / apu-1-config 查询获得:--cflags --cppflags --includes 提供编译参数(-g -O2 -DLINUX -D_REENTRANT -D_GNU_SOURCE -I/usr/local/apr/include/apr-1),--link-ld --libs 提供链接参数(-L/usr/local/apr/lib -lapr-1 与 -laprutil-1 -lexpat 等)。

四、示例一:内存池与字符串 API(demo_pool.c)

4.1 完整源码

内存池(memory pool)是 APR 的核心设计:程序从池上批量分配内存,销毁池时一次性释放,无需逐个 free。源码 demo_pool.c:

/* demo_pool.c:内存池 + 字符串 API 示例 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <apr_general.h>
#include <apr_pools.h>
#include <apr_strings.h>

int main(int argc, const char *argv[])
{
    apr_status_t rv;
    apr_pool_t *pool;

    /* APR 全局初始化 */
    rv = apr_initialize();
    if (rv != APR_SUCCESS) {
        char errbuf[256];
        apr_strerror(rv, errbuf, sizeof(errbuf));
        fprintf(stderr, "apr_initialize failed: %s\n", errbuf);
        return 1;
    }
    atexit(apr_terminate);

    /* 创建根内存池 */
    rv = apr_pool_create(&pool, NULL);
    if (rv != APR_SUCCESS) {
        fprintf(stderr, "apr_pool_create failed\n");
        return 1;
    }

    /* 池上分配:统一随池销毁释放,无需逐个 free */
    char *msg = apr_pstrcat(pool, "Hello, ", "APR ", "pool!", NULL);
    char *line = apr_psprintf(pool, "psprintf demo: %d + %d = %d", 2, 3, 5);
    char *copy = apr_pstrdup(pool, msg);
    void *buf = apr_palloc(pool, 128);

    printf("%s\n", msg);
    printf("%s\n", line);
    printf("pstrdup equals msg: %s\n", strcmp(copy, msg) == 0 ? "yes" : "no");
    printf("apr_palloc(128) OK, pool addr=%p\n", (void *)pool);

    /* 一次性回收池内所有内存 */
    apr_pool_destroy(pool);
    return 0;
}

4.2 编译与运行

cd /root/apr-demo
gcc demo_pool.c -o demo_pool $(/usr/local/apr/bin/apr-1-config --cflags --cppflags --includes --link-ld --libs)
./demo_pool

实测回显(退出码 0;pool addr 为进程地址,每次运行可能不同):

# ./demo_pool
Hello, APR pool!
psprintf demo: 2 + 3 = 5
pstrdup equals msg: yes
apr_palloc(128) OK, pool addr=0x5641976153b8

五、示例二:文件读写(demo_file.c)

5.1 完整源码

演示 apr_file_open 以不同标志打开文件、写入 UTF-8 中文内容、读取与获取文件大小、重命名与删除。源码 demo_file.c:

/* demo_file.c:apr_file_open 文件读写示例 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <apr_general.h>
#include <apr_pools.h>
#include <apr_file_io.h>
#include <apr_file_info.h>

int main(int argc, const char *argv[])
{
    apr_status_t rv;
    apr_pool_t *pool;
    apr_file_t *fp = NULL;
    const char *path = "/root/apr-demo/hello.txt";
    char errbuf[256];

    apr_initialize();
    atexit(apr_terminate);
    apr_pool_create(&pool, NULL);

    /* 1) 以创建|写入|截断方式打开文件 */
    rv = apr_file_open(&fp, path, APR_CREATE | APR_WRITE | APR_TRUNCATE,
                       APR_OS_DEFAULT, pool);
    if (rv != APR_SUCCESS) {
        apr_strerror(rv, errbuf, sizeof(errbuf));
        fprintf(stderr, "apr_file_open(write) failed: %s\n", errbuf);
        return 1;
    }

    /* 2) 写入两行文本 */
    const char *text = "Apache APR file I/O demo.\n第二行:UTF-8 中文内容。\n";
    apr_size_t len = strlen(text);
    rv = apr_file_write(fp, text, &len);
    printf("apr_file_write wrote %ld bytes\n", (long)len);
    apr_file_close(fp);

    /* 3) 取文件元信息并读回内容 */
    rv = apr_file_open(&fp, path, APR_READ, APR_OS_DEFAULT, pool);
    if (rv != APR_SUCCESS) {
        apr_strerror(rv, errbuf, sizeof(errbuf));
        fprintf(stderr, "apr_file_open(read) failed: %s\n", errbuf);
        return 1;
    }
    apr_finfo_t finfo;
    apr_file_info_get(&finfo, APR_FINFO_SIZE, fp);
    printf("file size: %ld bytes\n", (long)finfo.size);

    char buf[256];
    apr_size_t nread = sizeof(buf) - 1;
    rv = apr_file_read(fp, buf, &nread);
    buf[nread] = 0;
    printf("read %ld bytes:\n%s", (long)nread, buf);
    apr_file_close(fp);

    /* 4) 重命名后原路径应不存在 */
    apr_file_rename(path, "/root/apr-demo/hello-renamed.txt", pool);
    rv = apr_file_open(&fp, path, APR_READ, APR_OS_DEFAULT, pool);
    if (rv == APR_ENOENT) {
        printf("old path gone after rename (rv=%d)\n", (int)rv);
    } else {
        printf("old path still openable (rv=%d)\n", (int)rv);
    }
    apr_file_remove("/root/apr-demo/hello-renamed.txt", pool);
    apr_pool_destroy(pool);
    return 0;
}

5.2 编译与运行

cd /root/apr-demo
gcc demo_file.c -o demo_file $(/usr/local/apr/bin/apr-1-config --cflags --cppflags --includes --link-ld --libs)
./demo_file

实测回显(退出码 0;运行后测试文件已被程序删除,目录仅剩源码与可执行文件):

# ./demo_file
apr_file_write wrote 60 bytes
file size: 60 bytes
read 60 bytes:
Apache APR file I/O demo.
第二行:UTF-8 中文内容。
old path gone after rename (rv=2)
# ls /root/apr-demo/
demo_file  demo_file.c  demo_pool  demo_pool.c  demo_uuid  demo_uuid.c

💡 说明:写入 60 字节是按字节计数——两行文本中 UTF-8 中文每字占 3 字节,apr_size_t 传入时是「缓冲区容量」、函数返回后变为「实际写入/读取的字节数」,这是 APR 文件读写 API 的固定用法。rv=2 即 APR_ENOENT(Linux 上映射系统错误码 ENOENT)。

六、示例三:UUID 生成与解析(demo_uuid.c)

6.1 完整源码

UUID API 属于 apr-util(头文件 apr_uuid.h),编译时需同时链接 libaprutil-1。源码 demo_uuid.c:

/* demo_uuid.c:apr-util 的 apr_uuid 生成与解析示例 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <apr_general.h>
#include <apr_pools.h>
#include <apr_uuid.h>

int main(int argc, const char *argv[])
{
    apr_initialize();
    atexit(apr_terminate);
    apr_pool_t *pool;
    apr_pool_create(&pool, NULL);

    apr_uuid_t uuid, parsed;
    char buf[APR_UUID_FORMATTED_LENGTH + 1];   /* 36 字符 + 结尾 0 */
    apr_status_t rv;

    apr_uuid_get(&uuid);
    apr_uuid_format(buf, &uuid);
    printf("uuid#1: %s\n", buf);

    apr_uuid_get(&uuid);
    apr_uuid_format(buf, &uuid);
    printf("uuid#2: %s\n", buf);

    /* 将字符串解析回二进制结构,再次格式化验证一致性 */
    rv = apr_uuid_parse(&parsed, buf);
    char buf2[APR_UUID_FORMATTED_LENGTH + 1];
    apr_uuid_format(buf2, &parsed);
    printf("parse+format roundtrip: %s (rv=%d, len=%d)\n",
           strcmp(buf, buf2) == 0 ? "match" : "mismatch", (int)rv,
           APR_UUID_FORMATTED_LENGTH);

    apr_pool_destroy(pool);
    return 0;
}

6.2 编译与运行

编译命令需同时取 apr-1-config 与 apu-1-config 的链接参数:

cd /root/apr-demo
gcc demo_uuid.c -o demo_uuid $(/usr/local/apr/bin/apr-1-config --cflags --cppflags --includes --link-ld) $(/usr/local/apr/bin/apu-1-config --link-ld --libs)
./demo_uuid
./demo_uuid

实测运行两次(UUID 各不相同,退出码 0):

# ./demo_uuid
uuid#1: e4b33c28-c5d6-11f1-b461-8781a5106fe2
uuid#2: e4b33d90-c5d6-11f1-b461-8781a5106fe2
parse+format roundtrip: match (rv=0, len=36)
# ./demo_uuid
uuid#1: e4b3c67a-c5d6-11f1-a321-193684b9e0dc
uuid#2: e4b3c80a-c5d6-11f1-a321-193684b9e0dc
parse+format roundtrip: match (rv=0, len=36)

💡 说明:APR_UUID_FORMATTED_LENGTH 为 36(标准 UUID 字符串长度),格式化缓冲区必须给 37 字节(含结尾 \0);同一次运行内两次 apr_uuid_get 生成的 UUID 不同,且 parse+format 往返结果一致。

七、统一 Makefile 构建

7.1 Makefile 内容

三个示例的编译参数完全一致,可收敛为一个 Makefile,编译参数全部由配置工具动态查询。/root/apr-demo/Makefile:

# APR 编译参数统一由 apr-1-config / apu-1-config 提供
APR_CONFIG  = /usr/local/apr/bin/apr-1-config
APU_CONFIG  = /usr/local/apr/bin/apu-1-config
CFLAGS     += $(shell $(APR_CONFIG) --cflags --cppflags --includes)
LDLIBS     += $(shell $(APR_CONFIG) --link-ld --libs)
LDLIBS     += $(shell $(APU_CONFIG) --link-ld --libs)

all: demo_pool demo_file demo_uuid

demo_pool: demo_pool.c
	$(CC) demo_pool.c -o demo_pool $(CFLAGS) $(shell $(APR_CONFIG) --link-ld) $(LDLIBS)

demo_file: demo_file.c
	$(CC) demo_file.c -o demo_file $(CFLAGS) $(shell $(APR_CONFIG) --link-ld) $(LDLIBS)

demo_uuid: demo_uuid.c
	$(CC) demo_uuid.c -o demo_uuid $(CFLAGS) $(shell $(APR_CONFIG) --link-ld) $(LDLIBS)

clean:
	rm -f demo_pool demo_file demo_uuid

7.2 make 全量构建实测

cd /root/apr-demo && make clean && make

实测回显(三条 cc 命令即为最终生效的完整编译参数):

rm -f demo_pool demo_file demo_uuid
cc demo_pool.c -o demo_pool  -g -O2 -DLINUX -D_REENTRANT -D_GNU_SOURCE -I/usr/local/apr/include/apr-1   -L/usr/local/apr/lib -lapr-1  -L/usr/local/apr/lib -lapr-1 -lcrypt  -lpthread  -L/usr/local/apr/lib -laprutil-1     -lexpat
cc demo_file.c -o demo_file  -g -O2 -DLINUX -D_REENTRANT -D_GNU_SOURCE -I/usr/local/apr/include/apr-1   -L/usr/local/apr/lib -lapr-1  -L/usr/local/apr/lib -lapr-1 -lcrypt  -lpthread  -L/usr/local/apr/lib -laprutil-1     -lexpat
cc demo_uuid.c -o demo_uuid  -g -O2 -DLINUX -D_REENTRANT -D_GNU_SOURCE -I/usr/local/apr/include/apr-1   -L/usr/local/apr/lib -lapr-1  -L/usr/local/apr/lib -lapr-1 -lcrypt  -lpthread  -L/usr/local/apr/lib -laprutil-1     -lexpat

构建完成后三个可执行文件生成,./demo_pool 输出 Hello, APR pool! 正常。

八、链接与运行期排障实录(ld.so)

8.1 现象:编译通过,运行报共享库找不到

共享库路径 /usr/local/apr/lib 未注册进系统缓存时(曾把 /etc/ld.so.conf.d/apr.conf 移除并执行 ldconfig 复现),编译期 -L 参数只对链接器生效,运行期动态加载器找不到库:

# ldd /root/apr-demo/demo_pool
        linux-vdso.so.1 (0x00007fff8f1c1000)
        libapr-1.so.0 => not found
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007cb86c52b000)
        /lib64/ld-linux-x86-64.so.2 (0x00007cb86c75e000)
# /root/apr-demo/demo_pool
/root/apr-demo/demo_pool: error while loading shared libraries: libapr-1.so.0: cannot open shared object file: No such file or directory
(退出码 127)

8.2 定位与解决:ld.so.conf.d 注册 + ldconfig

定位方法即上一步 ldd:哪个 soname 显示 not found,说明运行期缺哪个库。解决方法是把库目录写入 ld.so 配置并刷新缓存:

echo "/usr/local/apr/lib" > /etc/ld.so.conf.d/apr.conf
ldconfig

注册后 ldconfig -p | grep libapr-1 可见缓存项:

        libapr-1.so.0 (libc6,x86-64) => /usr/local/apr/lib/libapr-1.so.0
        libapr-1.so (libc6,x86-64) => /usr/local/apr/lib/libapr-1.so

恢复后 demo_pool、demo_uuid 均正常运行(退出码 0)。

💡 说明:替代方案有两条——链接时给环境变量 LD_LIBRARY_PATH=/usr/local/apr/lib(仅当次会话生效),或链接时加 -Wl,-rpath,/usr/local/apr/lib 把路径写死进可执行文件。系统级注册(ld.so.conf.d + ldconfig)最省心,也是本文推荐方式。

九、常见问题(FAQ)

9.1 gcc 报 fatal error: apr_general.h: No such file or directory

Q: 编译时报找不到 APR 头文件怎么办?

A: 说明编译命令缺少 --includes 提供的 -I/usr/local/apr/include/apr-1。编译参数一律由配置工具生成,不要手工拼写,参照第四章命令:

gcc demo_pool.c -o demo_pool $(/usr/local/apr/bin/apr-1-config --cflags --cppflags --includes --link-ld --libs)

同时确认头文件目录存在:ls /usr/local/apr/include/apr-1/apr_general.h。

9.2 链接报 undefined reference to apr_uuid_get

Q: demo_uuid 编译时报 undefined reference to 'apr_uuid_get' 等符号未定义?

A: apr_uuid_get / apr_uuid_format / apr_uuid_parse 属于 apr-util(libaprutil-1),只链 libapr-1 不够。链接参数需追加 apu-1-config 的输出:

gcc demo_uuid.c -o demo_uuid $(/usr/local/apr/bin/apr-1-config --cflags --cppflags --includes --link-ld) $(/usr/local/apr/bin/apu-1-config --link-ld --libs)

9.3 运行报 error while loading shared libraries: libapr-1.so.0

Q: 编译成功,运行时报 error while loading shared libraries: libapr-1.so.0: cannot open shared object file?

A: 运行期共享库路径未注册,完整排障实录见「八、链接与运行期排障实录」。一句话解决:

echo "/usr/local/apr/lib" > /etc/ld.so.conf.d/apr.conf && ldconfig

9.4 demo_file 写入的中文为什么是 60 字节

Q: 源码里两行文本看起来只有 40 多个字符,为什么 apr_file_write wrote 60 bytes?

A: APR 文件 API 按字节计数,UTF-8 编码下中文每字占 3 字节。"Apache APR file I/O demo.\n第二行:UTF-8 中文内容。\n" 中含 9 个中文字符与全角冒号等,逐字节数恰为 60。可用 wc -c 验证任意同内容文件的字节数。

十、参考链接