pybind11对接修改版r.viewshed时GRASS库未找到对应符号报错
- 开发场景:使用pybind11对接修改后的
r.viewshed源代码,目前暂将源码复制到自身项目源码目录下编译 - 报错现象:Python环境导入封装生成的动态库时,提示找不到GRASS GIS相关目标符号,报错信息:
package.cpython-39-x86_64-linux-gnu.so: undefined symbol: _ZN16mm_register_initD1Ev
- 已做排查:
- 用nm工具检查生成的so文件,两个
mm_register_init相关符号均被标记为U(未定义),检查命令与输出:
$ nm -D package.cpython-39-x86_64-linux-gnu.so| grep mm_register_init U _ZN16mm_register_initC1Ev U _ZN16mm_register_initD1Ev- 遍历ldconfig可识别的所有
libgrass库、全盘所有GRASS相关.so动态库检索对应符号,均无匹配结果,排查命令:
ldconfig -p | grep -o "/opt/grass/.*" | xargs nm -D | grep mm_register_init fd "libgrass" / -e so | xargs nm -D | grep mm_register_init- 直接执行
grass命令可正常启动GRASS GIS,无运行异常
- 用nm工具检查生成的so文件,两个
- 已考虑但不期望的方案:将GRASS对应源码作为子模块引入项目,静态链接所有依赖函数
根因说明
mm_register_init是GRASS源码内部通过模块定义宏(G_define_module()等)展开生成的静态注册类,GRASS官方发行的公开动态库根本不会导出这个符号。
GRASS原生的r.viewshed是编译为独立可执行模块运行的,设计之初就没考虑给外部程序动态链接调用。你直接把r.viewshed源码复制到自己项目里编译时,代码里的模块注册宏会自动生成对这个内部符号的引用,但公开的libgrass库没有导出这个符号,所以你搜遍系统所有GRASS动态库都找不到匹配项。
直接执行grass命令不报错也很好理解:GRASS运行r.viewshed时是直接调用编译好的独立可执行文件,这个可执行文件在编译阶段已经把mm_register_init的实现静态编进自身二进制里了,不依赖外部动态库提供该符号。
可行方案
方案1(改动最小,推荐生产环境使用)
直接删掉你复制过来的r.viewshed源码里所有GRASS模块入口、命令行解析相关的代码,也就是移除G_define_module()、G_parser()以及配套的参数定义、模块元信息声明代码。
你用pybind11做封装本来就不需要GRASS自带的命令行解析、模块元信息注册逻辑,删掉这部分内容后,就不会生成对mm_register_init的符号引用,之后正常链接你需要的公开GRASS库(libgrass_gis、libgrass_raster等)即可。
注意:移除G_parser相关逻辑后,调用GRASS库函数前需要手动完成GIS环境初始化:自行设置GISDBASE、LOCATION_NAME、MAPSET等必要环境变量,再调用
G_gisinit("your_module_name"),后续其他GRASS库函数就可以正常调用。
方案2(无需改源码,适合临时验证)
编译自己的so动态库时,给链接器加参数-Wl,--unresolved-symbols=ignore-in-object-files,强行跳过这个未定义符号的检查。
实际运行时只要你不主动调用GRASS原生命令行解析、模块注册相关的流程,这个未定义符号永远不会被触发,不会影响正常的可视域计算逻辑。
该方案存在隐患:如果后续代码误调用到模块注册相关逻辑,会直接触发段错误,仅建议快速验证功能时使用。
不需要把整个GRASS源码拉到项目里做静态链接,这套方案太重,完全没有必要。
内容的提问来源于stack exchange,提问作者iwishiwasaneagle

