For a few days I'm trying to build OpenALPR example project for Android. It builds and launches, but after calling native method for recognizing it make exception:
java.lang.RuntimeException: An error occured while executing doInBackground()
at android.os.AsyncTask$3.done(AsyncTask.java:299)
at java.util.concurrent.FutureTask.finishCompletion(FutureTask.java:352)
at java.util.concurrent.FutureTask.setException(FutureTask.java:219)
at java.util.concurrent.FutureTask.run(FutureTask.java:239)
at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:230)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1080)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:573)
at java.lang.Thread.run(Thread.java:838)
Caused by: java.lang.UnsatisfiedLinkError: Native method not found: org.openalpr.AlprJNIWrapper.recognizeWithCountryRegionNConfig:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;I)Ljava/lang/String;
at org.openalpr.AlprJNIWrapper.recognizeWithCountryRegionNConfig(Native Method)
at org.openalpr.app.AlprFragment$AlprTask.doInBackground(AlprFragment.java:78)
at org.openalpr.app.AlprFragment$AlprTask.doInBackground(AlprFragment.java:1)
at android.os.AsyncTask$2.call(AsyncTask.java:287)
at java.util.concurrent.FutureTask.run(FutureTask.java:234)
... 4 more
I can't do anything, it appears all the time.
Application.mk
APP_ABI := armeabi-v7a
APP_CPPFLAGS := -frtti -fexceptions
APP_STL := gnustl_static
Android.mk after all of my attempts
LOCAL_PATH := $(call my-dir)
LIB_PATH := $(LOCAL_PATH)/../libs/armeabi-v7a
include $(CLEAR_VARS)
LOCAL_MODULE := leptonica
LOCAL_SRC_FILES := 3rdparty/liblept.so
include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := tesseract
LOCAL_SRC_FILES := 3rdparty/libtess.so
include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := simpleini
LOCAL_SRC_FILES := 3rdparty/libsimpleini.a
include $(PREBUILT_STATIC_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := support
LOCAL_SRC_FILES := 3rdparty/libsupport.a
include $(PREBUILT_STATIC_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := openalpr
LOCAL_SRC_FILES := 3rdparty/libopenalpr-static.a
include $(PREBUILT_STATIC_LIBRARY)
include $(CLEAR_VARS)
OPENCV_INSTALL_MODULES:=on
OPENCV_CAMERA_MODULES:=off
include d:\Other\robovisor_mobile\OpenCV-android-sdk\sdk\native\jni\OpenCV.mk
LOCAL_MODULE := openalpr-native
SOURCE_LIST := $(wildcard $(LOCAL_PATH)/*.cpp)
HEADER_LIST := $(wildcard $(LOCAL_PATH)/*.h)
LOCAL_SRC_FILES := AlprJNIWrapper.cpp
LOCAL_SRC_FILES += $(HEADER_LIST:$(LOCAL_PATH)/%=%)
LOCAL_SRC_FILES += $(SOURCE_LIST:$(LOCAL_PATH)/%=%)
LOCAL_EXPORT_C_INCLUDES += /home/sujay/builds/src/openalpr/src/openalpr
LOCAL_EXPORT_C_INCLUDES += /home/sujay/builds/src/OpenCV-2.4.9-android-sdk/sdk/native/include
FILE_LIST := $(foreach dir, $(LOCAL_EXPORT_C_INCLUDES), $(wildcard $(dir)/*.cpp))
LOCAL_SRC_FILES := $(FILE_LIST:$(LOCAL_PATH)/%=%)
LOCAL_C_INCLUDES += /home/sujay/builds/src/openalpr/src/openalpr
LOCAL_C_INCLUDES += /home/sujay/builds/src/OpenCV-2.4.9-android-sdk/sdk/native/include
LOCAL_C_INCLUDES += /home/sujay/tools/android-ndk-r10/platforms/android-19/arch-arm/usr/include
LOCAL_SHARED_LIBRARIES += tesseract leptonica
LOCAL_STATIC_LIBRARIES += openalpr support simpleini
LOCAL_LDLIBS := -llog
include $(BUILD_SHARED_LIBRARY)
AlprJNIWrapper.cpp contains needed for me native functions
/**
* Created by sujay on 13/11/14.
*/
#include <string>
#include <sstream>
#include <cstdio>
#include <iostream>
// openCV includes
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"
// open alpr includes
#include "support/filesystem.h"
#include "support/timing.h"
#include "alpr.h"
#include "cjson.h"
#include "AlprJNIWrapper.h"
#include "AlprNative.h"
using namespace alpr;
JNIEXPORT jstring JNICALL Java_org_openalpr_AlprJNIWrapper_recognize(JNIEnv *env,
jobject object, jstring jimgFilePath, jint jtopN)
{
jstring defaultCountry = env->NewStringUTF("us");
jstring defaultRegion = env->NewStringUTF("");
jstring defaultConfigFilePath = env->NewStringUTF(CONFIG_FILE);
return _recognize(env, object, defaultCountry, defaultRegion, jimgFilePath, defaultConfigFilePath, jtopN);
}
JNIEXPORT jstring JNICALL Java_org_openalpr_AlprJNIWrapper_recognizeWithCountryNRegion(
JNIEnv *env, jobject object, jstring jcountry,
jstring jregion, jstring jimgFilePath, jint jtopN)
{
jstring defaultConfigFilePath = env->NewStringUTF(CONFIG_FILE);
return _recognize(env, object, jcountry, jregion, jimgFilePath, defaultConfigFilePath, jtopN);
}
JNIEXPORT jstring JNICALL Java_org_openalpr_AlprJNIWrapper_recognizeWithCountryRegionNConfig
(JNIEnv *env, jobject object, jstring jcountry, jstring jregion,
jstring jimgFilePath, jstring jconfigFilePath, jint jtopN)
{
return _recognize(env, object, jcountry, jregion, jimgFilePath, jconfigFilePath, jtopN);
}
jstring _recognize(JNIEnv *env, jobject object,
jstring jcountry, jstring jregion, jstring jimgFilePath,
jstring jconfigFilePath, jint jtopN)
{
const char* countryChars = env->GetStringUTFChars(jcountry, NULL);
std::string country(countryChars);
env->ReleaseStringUTFChars(jcountry, countryChars);
if(country.empty())
{
country = "us";
}
const char* configFilePathChars = env->GetStringUTFChars(jconfigFilePath, NULL);
std::string configFilePath(configFilePathChars);
env->ReleaseStringUTFChars(jconfigFilePath, configFilePathChars);
if(configFilePath.empty())
{
configFilePath = "/etc/openalpr/openalpr.conf";
}
const char* imgFilePath = env->GetStringUTFChars(jimgFilePath, NULL);
int topN = jtopN;
std::string response = "";
cv::Mat frame;
Alpr alpr(country, configFilePath);
const char* regionChars = env->GetStringUTFChars(jregion, NULL);
std::string region(regionChars);
env->ReleaseStringUTFChars(jregion, regionChars);
if(region.empty())
{
alpr.setDetectRegion(true);
alpr.setDefaultRegion(region);
}
alpr.setTopN(topN);
if (alpr.isLoaded() == false) {
env->ReleaseStringUTFChars(jimgFilePath, imgFilePath);
response = errorJsonString("Error initializing Open Alpr");
return env->NewStringUTF(response.c_str());
}
if(fileExists(imgFilePath))
{
frame = cv::imread(imgFilePath);
response = detectandshow(&alpr, frame, "");
}
else
{
response = errorJsonString("Image file not found");
}
env->ReleaseStringUTFChars(jimgFilePath, imgFilePath);
return env->NewStringUTF(response.c_str());
}
JNIEXPORT jstring JNICALL Java_org_openalpr_AlprJNIWrapper_version
(JNIEnv *env, jobject object)
{
return env->NewStringUTF(Alpr::getVersion().c_str());
}
std::string detectandshow(Alpr* alpr, cv::Mat frame, std::string region)
{
std::vector < uchar > buffer;
std::string resultJson = "";
cv::imencode(".bmp", frame, buffer);
std::vector < char > buffer1;
for(std::vector < uchar >::iterator i = buffer.begin(); i < buffer.end(); i++) {
buffer1.push_back(*i);
}
timespec startTime;
getTimeMonotonic(&startTime);
//std::vector < AlprResults > results = alpr->recognize(buffer);
AlprResults results = alpr->recognize(buffer1);
timespec endTime;
getTimeMonotonic(&endTime);
double totalProcessingTime = diffclock(startTime, endTime);
//if (results.size() > 0)
{
resultJson = alpr->toJson(results/*, totalProcessingTime*/);
}
return resultJson;
}
std::string errorJsonString(std::string msg)
{
cJSON *root;
root = cJSON_CreateObject();
cJSON_AddTrueToObject(root, "error");
cJSON_AddStringToObject(root, "msg", msg.c_str());
char *out;
out = cJSON_PrintUnformatted(root);
cJSON_Delete(root);
std::string response(out);
free(out);
return response;
}
AlprJNIWrapper.java calls native method
/**
*
*/
package org.openalpr;
/**
* #author sujay
*
*/
public class AlprJNIWrapper implements Alpr {
static {
System.loadLibrary("lept");
System.loadLibrary("tess");
System.loadLibrary("opencv_java");
System.loadLibrary("openalpr-native");
}
/* (non-Javadoc)
* #see org.openalpr.Alpr#recognize(java.lang.String, int)
*/
#Override
public native String recognize(String imgFilePath, int topN);
/* (non-Javadoc)
* #see org.openalpr.Alpr#recognizeWithCountryNRegion(java.lang.String, java.lang.String, java.lang.String, int)
*/
#Override
public native String recognizeWithCountryNRegion(String country, String region,
String imgFilePath, int topN);
/* (non-Javadoc)
* #see org.openalpr.Alpr#recognizeWithCountryRegionNConfig(java.lang.String, java.lang.String, java.lang.String, java.lang.String, int)
*/
#Override
public native String recognizeWithCountryRegionNConfig(String country,
String region, String imgFilePath, String configFilePath, int topN);
/*
* (non-Javadoc)
* #see org.openalpr.Alpr#version()
*/
#Override
public native String version();
}
Edited
There is info about processor on my phone.
Processor : ARMv7 Processor rev 3 (v7l)
processor : 0
BogoMIPS : 1993.93
Features : swp half thumb fastmult vfp edsp thumbee neon vfpv3 tls vfpv4
idiva idivt
CPU implementer : 0x41
CPU architecture: 7
CPU variant : 0x0
CPU part : 0xc07
CPU revision : 3
Edited again
I tried to get info about function in result .so file and there is what I get:
Symbol table '.dynsym' contains 6 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 00000000 0 NOTYPE LOCAL DEFAULT UND
1: 00000000 0 FUNC GLOBAL DEFAULT UND __cxa_finalize
2: 00000000 0 FUNC GLOBAL DEFAULT UND __cxa_atexit
3: 00002004 0 NOTYPE GLOBAL DEFAULT ABS _edata
4: 00002004 0 NOTYPE GLOBAL DEFAULT ABS __bss_start
5: 00002004 0 NOTYPE GLOBAL DEFAULT ABS _end
I get it from:
<ndk_path>\toolchains\arm-linux-androideabi-4.8\prebuilt\windows\bin>arm-linux-androideabi-readelf.exe
-Ws <projects_path>\libs\armeabi-v7a\libopenalpr-native.so
Am I doing something wrong? Or there is really no my functions in .so file?
There are could be several issues.
First, you run the sample on unsupported device, like if lib built for armeabi-v7 (and its definitely so due to APP_ABI := armeabi-v7a setting in make file) and your device is intel x86 or lower than 7 armeabi version, etc.
Could be that sample is outdated while lib project has been updated, so some method names was changed or method was removed as deprecated, etc.
Also compiled NDK lib is package sensitive so if you place JNI class into a different package it wont work either.
Also the native library .so-file has to be placed into the right place of your project and its not a libs folder where you place jar-libs usually. Its a bit different folder like as for android studio:
...\src\main\jniLibs\aremabi-v7a\libYourLib.so (for aremabi-v7a version)
...\src\main\jniLibs\x86\libYourLib.so (for x86 version and so on)
UPDATE:
Don't forget to respect the min API level which is 19. App wont work on devices with lower API level, I mean you could change the min API level in project - don't do it.
The issue here is that libopenalpr-native.so has a dash - char in its name. Dash is a restricted char for resource naming in AOS. So I replaced it with "_" and app works now
And replace it here as well:
System.loadLibrary("openalpr_native");
And I didn't use your version of .so but only the included in project one.
UPDATE
Take a look: http://prntscr.com/6w6qfx your lib is just 5kb comparing to original 1.7Mb there is something wrong definitely. And it explains your question in comment:
Why there is no error on System.loadLibrary("openalpr-native");? I
just can't understand this situation - creates libopenalpr-native.so
file, it loads to program, by there is no method.
Could you just use original lib included in sample project instead? Just to test.
No correct method found in your library is due to name mismatch, you should wrap your JNI function code with extern "C" in C++ code.
Related
I am deploying a program and encountered error of
Caused by: java.lang.Exception: java.lang.UnsatisfiedLinkError:
com.package.JniClass.JniGeoDbReader.openGeoDb()Ljava/lang/String;
I managed to load the dll but I feel like something has caused the string to not be converted correctly and caused the error. The following is the JNI code I am currently testing to deploy on Karaf :
JNI Codes>>>JAVA and c++
JniGeoDbReader.class
package com.package.JniClass;
public final class JniGeoDbReader {
private JniGeoDbReader() { }
public static native String openGeoDb(); }
ServiceClass.class <--- This class is the simplified version
import com.package.JniClass.JniGeoDbReader;
public class ServiceClass{
public void fetchResponse(){
System.load("D:\\DLL_PATH\\com_package_jniGeoDbReader.dll");
LOGGER.info(">>>>>>>>>>>>>>>>> {}", JniGeoDbReader.openGeoDb());
}
}
com_package_JniClass_JniGeoDbReader.h
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_package_JniClass_JniGeoDbReader */
#ifndef _Included_com_package_JniClass_JniGeoDbReader
#define
_Included_com_package_JniClass_JniGeoDbReader_JniClass_JniGeoDbReader
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_package_JniClass_JniGeoDbReader
* Method: openGeoDb
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL
Java_com_package_JniClass_JniGeoDbReader_openGeoDb
(JNIEnv *, jclass);
#ifdef __cplusplus
}
#endif
#endif
dllmain.cpp <--I got this code from somewhere else
// dllmain.cpp : Defines the entry point for the DLL application.
#include "stdafx.h"
#include "com_package_JniClass_JniGeoDbReader.h"
#include <string>
using namespace std;
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
JNIEXPORT jstring JNICALL
Java_com_package_JniClass_JniGeoDbReader_openGeoDb
(JNIEnv *env , jobject obj) {
string message = "Welcome to JNI";
int byteCount = message.length();
const jbyte* pNativeMessage = reinterpret_cast<const jbyte*>
(message.c_str());
jbyteArray bytes = env->NewByteArray(byteCount);
env->SetByteArrayRegion(bytes, 0, byteCount, pNativeMessage);
// find the Charset.forName method:
// javap -s java.nio.charset.Charset | egrep -A2 "forName"
jclass charsetClass = env->FindClass("java/nio/charset/Charset");
jmethodID forName = env->GetStaticMethodID(
charsetClass, "forName", "
(Ljava/lang/String;)Ljava/nio/charset/Charset;");
jstring utf8 = env->NewStringUTF("UTF-8");
jobject charset = env->CallStaticObjectMethod(charsetClass,
forName, utf8);
// find a String constructor that takes a Charset:
// javap -s java.lang.String | egrep -A2 "String\(.*charset"
jclass stringClass = env->FindClass("java/lang/String");
jmethodID ctor = env->GetMethodID(
stringClass, "<init>", "([BLjava/nio/charset/Charset;)V");
jstring jMessage = reinterpret_cast<jstring>(
env->NewObject(stringClass, ctor, bytes, charset));
return jMessage; }
I got the code in the cpp from here: Send C++ string to Java via JNI. I would like to do something similar, that is to return a string without accepting any parameters. But for some reason, it is throwing error. Please guide me in what causes this error and which part of this code I should change. Thank you.
UPDATE
Here is the full stacktrace:
at com.sun.proxy.$Proxy116.fetchCatalogue(Unknown Source)
at com.package.ArcGisDiscoveryJob$CatalogueTask.call(ArcGisDiscoveryJob.java:485)
at com.package.ArcGisDiscoveryJob$CatalogueTask.call(ArcGisDiscoveryJob.java:450)
at com.package.BaseTaskExecutor.invoke(BaseTaskExecutor.java:84)
at com.package.ArcGisDiscoveryJob.traverse(ArcGisDiscoveryJob.java:131)
at com.package.ArcGisDiscoveryJob.run(ArcGisDiscoveryJob.java:224)
at com.package.BaseScheduler$1.run(BaseScheduler.java:101)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
at java.util.concurrent.FutureTask.runAndReset(FutureTask.java:308)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$301(ScheduledThreadPoolExecutor.java:180)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:294)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
Caused by: java.lang.Exception: java.lang.UnsatisfiedLinkError: com.package.JniClass.JniGeoDbReader.openGeoDb()Ljava/lang/String;
at com.package.service.rest.impl.ArcGisClientProxy$1.call(ArcGisClientProxy.java:161)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
... 3 more
Caused by: java.lang.UnsatisfiedLinkError: com.package.JniClass.JniGeoDbReader.openGeoDb()Ljava/lang/String;
at com.package.JniClass.JniGeoDbReader.openGeoDb(Native Method)
at com.package.service.rest.impl.ServiceClass.fetchResponse(ServiceClass.java:364)
at com.package.service.rest.impl.ServiceClass.fetchCatalogue(ServiceClass.java:124)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at com.package.service.rest.impl.ArcGisClientProxy.callEndPoint(ArcGisClientProxy.java:241)
at com.package.service.rest.impl.ArcGisClientProxy.access$000(ArcGisClientProxy.java:32)
at com.package.service.rest.impl.ArcGisClientProxy$1.call(ArcGisClientProxy.java:155)
... 4 more
Your header file and your CPP code don't match. The header declares jclass for the second function parameter, but the implementation has jobject. JNI itself would not care because jclass is the same as jobject, but your compiler will not recognize the function, because of the different parameter declaration. Therefore it will not see the extern "C" for the function and export it with CPP name mangling, and Java will not find it.
Edit: hadn't seen #user207421's comment, and while it is important that you always regenerate the header file if something changes in your native method, it won't help in your case because your header already matches the Java code. You also have to change your implementation to match the header.
I've tried creating a simple jni test app from "Beginning Android Games 3rd Ed" book. I copy-pasted the code, but when I try to link with ndk-build, I get the following error messages:
D:\apps\android\projects\NDK\app\src\main\java>ndk-build
[armeabi-v7a] Compile++ arm : jniutils <= jniutils.cpp
jni/jniutils.cpp:8:52: error: format string is not a string literal (potentially insecure)
[-Werror,-Wformat-security]
__android_log_print(ANDROID_LOG_VERBOSE, cTag, cMessage);
^~~~~~~~
jni/jniutils.cpp:8:52: note: treat the string as an argument to avoid this
__android_log_print(ANDROID_LOG_VERBOSE, cTag, cMessage);
^
"%s",
1 error generated.
make: *** [obj/local/armeabi-v7a/objs/jniutils/jniutils.o] Error 1
If I try to treat these errors as warnings and suppress these with the following lines in android.mk / application.mk:
APP_CFLAGS += -Wno-error=format-security
LOCAL_DISABLE_FORMAT_STRING_CHECKS := true
Then I get this error:
D:\apps\android\projects\NDK\app\src\main\java>ndk-build
[armeabi-v7a] SharedLibrary : libjniutils.so
clang++.exe: error: no such file or directory: '遶上・llog'
make: *** [obj/local/armeabi-v7a/libjniutils.so] Error 1
Which I guess is just some garbage from memory, like a string /variable hasn't been addressed correctly.
I'm very new to jni, so I'm at a bit of a loss of how to troubleshoot this.
Code follows:
JniUtils.java
package com.badlogic.androidgames.ndk;
import java.nio.ByteBuffer;
public class JniUtils {
static {
System.loadLibrary("jniutils");
}
public static native void log(String tag, String message);
public static native void copy(ByteBuffer dst, float[] src, int offset, int len);
}
jniutils.cpp
#include <android/log.h>
#include <string.h>
#include "jniutils.h"
JNIEXPORT void JNICALL Java_com_badlogic_androidgames_ndk_JniUtils_log
(JNIEnv *env, jclass clazz, jstring tag, jstring message) {
const char *cTag = env-> GetStringUTFChars(tag, 0);
const char *cMessage = env-> GetStringUTFChars(message, 0);
__android_log_print(ANDROID_LOG_VERBOSE, cTag, cMessage);
env-> ReleaseStringUTFChars(tag, cTag);
env-> ReleaseStringUTFChars(message, cMessage);
}
JNIEXPORT void JNICALL Java_com_badlogic_androidgames_ndk_JniUtils_copy
(JNIEnv *env, jclass clazz, jobject dst, jfloatArray src, jint offset, jint len) {
unsigned char* pDst = (unsigned char*)env-> GetDirectBufferAddress(dst);
float* pSrc = (float*)env-> GetPrimitiveArrayCritical(src, 0); memcpy(pDst,
pSrc + offset, len * 4);
env-> ReleasePrimitiveArrayCritical(src, pSrc, 0);
}
application.mk
APP_ABI := armeabi-v7a x86
APP_PLATFORM := android-19
android.mk
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := jniutils
LOCAL_LDLIBS := − llog
LOCAL_ARM_MODE := arm
LOCAL_SRC_FILES := jniutils.cpp
include $(BUILD_SHARED_LIBRARY)
I am trying to use concorde in a java app
but I can't figure it out.
I have created the java class as follows:
package jconcorde;
public class TSP {
static {
System.loadLibrary("tsp");
}
private native int[] getNNTour(String path);
public static void main(String[] args) {
int [] a = new TSP().getNNTour("st70.tsp");
for (int i = 0; i < a.length; i++) {
System.out.println(a[i]);
}
}
}
after compiling it using javac -h
I got the following file: jconcorde_TSP.h
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class jconcorde_TSP */
#ifndef _Included_jconcorde_TSP
#define _Included_jconcorde_TSP
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: jconcorde_TSP
* Method: getNNTour
* Signature: (Ljava/lang/String;)[I
*/
JNIEXPORT jintArray JNICALL Java_jconcorde_TSP_getNNTour
(JNIEnv *, jobject, jstring);
#ifdef __cplusplus
}
#endif
#endif
inside of concorde there is a folder named KDTREE i have copied from the kd_main.c some of it into my jconcorde_TSP.c as follows:
#include <jni.h> // JNI header provided by JDK
#include <stdio.h> // C Standard IO Header
#include "jconcorde_TSP.h" // Generated
#include "machdefs.h"
#include "util.h"
#include "kdtree.h"
#include <string.h>
static int norm = CC_EUCLIDEAN;
static int seed = 0;
static char *nodefile2 = (char *) NULL;
JNIEXPORT jintArray JNICALL Java_jconcorde_TSP_getNNTour(JNIEnv *env, jobject thisObj, jstring jstr) {
const char *nodefile = (*env)->GetStringUTFChars(env, jstr, NULL);
strcpy(nodefile2, nodefile);
double val, szeit;
CCdatagroup dat;
int ncount;
int *ttour = (int *) NULL, *tour2 = (int *) NULL;
CCrandstate rstate;
CCutil_init_datagroup (&dat);
seed = (int) CCutil_real_zeit ();
CCutil_sprand (seed, &rstate);
CCutil_gettsplib (nodefile2, &ncount, &dat);
CCutil_dat_getnorm (&dat, &norm);
ttour = CC_SAFE_MALLOC (ncount, int);
szeit = CCutil_zeit ();
if (CCkdtree_nearest_neighbor_tour ((CCkdtree *) NULL, ncount,
CCutil_lprand (&rstate) % ncount, &dat, ttour, &val, &rstate)) {
fprintf (stderr, "Nearest neighbor failed\n");
}
jintArray outJNIArray = (*env)->NewIntArray(env, ncount);
(*env)->SetIntArrayRegion(env, outJNIArray, 0 , ncount, ttour);
return outJNIArray;
}
and the original Makefile of the KDTREE is as follows:
# Generated automatically from Makefile.in by configure.
#
# This file is part of CONCORDE
#
# (c) Copyright 1995--1999 by David Applegate, Robert Bixby,
# Vasek Chvatal, and William Cook
#
# Permission is granted for academic research use. For other uses,
# contact the authors for licensing options.
#
# Use at your own risk. We make no guarantees about the
# correctness or usefulness of this code.
#
SHELL = /bin/sh
SRCROOT = ..
BLDROOT = ..
CCINCDIR=$(SRCROOT)/INCLUDE
srcdir = .
CC = gcc
CFLAGS = -g -O3 -arch x86_64 -I$(BLDROOT)/INCLUDE -I$(CCINCDIR)
LDFLAGS = -g -O3 -arch x86_64
LIBFLAGS = -lm
RANLIB = ranlib
OBJ_SUFFIX = o
o = $(OBJ_SUFFIX)
THISLIB=kdtree.a
LIBSRCS=kdbuild.c kdnear.c kdspan.c kdtwoopt.c
ALLSRCS=kd_main.c $(LIBSRCS)
LIBS=$(BLDROOT)/UTIL/util.a
all: kdtree $(THISLIB)
everything: all kdtree
kdtree: kd_main.$o $(THISLIB) $(LIBS)
$(CC) $(LDFLAGS) -o $# kd_main.$o $(THISLIB) $(LIBS) $(LIBFLAGS)
clean:
-rm -f *.$o $(THISLIB) kdtree
OBJS=$(LIBSRCS:.c=.o)
$(THISLIB): $(OBJS)
$(AR) $(ARFLAGS) $(THISLIB) $(OBJS)
$(RANLIB) $(THISLIB)
.PHONY: $(BLDROOT)/concorde.a
$(BLDROOT)/concorde.a: $(OBJS)
$(AR) $(ARFLAGS) $(BLDROOT)/concorde.a $(OBJS)
$(RANLIB) $(BLDROOT)/concorde.a
include ../INCLUDE/Makefile.common
# DO NOT DELETE THIS LINE -- make depend depends on it.
I=$(CCINCDIR)
I2=$(BLDROOT)/INCLUDE
kd_main.$o: kd_main.c $(I)/machdefs.h $(I2)/config.h $(I)/util.h \
$(I)/kdtree.h
kdbuild.$o: kdbuild.c $(I)/machdefs.h $(I2)/config.h $(I)/util.h \
$(I)/kdtree.h $(I)/macrorus.h
kdnear.$o: kdnear.c $(I)/machdefs.h $(I2)/config.h $(I)/kdtree.h \
$(I)/util.h $(I)/macrorus.h
kdspan.$o: kdspan.c $(I)/machdefs.h $(I2)/config.h $(I)/kdtree.h \
$(I)/util.h $(I)/macrorus.h
kdtwoopt.$o: kdtwoopt.c $(I)/machdefs.h $(I2)/config.h $(I)/util.h \
$(I)/kdtree.h $(I)/macrorus.h
I have copied both of jconcorde_TSP.c and jconcorde_TSP.h into the KDTREE folder
what do I need to do in order to create the libtsp.dylib for the java code?
I have tried this:
gcc -g -O3 -arch x86_64 -I../INCLUDE -I../INCLUDE -I/Library/Java/JavaVirtualMachines/jdk-11.0.1.jdk/Contents/Home//include -I/Library/Java/JavaVirtualMachines/jdk-11.0.1.jdk/Contents/Home//include/darwin -c -dynamiclib -o libtsp.dylib jconcorde_TSP.c ../UTIL/util.a -lm
but after I copied the libtsp.dylib into the java project I get this exception:
Exception in thread "main" java.lang.UnsatisfiedLinkError: /Users/home_pc/NetBeansProjects/JConcorde/libtsp.dylib: dlopen(/Users/home_pc/NetBeansProjects/JConcorde/libtsp.dylib, 1): no suitable image found. Did find:
/Users/home_pc/NetBeansProjects/JConcorde/libtsp.dylib: mach-o, but wrong filetype
/Users/home_pc/NetBeansProjects/JConcorde/libtsp.dylib: mach-o, but wrong filetype
at java.base/java.lang.ClassLoader$NativeLibrary.load0(Native Method)
at java.base/java.lang.ClassLoader$NativeLibrary.load(ClassLoader.java:2430)
at java.base/java.lang.ClassLoader$NativeLibrary.loadLibrary(ClassLoader.java:2487)
at java.base/java.lang.ClassLoader.loadLibrary0(ClassLoader.java:2684)
at java.base/java.lang.ClassLoader.loadLibrary(ClassLoader.java:2649)
at java.base/java.lang.Runtime.loadLibrary0(Runtime.java:829)
at java.base/java.lang.System.loadLibrary(System.java:1867)
at jconcorde.TSP.<clinit>(TSP.java:15)
/Users/home_pc/NetBeansProjects/JConcorde/nbproject/build-impl.xml:1328: The following error occurred while executing this line:
/Users/home_pc/NetBeansProjects/JConcorde/nbproject/build-impl.xml:948: Java returned: 1
what am I doing wrong?
After a discussion in the comments with #P.N.N the solution was found.
The correct compilation command is:
gcc -g -O3 -arch x86_64 -I"$JAVA_HOME/include" -I"$JAVA_HOME/include/darwin" -L"../UTIL" -I"../INCLUDE" -I"../" -dynamiclib -o libtsp.dylib jconcorde_TSP.c ../UTIL/util.a kdtree.a
Compared to the original command the solution was to add a library path adding -L"../UTIL" to the compilation and then add a missing static library to the command kdtree.a
I am trying to make a proof of concept where in a cpp program I fetch the windows username and then call this program from a java code, using Java native interface(JNI). Now what i have so far is, a sample JNI hello world program which is able to compile and print Hello world or what what ever parameter I set up. Now in a separate cpp snippet i am able to fetch the current username and it works as well; looks as follows:
#include <iostream>
#include <windows.h>
using namespace std;
int main()
{
char acUserName[100];
DWORD nUserName = sizeof(acUserName);
if (GetUserName(acUserName, &nUserName)) {
cout << "User name is " << acUserName << "." << endl;
cin.get();
}
return 0;
}
When compiled and executed using my G++ in command prompt it prints the current username correctly.
Now i just want to combine the two programs. When I do that as follows
public class Sample1
{
public native String fetchCurrentUserName();
public static void main(String[] args)
{
System.loadLibrary("Sample1");
Sample1 sample = new Sample1();
String userName = sample.fetchCurrentUserName();
System.out.println("userName: " + userName);
}
}
This is the java part and the cpp part is as follows:
#include "Sample1.h"
#include <string.h>
#include <iostream>
#include <windows.h>
using namespace std;
JNIEXPORT jstring JNICALL Java_Sample1_fetchCurrentUserName
(JNIEnv *env, jobject obj) {
char acUserName[100];
DWORD nUserName = sizeof(acUserName);
if (GetUserName(acUserName, &nUserName)) {
cout << "User name is " << acUserName << "." << endl;
cin.get();
}
const char* userName = acUserName;
//const char* userName = "acUserName";
return env->NewStringUTF(userName);
}
void main(){}
This does not compile anymore, it says
Sample1.obj : error LNK2019: unresolved external symbol __imp_GetUserNameA referenced in function Java_Sample1_fetchCurrentUserName
Sample1.dll : fatal error LNK1120: 1 unresolved externals
I used the following command to compile the JNI code.
cl -I"C:\Program Files\Java\jdk1.8.0_131\include" -I"C:\Program Files\Java\jdk1.8.0_131\include\win32" -LD Sample1.cpp -FeSample1.dll
as per tutorial from https://www.ibm.com/developerworks/java/tutorials/j-jni/j-jni.html. Now from few other questions in stack overflow it seems somehow my program is unable to link the windows library or leader somehow. But I have no clue as to how this can be solved. Any body have any bright ideas? Please make my day! Thank you for your time :D I looked into Linking of dll file in JNI which is very relevant for my question. But not sure if the solutions are applicable to me.
Edit
error: LNK1120: 5 unresolved externals This question had similar problem and it seems to have same solution which is as suggested by Remy to link with Advapi32.lib. At least now i learned few new things like #pragma comments and the role of Advapi32 library. Many thanks!
Your code compiles fine. You are actually getting a linker error, not a compiler error.
From the error message, you can see that your C++ code is trying to call a GetUserNameA() function. GetUserName() is a preprocessor #define macro in winbase.h (which is included by windows.h) that resolves to GetUserNameA() when UNICODE is not defined:
WINADVAPI
BOOL
WINAPI
GetUserNameA (
__out_ecount_part_opt(*pcbBuffer, *pcbBuffer) LPSTR lpBuffer,
__inout LPDWORD pcbBuffer
);
WINADVAPI
BOOL
WINAPI
GetUserNameW (
__out_ecount_part_opt(*pcbBuffer, *pcbBuffer) LPWSTR lpBuffer,
__inout LPDWORD pcbBuffer
);
#ifdef UNICODE
#define GetUserName GetUserNameW
#else
#define GetUserName GetUserNameA // <-- HERE!
#endif // !UNICODE
As there is no GetUserNameA() function implemented in your C++ code, but there is at least a declaration available (from winbase.h), the compiler emits machine code that references GetUserNameA() externally (via a symbol named __imp_GetUserNameA). When the linker is then invoked after compiling is finished, the linker outputs the "unresolved" error because it is not able to find an implementation of any GetUserNameA function to satisfy that reference.
GetUserNameA() is a Win32 API function implemented in Advapi32.dll. You need to link to Advapi32.lib from your compiler's Windows SDK so the linker can find the implementation of GetUserNameA().
One way to do that is to add a #pragma comment(lib) statement to your C++ code, eg:
#include "Sample1.h"
#include <windows.h>
#include <iostream>
using namespace std;
#pragma comment(lib, "Advapi32.lib") // <-- add this!
JNIEXPORT jstring JNICALL Java_Sample1_fetchCurrentUserName(JNIEnv *env, jobject obj) {
char acUserName[100] = {};
DWORD nUserName = sizeof(acUserName);
if (GetUserNameA(acUserName, &nUserName)) {
cout << "User name is " << acUserName << "." << endl;
}
else {
DWORD dwErr = GetLastError();
cout << "Unable to get User name, error " << dwErr << "." << endl;
}
return env->NewStringUTF(acUserName);
}
void main() {}
Alternatively:
#include "Sample1.h"
#include <windows.h>
#include <iostream>
#include <vector>
using namespace std;
#pragma comment(lib, "Advapi32.lib")
JNIEXPORT jstring JNICALL Java_Sample1_fetchCurrentUserName(JNIEnv *env, jobject obj) {
DWORD nUserName = 100, dwErr;
std::vector<char> acUserName(nUserName);
do {
if (GetUserNameA(&acUserName[0], &nUserName)) {
cout << "User name is " << &acUserName[0] << "." << endl;
break;
}
dwErr = GetLastError();
if (dwErr != ERROR_INSUFFICIENT_BUFFER) {
cout << "Unable to get the User name, error " << dwErr << "." << endl;
break;
}
acUserName.resize(nUserName);
}
while (true);
return env->NewStringUTF(&acUserName[0]);
}
void main() {}
That being said, NewStringUTF() requires an input string in modified UTF-8 format. However, GetUserNameA() outputs ANSI format (hence the A in its name), not in UTF-8, let alone modified UTF-8. Windows has no concept of modified UTF-8 at all. Your code will work correctly only if the username does not contain any non-ASCII characters in it.
Your C++ code really should be calling GetUserNameW() instead, which outputs in UTF-16 format. Then you can use NewString() instead of NewStringUTF() (Java strings use UTF-16 anyway), eg:
#include "Sample1.h"
#include <windows.h>
#include <iostream>
using namespace std;
#pragma comment(lib, "Advapi32.lib")
JNIEXPORT jstring JNICALL Java_Sample1_fetchCurrentUserName(JNIEnv *env, jobject obj) {
WCHAR acUserName[100];
DWORD nUserName = 100;
if (GetUserNameW(acUserName, &nUserName)) {
--nUserName; // ignore the null terminator
wcout << L"User name is " << acUserName << L"." << endl;
}
else
{
nUserName = 0;
DWORD dwErr = GetLastError();
cout << "Unable to get the User name, error " << dwErr << "." << endl;
}
return env->NewString(reinterpret_cast<jchar*>(acUserName), nUserName);
}
void main() {}
Alternatively:
#include "Sample1.h"
#include <windows.h>
#include <iostream>
#include <vector>
using namespace std;
#pragma comment(lib, "Advapi32.lib")
JNIEXPORT jstring JNICALL Java_Sample1_fetchCurrentUserName(JNIEnv *env, jobject obj) {
DWORD nUserName = 100, dwErr;
std::vector<WCHAR> acUserName(nUserName);
do {
if (GetUserNameW(&acUserName[0], &nUserName)) {
--nUserName; // ignore the null terminator
wcout << L"User name is " << &acUserName[0] << L"." << endl;
break;
}
dwErr = GetLastError();
if (dwErr != ERROR_INSUFFICIENT_BUFFER) {
nUserName = 0;
cout << "Unable to get the User name, error " << dwErr << "." << endl;
break;
}
acUserName.resize(nUserName);
}
while (true);
return env->NewString(reinterpret_cast<jchar*>(&acUserName[0]), nUserName);
}
void main() {}
If I put my java files in a package (like jni/test/), I get a fatal error when executing. But if I dont put the files in a package everything work fine.
When having a package:
javac jni/test/Main.java
javah -jni jni.test.Main
g++ -shared -o libfoo.so -I/usr/lib/jvm/java-7-openjdk-i386/include -I/usr/lib/jvm/java-7-openjdk-i386/include/linux Main.cpp
java -Djava.library.path=. jni/test/Main
#
# A fatal error has been detected by the Java Runtime Environment:
#
# SIGSEGV (0xb) at pc=0xb6c1ce3c, pid=3704, tid=3060386624
#
# JRE version: 7.0_25-b30
# Java VM: OpenJDK Server VM (23.7-b01 mixed mode linux-x86 )
# Problematic frame:
# V [libjvm.so+0x436e3c] get_method_id(JNIEnv_*, _jclass*, char const*, char const*, bool, Thread*) [clone .isra.106]+0x7c
#
# Failed to write core dump. Core dumps have been disabled. To enable core dumping, try "ulimit -c unlimited" before starting Java again
#
# An error report file with more information is saved as:
# /home/idle/workspace/JNITest/src/hs_err_pid3704.log
#
# If you would like to submit a bug report, please include
# instructions on how to reproduce the bug and visit:
# https://bugs.launchpad.net/ubuntu/+source/openjdk-7/
#
Aborted
When not having a package:
javac Main.java
javah -jni Main
g++ -shared -o libfoo.so -I/usr/lib/jvm/java-7-openjdk-i386/include -I/usr/lib/jvm/java-7-openjdk-i386/include/linux Main.cpp
java -Djava.library.path=. Main
//This executes fine
I have 2 java classes
public class Animal {
public String name = null;
public int age = 0;
}
public class Main {
public native Animal nativeFoo();
static {
System.loadLibrary("foo");
}
public void print () {
Animal a = nativeFoo();
System.out.println(a.name + " " + a.age);
}
public static void main(String[] args) {
(new Main()).print();
}
}
The c++ part
/* 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: nativeFoo
* Signature: ()LAnimal;
*/
//if the java file in a package
//JNIEXPORT jobject JNICALL Java_jni_test_Main_nativeFoo (JNIEnv *env, jobject obxj)
JNIEXPORT jobject JNICALL Java_Main_nativeFoo
(JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif
The cpp file
#include "Main.h"
//if the java file in a package
//JNIEXPORT jobject JNICALL Java_jni_test_Main_nativeFoo (JNIEnv *env, jobject obxj)
JNIEXPORT jobject JNICALL Java_Main_nativeFoo (JNIEnv *env, jobject obxj){
jclass animal = env->FindClass("Animal");
jmethodID cons = env->GetMethodID(animal, "<init>", "()V");
jobject obj = env->NewObject(animal, cons);
jfieldID age = env->GetFieldID(animal, "age", "I");
jfieldID name = env->GetFieldID(animal, "name", "Ljava/lang/String;");
env->SetObjectField(obj, name, env->NewStringUTF("awww"));
env->SetIntField(obj, age, 23);
return obj;
}
Based on the error, which is when get_method_id(JNIEnv_*, _jclass*, char const*, char const*, bool, Thread*) is called via env->GetMethodID(), your jclass variable animal is null.
The FindClass call is failing because it cannot locate the class "Animal".
Validate that your class name and package definition is correct.
See similar question answered here with the same sigsegv