Apache APR 构建并运行示例应用指南
Apache APR 示例应用构建与运行指南:给出内存池与字符串、文件读写、UUID 生成与解析三个完整 C 示例的源码、apr-1-config/apu-1-config 编译命令与真实运行回显,并附统一 Makefile 构建与共享库链接排障实录,全程在 Ubuntu 22.04 实测。
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
二、前置条件
- 已按《Apache APR 安装指南》完成 apr-1.7.6 与 apr-util-1.6.5 源码编译安装,并完成共享库注册(
/etc/ld.so.conf.d/apr.conf+ldconfig)。 - 验证编译参数查询工具可用:
/usr/local/apr/bin/apr-1-config --version
/usr/local/apr/bin/apu-1-config --version
实测回显:
1.7.6
1.6.5
- 创建示例工程目录:
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_pool | libapr-1 | apr_initialize / apr_pool_create / apr_pstrcat / apr_psprintf / apr_pstrdup / apr_palloc |
| demo_file | libapr-1 | apr_file_open / apr_file_write / apr_file_read / apr_file_info_get / apr_file_rename / apr_file_remove |
| demo_uuid | libapr-1 + libaprutil-1 | apr_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 验证任意同内容文件的字节数。
十、参考链接
- APR 官方文档(内存池):https://apr.apache.org/docs/apr/1.7/group__apr__pools.html
- APR 官方文档(文件 I/O):https://apr.apache.org/docs/apr/1.7/group__apr__file__io.html
- apr-util 官方文档(UUID):https://apr.apache.org/docs/apr-util/1.6/group__APR__UU.html
- 示例工程依赖安装:《Apache APR 安装指南》
- 模块 API 速查:《Apache APR 常用工具与 API 详解》