C++ 库的 JNI 包装器和未定义的符号错误

Posted

技术标签:

【中文标题】C++ 库的 JNI 包装器和未定义的符号错误【英文标题】:JNI wrapper for C++ libraries and undefined symbol error 【发布时间】:2014-10-30 18:55:43 【问题描述】:

我想编写一个简单的 JNI 包装器,用于使用具有 C++ API 的 Festival 文本到语音(或任何其他)库。我有以下文件:

Main.java:

public class Main 
   static 
      System.loadLibrary("TTSWrapper");
   
   private native void FestivalSayHello();

   public static void main(String[] args) 
      new Main().FestivalSayHello();
   

生成的 Main.h:

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class Main */

#ifndef _Included_Main
#define _Included_Main
#ifdef __cplusplus
extern "C" 
#endif
/*
 * Class:     Main
 * Method:    FestivalSayHello
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_Main_FestivalSayHello
  (JNIEnv *, jobject);

#ifdef __cplusplus

#endif
#endif

FestivalWrapper.cpp:

#include <iostream>
#include <jni.h>
#include "Main.h"
#include "include/festival.h"

JNIEXPORT void JNICALL Java_Main_FestivalSayHello(JNIEnv *env, jobject thisObj) 
    EST_Wave wave;
    int heap_size = 210000;  // default scheme heap size
    int load_init_files = 1; // we want the festival init files loaded
    festival_initialize(load_init_files,heap_size);
    festival_say_file("/etc/motd");
    festival_eval_command("(voice_ked_diphone)");
    festival_say_text("hello world");
    festival_text_to_wave("hello world",wave);
    wave.save("output.wav","riff");
    festival_wait_for_spooler();
    return;

制作文件:

CLASS_PATH = ../bin

all : libTTSWrapper.so

libTTSWrapper.so : FestivalWrapper.o Main.h
   g++ -m64 -fPIC -shared -o $@ $<

FestivalWrapper.o : FestivalWrapper.cpp Main.h
    g++ -fPIC \
   -I"/usr/lib/jvm/java-7-openjdk-amd64/include" \
   -I"/home/TTS_tools/Festival/festival/src" \
   -I"/home/TTS_tools/Festival/speech_tools/include" \
   -L"/home/TTS_tools/Festival/festival/src/lib" \
   -L"/home/TTS_tools/Festival/speech_tools/lib" \
   -lFestival \
   -lestools \
   -lestbase \
   -leststring \
   -lncurses \
   -c $< \
   -o $@

Main.h : Main.class
   javah -classpath $(CLASS_PATH) $*

运行时出现以下错误:

/usr/lib/jvm/java-7-openjdk-amd64/bin/java: symbol lookup error: /home/workspace/TTS/jni/libTTSWrapper.so: undefined symbol: _ZN8EST_WaveC1Ev

您能帮我找出问题所在吗?而且我不确定是否需要 -m64 和 -fPIC。

注意 1:nm libTTSWrapper.so | grep EST_Wave 列出了导致错误的符号,即使我删除了 -L 和 -l 行。

注 2:Festival 是静态库的组合。

注 3:这个问题不是特定于库的,我在另一个库 (Ekho) 上也遇到了同样的问题。问题是我不知道如何链接第三方库。

更新:我仍然不确定,但我想问题是因为 Festival 没有与 -fPIC 选项相关联。尽管这些符号出现在生成的共享库中,但我认为它们无法正确引用。当我试图制作一个使用链接到静态库的共享库的简单可执行文件时,我达到了这一点,在纯 C 中。我必须从构建的目标文件创建 .a 文件-fPIC 选项,共享库也需要-fPIC 选项。

【问题讨论】:

问题对外部库来说太具体了..您需要提供代码..错误是什么?是代码造成的吗?只是您的makefile找不到文件吗?等等。仅靠 makefile 是不够的。 确切的错误总是相关的 @Brandon:我已经编辑了问题,并添加了源代码。 您可以使用JavaCPP 之类的工具让这一切变得更简单 - 而不会牺牲性能。 【参考方案1】:

我不确定你的代码是如何打包的,但是包名对 JNI 来说非常重要。另外我不建议使用默认包(在任何时候,但尤其是在这种情况下:p)

假设你的包被调用 package groove.FestivaWrapper

那么您的 JNI 调用必须如下所示 groove_FestivalWrapper_FestivalSayHello

否则 JNI 将不知道调用什么。

【讨论】:

如果出现你所说的包问题,还能编译吗? 如果我没记错的话是的。如果 .h 与 .cpp 匹配,则编译器不会抱怨。然后在运行时 JNI 运行时将找不到任何东西并导致难以调试的痛苦。 但是,您收到的错误消息可能不是问题。【参考方案2】:

你有两个问题。第一个问题是,当您构建FestivalWrapper.o 时,它忽略 -L-l 选项——它们仅由链接器使用,而不是由编译器使用。这些选项需要传递给链接器。你的 makefile 应该看起来更像:

CLASS_PATH = ../bin

all : libTTSWrapper.so

libTTSWrapper.so : FestivalWrapper.o Main.h
   g++ -m64 -fPIC -shared -o $@ $< \
   -L"/home/TTS_tools/Festival/festival/src/lib" \
   -L"/home/TTS_tools/Festival/speech_tools/lib" \
   -lFestival \
   -lestools \
   -lestbase \
   -leststring \
   -lncurses

FestivalWrapper.o : FestivalWrapper.cpp Main.h
    g++ -fPIC \
   -I"/usr/lib/jvm/java-7-openjdk-amd64/include" \
   -I"/home/TTS_tools/Festival/festival/src" \
   -I"/home/TTS_tools/Festival/speech_tools/include" \
   -c $< \
   -o $@

Main.h : Main.class
   javah -classpath $(CLASS_PATH) $*

即您必须将-L-l 选项移动到生成.so 文件的项目,即执行链接时。

你的第二个问题是你没有用-fPIC 编译节日。是的,这将阻止链接,因为 all .so 64 位文件必须使用 -fPIC 编译,如果它们是 ABI 编译器。您会收到如下错误:

/usr/bin/ld: ../festival/src/lib/libFestival.a(festival.o): relocation R_X86_64_32 against `.rodata.str1.1' can not be used when making a shared object; recompile with -fPIC
../festival/src/lib/libFestival.a: error adding symbols: Bad value

这些库不遵循标准配置约定 - 即在运行配置脚本之前尝试设置 CFLAGS 根本不起作用。

使用-fPIC 构建speech_toolsfestival 用于您执行的.a 文件:

export CC_OTHER_FLAGS=-fPIC

在为每个包运行 configure 之前。这将确保-fPIC 标志被传递到编译的所有阶段。

【讨论】:

感谢您的详细回答。不幸的是,“export...”命令不适用于 Ekho TTS 库。因此,如果我没有做错任何事情,恐怕此过程不适用于所有图书馆。 speech_tools 和节日包不遵循 configure/autoconf 规范,这就是为什么在构建它们时最终必须使用 CC_OTHER_FLAGS 以获得 -fPIC 标志建造。 Ekho TTS 使用标准的 autoconf 机制,因此设置 export CXXFLAGS="-O -fPIC"export CFLAGS="-O -fPIC" 应该对该库具有预期的效果。

以上是关于C++ 库的 JNI 包装器和未定义的符号错误的主要内容,如果未能解决你的问题,请参考以下文章

导入错误和未定义符号:dgesv_ 使用 PIP 安装 CVXPY 时出错

jni 未定义符号错误

Xcode 7.3.1 中的 C++ 链接错误和未定义引用

编译时 x86_64 的未定义符号

JNI技术---clojure 调用C++库的方法

无法加载库“/usr/lib/pgsql/plpgsql.so”和未定义符号:PinPortal