在Windows上调用MinGW编译的函数(没有加载库)时,为什么会出现UnsatisfiedLinkError错误?

5
我正在使用Windows上的Eclipse创建一个简单的JNI测试应用程序。我的C++编译器是MinGW 4.6.2。当我尝试调用我的测试DLL中的函数时(DLL本身可以正常加载),Java会抛出一个“UnsatisfiedLinkError”。我已经验证了我的DLL导出了一个与由“javah”实用程序生成的函数同名的“C”函数。
尝试调用该函数如何可能生成链接错误?(此外,是否有任何方法可以获得有关未找到哪个符号的更多详细信息?仅有一个“UnsatisfiedLinkError”的简单声明几乎没有用处。)
以下是定义本地函数的Java代码:
package com.xyz.jsdi_test;

import java.io.File;

public class JSDI
{
    public static native void func(
        String str,
        int i,
        Integer ii,
        long j /* 64 bits */,
        Long jj,
        byte[] b
    );
    public static void dummy()
    {
        System.out.println("JSDI.dummy()");
    }
    static
    {
        File f = new File("..\\jsdi\\bin\\jsdi.dll");
        System.out.println("Preparing to load: " + f);
        System.load(f.getAbsolutePath());
        System.out.println("Successfully loaded: " + f);
    }

以下是javah的相应输出:

...
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_xyz_jsdi_test_JSDI
 * Method:    func
 * Signature: (Ljava/lang/String;ILjava/lang/Integer;JLjava/lang/Long;[B)V
 */
JNIEXPORT void JNICALL Java_com_xyz_jsdi_1test_JSDI_func
  (JNIEnv *, jclass, jstring, jint, jobject, jlong, jobject, jbyteArray);

#ifdef __cplusplus
}
#endif

...以及我如何实现这个函数...:

extern "C"
{

JNIEXPORT void JNICALL Java_com_xyz_jsdi_1test_JSDI_func(
    JNIEnv * env,
    jclass _class,
    jstring str,
    jint i,
    jobject ii,
    jlong j,
    jobject jj,
    jbyteArray b
)
{
    // don't do anything...let's just try to get called successfully...
}

} // extern "C"

这是我尝试调用它的方法。

...

public static void main(String[] args)
{
    JSDI.dummy(); // cause class to load, which should cause System.load() to run.
    JSDI.func("hello", 0, 0, 0L, 0L, (byte[])null);
}

最后,这是输出结果:

Preparing to load: ..\jsdi\bin\jsdi.dll
Successfully loaded: ..\jsdi\bin\jsdi.dll
JSDI.dummy()
java.lang.UnsatisfiedLinkError: com.xyz.jsdi_test.JSDI.func(Ljava/lang/String;ILjava/lang/Integer;JLjava/lang/Long;[B)V
   at com.xyz.jsdi_test.JSDI.func(Native Method)
   at com.xyz.jsdi_test.SimpleTest.main(SimpleTest.java:24)

我已经消除了一些低挂果。这不是包名中的下划线(请注意,javah想在其前面加上“1”)。此外,我创建了一个不带参数的非静态成员函数,并在尝试调用new JSDI().mf();时出现了相同的错误。 - 0xbe5077ed
3个回答

4

问题已解决——太棒了!

事实证明,MSVC在函数名前加上下划线__stdcall。而MinGW则不会这样做。Windows的JVM显然期望有这个'_'前缀。只要我在函数名前加上'_'并使用MinGW重新构建,一切就都能正常工作了。

例如:

JNIEXPORT void JNICALL Java_com_xyz_jsdi_1test_JSDI_func ==> _Java_com_xyz_jsdi_1test_JSDI_func
编辑: MinGW自带的dlltool实用程序的--add-stdcall-underscore功能可以透明地为您解决此问题。在Makefile中设置它,您就不需要担心针对不同编译器使用不同版本的实际源代码。请参见此链接

1

提供一个可工作的例子,将这三个文件的内容复制到同一目录下(修改JDK路径),然后调用build.cmd。

/* File: HelloWorld.java */
public class HelloWorld {
    private static native void writeHelloWorldToStdout();

    public static void main(String[] args) {
        System.loadLibrary("HelloWorld");
        writeHelloWorldToStdout();
    }
}

/* File: HelloWorld.c */
#include <stdio.h>
#include "HelloWorld.h"
JNIEXPORT void JNICALL Java_HelloWorld_writeHelloWorldToStdout(JNIEnv *env, jclass c)
{
    printf("Hello World!");
}

rem File: build.cmd 

%echo off
echo delete generated binaries
del HelloWorld.class
del HelloWorld.dll
del HelloWorld.h
del HelloWorld.def

echo Compile the Java Class
javac HelloWorld.java

echo Generate the Header file
javah -classpath . -o HelloWorld.h HelloWorld

echo Build the DLL 
gcc -I"C:\Program Files (x86)\Java\jdk1.7.0_25\include" -I"C:\Program Files (x86)\Java\jdk1.7.0_25\include\win32" -Wl,--add-stdcall-alias -Wl,--output-def,HelloWorld.def -shared  -o HelloWorld.dll HelloWorld.c

echo run the program 
java HelloWorld

谢谢,在我的情况下,添加“-Wl,--add-stdcall-alias”有所帮助。 - pevik

0

异常中的签名没有'int'参数。因此您的Java代码与本地代码不一致。


嗯,我不确定你的信息来源。"I"指的是一个int参数。此外,“C”调用本身没有“签名”,因为库仅导出符号名称,调用者需要知道如何调用它。因此,最有可能的解释是JVM找不到该符号,而不是JVM找不到特定的签名。 - 0xbe5077ed

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接