I am hooking an existing library that is compiled using gnustl std::string instead of libc++ std::_ndk1::string. If I try to set or access these strings I just get garbage. How do I convert my std::string to std::_ndk1::string and vice versa in my hook application?
I cannot compile my hook with "-DANDROID_STL=gnustl_shared" because it no longer exists and other libraries in use require libc++.
The documentation mentions this https://developer.android.com/ndk/guides/cpp-support "The various STLs are not compatible with one another. As an example, the layout of std::string in libc++ is not the same as it is in gnustl" which is exactly the problem.
To use gnustl, you could compile all your native code with NDK r.17 or older. This is a dangerous path, because many important bugs, including security-related, have been fixed since then.
Another unsupported (and not recommended) way to deal with your problem is to get the gnustl sources from NDK r.17 and compile them with the latest NDK version.
Your best option is to have all your dependencies rebuilt with a recent version of NDK and its c++_shared runtime library.
Here's what I came up with (for now, really not ideal):
StringsProxy.cc
#include "StringsProxy.h"
#include <iostream>
#include <string>
using namespace std;
__attribute__((visibility("default")))
extern "C" StringsProxy::StringsProxy(const char* contents)
{
set_string = std::string(contents);
}
__attribute__((visibility("default")))
extern "C" StringsProxy::StringsProxy(uintptr_t str) {
set_string = *reinterpret_cast<proxy_string*>(str);
}
__attribute__((visibility("default")))
extern "C" const char* StringsProxy::c_str() {
return set_string.c_str();
}
__attribute__((visibility("default")))
extern "C" const uintptr_t* StringsProxy::ptr() {
return reinterpret_cast<uintptr_t *>(&set_string);
}
__attribute__((visibility("default")))
extern "C" StringsProxy::~StringsProxy() {
}
StringsProxy.h
#ifndef __STRINGSPROXY_H__
#define __STRINGSPROXY_H__
#include <string>
typedef std::basic_string<char> proxy_string;
class StringsProxy
{
public:
/* Initialize StringsProxy with a pointer to an existing string */
StringsProxy(uintptr_t str);
/* Initialize StringsProxy with a new string */
StringsProxy(const char* str);
/* Get C string */
virtual const char* c_str();
/* Get pointer to string for injection */
const virtual uintptr_t* ptr();
private:
proxy_string set_string;
};
#endif
Compile this into a shared object using the old NDK with -DCMAKE_ANDROID_STL_TYPE=gnustl_static
Then link this shared object to the hooking program (in CMakeLists):
target_link_libraries(${TARGET_NAME} ${CMAKE_CURRENT_SOURCE_DIR}/abiproxy/build/armeabi-v7a/libabiproxy.so)
Then in the hooking program, can be used like this:
#include "abiproxy/StringsProxy.h"
void *setUriDebug(uintptr_t a1, uintptr_t stri) {
auto y = StringsProxy(stri);
LOGI("URI CALLED %s", y.c_str());
return setUriDebugOld(a1, stri);
}
Or in reverse:
StringsProxy assetNameBaseProxy = StringsProxy("https://example.com/");
void setResourceUrl(uintptr_t* a1, int a2) {
*(a1 + 1) = *assetNameBaseProxy.ptr();
}
This isn't by any means a good solution, but it works for my use-case.
Related
I created C/C++ project in android studio and I want to use NDK camera.
I wrote in my cpp file
#include <camera/NdkCameraMetadata.h>
#include <camera/NdkCameraManager.h>
#include <camera/NdkCameraDevice.h>
#include <jni.h>
#include <string>
extern "C" JNIEXPORT jstring JNICALL
Java_com_example_test_MainActivity_listDevices(JNIEnv* env, jobject)
{
std::string text;
ACameraIdList *camList;
ACameraManager *camManager;
camManager = ACameraManager_create();
camera_status_t result = ACameraManager_getCameraIdList(camManager, &camList);
if (result == ACAMERA_OK)
{
text = "Error List devices";
}
else
{
text = "Device listed";
}
return env->NewStringUTF(text.c_str());;
}
but Android studio is writing
"Can't resolve type ACameraIdList"
What I'm doing wrong? I just added this code into .cpp file, nothing else. Idid not changed any other files.
As mentioned in the comments, you need to set the minSDK to 24 or higher.
The NdkCameraDevice.h header files detects the minSDK using the __ANDROID_API__ identifier:
#if __ANDROID_API__ >= 24
...
typedef struct ACameraIdList {
int numCameras; ///< Number of camera device Ids
const char** cameraIds; ///< list of camera device Ids
} ACameraIdList;
...
#endif
This means that if the minSDK is below 24 the C preprocessor will omit the declaration of ACameraIdList and other related structures and functions. This leads to the "Can't resolve type ACameraIdList" error.
I have a.so which defines void a() and b.so which defines void b(). They are both put in the .apk so they are available to the Android application.
Now suppose that I'm calling a() through JNI. Is it possible to call b() from a() while completely bypassing JNI?
Can I do it this way in Android (the code is only for illustration, so it might have some errors)?
void a() {
void *handle = dlopen("b.so", RTLD_LAZY);
void (*b)() = dlsym(handle, "b");
b();
}
Would I need to add the fully qualified path, or is b.so already in LD_LIBRARY_PATH for the app?
You can do it this way on Android, though take care of where the shared library has been put in Android folders. It can change from a version to another.
On api 17 for example, it remains in /data/app-lib/. You can hardwrite it, but the best is to make calls to Java (through JNI) to know where the libraries should be.
We're doing something like this in our project :
JNIEnv* env;
const char* temp;
jobject oActivity = state->activity->clazz;
jclass cActivity = env->GetObjectClass(oActivity);
// get the path to where android extracts native libraries to
jmethodID midActivityGetApplicationInfo = env->GetMethodID(cActivity, "getApplicationInfo", "()Landroid/content/pm/ApplicationInfo;");
jobject oApplicationInfo = env->CallObjectMethod(oActivity, midActivityGetApplicationInfo);
jclass cApplicationInfo = env->GetObjectClass(oApplicationInfo);
jfieldID fidApplicationInfoNativeLibraryDir = env->GetFieldID(cApplicationInfo, "nativeLibraryDir", "Ljava/lang/String;");
jstring sNativeLibraryDir = (jstring)env->GetObjectField(oApplicationInfo, fidApplicationInfoNativeLibraryDir);
temp = env->GetStringUTFChars(sNativeLibraryDir, NULL);
strcpy(libpath, temp);
strcat(libpath, "/");
Then you push your dlopen + dlsym combo in the fight and it should work.
As mentioned here : How do I load a shared object in C++?
There are two ways of loading shared objects in C++
For either of these methods you would always need the header file for the object you want to use. The header will contain the definitions of the classes or objects you want to use in your code.
#include "blah.h"
int main()
{
ClassFromBlah a;
a.DoSomething();
}
gcc yourfile.cpp -lblah
Dynamically (In Linux):
#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>
int main(int argc, char **argv) {
void *handle;
double (*cosine)(double);
char *error;
handle = dlopen ("libm.so", RTLD_LAZY);
if (!handle) {
fprintf (stderr, "%s\n", dlerror());
exit(1);
}
dlerror(); /* Clear any existing error */
cosine = dlsym(handle, "cos");
if ((error = dlerror()) != NULL) {
fprintf (stderr, "%s\n", error);
exit(1);
}
printf ("%f\n", (*cosine)(2.0));
dlclose(handle);
return 0;
}
PS : for the dynamic approach, it depends on platform : on Linux, you use dlopen, on windows, you use LoadLibrary.
I'm using Eclipse ADT with: CDT. Along with NDK interfacing with JNI, to c/c++, compiling c/c++ with Cygwin through Eclipse. Everything should be running the latest versions as this was just setup over the last two weeks.
When Building I get the following.
jni/testSocketClass.hpp:33:1: error: unknown type name 'class'
jni/testSocketClass.hpp:33:17: error: expected '=', ',', ';', 'asm' or '__attribute__' before '{' token
jni/ndkfoo.c:13:1: error: unknown type name 'mYNewClass'
JNI C file
#include <string.h>
#include <jni.h>
#include "testSocketClassWrapper.hpp"
void *m_GBLpmyCSocket;
ndkfoo.c
jstring Java_com_example_hydrobedcontrol1_MainActivity_inintNativeClass(JNIEnv * env, jobject object){
m_GBLpmyCSocket = MyClass_create();
MyClass_sendCommandToSerialDevice(m_GBLpmyCSocket, 0, 0, 0);
return (*env)->NewStringUTF(env, "started");
}
Class Wrapper .hpp
//file testSocketClassWrapper.hpp
#ifndef _MY_SOCKETCLASS_WRAPPER_H
#define _MY_SOCKETCLASS_WRAPPER_H
#include"testSocketClass.hpp"//<<<<<<<<<<<<<<<<<<<<<Wrong inclusion
#ifdef __cplusplus
extern "C" void* MyClass_create() {
return new mYNewClass;
}
extern "C" void MyClass_release(void* myclass) {
delete static_cast<mYNewClass*>(myclass);
}
extern "C" void MyClass_sendCommandToSerialDevice(void* myclass, int cmd, int params, int id) {
static_cast<mYNewClass*>(myclass)->sendCommandToSerialDevice(cmd,params,id);
}
#endif
#endif /* _MY_SOCKETCLASS_WRAPPER_H_INCLUDED */
Class wrapper .cpp
//file testSocketClassWrapper.cpp
#include "testSocketClassWrapper.hpp"
Class .h
// file testSocketClass.hpp
class mYNewClass{///////////////////////ERROR HERE////////////////////////////////
//public:
void sendCommandToSerialDevice(int Command, int Parameters, int DeviceID);
//int sockfd;
};
Class .cpp
// file testSocketClass.cpp
#include "testSocketClass.hpp"
void mYNewClass::sendCommandToSerialDevice(int Command, int Parameters, int DeviceID){
char testc[100];
sprintf(testc, "%d, %d, %d", Command, Parameters, DeviceID);
}
I've read countless questions on this topic and have had quite a few issues getting this far with Eclipse configurations too. But from what I've pulled together from other questions on this topic, I have hit a wall and do not know how to proceed at this point. Suggestions?
EDIT BELOW - ANSWER
After review with a colleague(sometimes a second pair of eyes helps bring things out), we located my error. See the line labeled improper inclusion in the Class wrapper .hpp. That header should be relocated as follows in the Class wrapper .cpp NOTE: the functions where also moved to the .cpp and the .hpp is now empty.
//file testSocketClassWrapper.cpp
#include "testSocketClassWrapper.hpp"
#include "testSocketClass.hpp"
extern "C" void* MyClass_create() {
return new mYNewClass;
}
extern "C" void MyClass_release(void* myclass) {
delete static_cast<mYNewClass*>(myclass);
}
extern "C" void MyClass_sendCommandToSerialDevice(void* myclass, int cmd, int params, int id) {
static_cast<mYNewClass*>(myclass)->sendCommandToSerialDevice(cmd,params,id);
}
Also for completeness as this has been no walk in the park, are the MK files.
Android.mk
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
# Here we give our module name and source file(s)
LOCAL_MODULE := ndkfoo
LOCAL_SRC_FILES := ndkfoo.c testSocketClassWrapper.cpp testSocketClass.cpp
include $(BUILD_SHARED_LIBRARY)
Application.mk
APP_STL:=stlport_static
Thank you for the prompt reply krsteeve. And yet another way to do this. Will keep it in mind.
testSockedClass.hpp is being included from a .c file, so it's being compiled with a C-compiler. class doesn't exist in C. Change ndkfoo.c to a .cpp file.
You will have to format things a bit differently in C++:
extern "C"
{
jstring Java_com_example_hydrobedcontrol1_MainActivity_inintNativeClass(JNIEnv * env, jobject object);
}
jstring Java_com_example_hydrobedcontrol1_MainActivity_inintNativeClass(JNIEnv * env, jobject object){
m_GBLpmyCSocket = MyClass_create();
MyClass_sendCommandToSerialDevice(m_GBLpmyCSocket, 0, 0, 0);
return env->NewStringUTF("started");
}
Note the form of the NewStringUTF call in C++ vs C.
EDIT: I've solved this problem (and found the next issue in the long line of JNI tribulations!) by changing the following:
Delete "static" declaration of the native method in both Java and C++, add a Java method to get an instance of my SimpleGame class, and calling
public static void callCppApiResponse (String result, String token, long context) {
**getInstance()**.cppAndroidApiResponse(token, 200, result, result.length(), context);
}
Now the information successfully makes it from Java to C++. Hopefully this may help someone else with a similar issue.
ORIGINAL POST:
I'm using the Cocos2d-x platform and writing C++ code for a cross-platform iOS/Android app, and there are certain methods that I need to be handled by the respective native iOs/Android systems, so I need to be able to call Java from C++ then call C++ from Java.
I am able to load the shared library for the app and trigger a Java response from C++, but when I try to call C++ from Java I get the following logcat output and the app crashes:
dalvik vm No implementation found for native Lorg/cocos2dx/simplegame/SimpleGame; cppSideAndroidApiResponse:(Ljava/lang/String;ILJava/lang/String;IJ)V
AndroidRuntime java.lang.UnsatisfiedLinkError: Native method not found: org.cocos2dx.simplegame.SimpleGame.cppAndroidApiResponse:(Ljava/lang/String;ILJava/lang/String;IJ)V
Here is the relevant Java code:
package org.cocos2dx.simplegame;
private static native void cppAndroidApiResponse(String token, int response, String data, int dataLen, long context);
static {
System.loadLibrary("sb");
}
public static void callCppApiResponse (String result, String token, long context) {
//This is where the error is triggered
cppAndroidApiResponse(token, 200, result, result.length(), context);
}
and in the C++ file cppSide.h:
# if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID)
#include "platform/android/jni/JniHelper.h"
#include <jni.h>
#endif
#ifdef __cplusplus
extern "C" {
#endif
# if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID)
JNIEXPORT static void JNICALL
Java_org_cocos2dx_simplegame_SimpleGame_cppAndroidApiResponse(JNIEnv *, jobject, jstring, jint, jstring, jint, jlong);
#endif
#ifdef __cplusplus
}
#endif
Then finally the cppSide.cpp file
#include "cppSide.h"
extern "C" {
JNIEXPORT static void JNICALL
Java_org_cocos2dx_simplegame_SimpleGame_cppAndroidApiResponse(JNIEnv *env, jobject obj, jstring token, jint response, jstring data, jint length, jlong context)
{ ...do stuff...}
}
I know that my shared library is loading because I get my C++ based loading screen, and I know that JNI is working at least partially because I am able to call Java from that same cppSide.cpp file in another method. I have been researching the JNI and Cocos2d-X documentation, but to no avail.
Thanks in advance!
I'm new to jni, and I was going over a tutorial to implement a simple native method, but I'm getting an unsatisfiedlinkerror. As far as I know, I followed the steps in the tutorial exactly. Please help me.
Here is the java wrapper code:
package com.cookbook.jni;
public class SquaredWrapper {
// Declare native method (and make it public to expose it directly)
public static native int squared(int base);
// Provide additional functionality, that "extends" the native method
public static int to4(int base)
{
int sq = squared(base);
return squared(sq);
}
// Load library
static {
System.loadLibrary("squared");
}
}
Here's what my Android.mk file looks like:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := squared
LOCAL_SRC_FILES := squared.c
include $(BUILD_SHARED_LIBRARY)
Here's what my .c file looks like:
#include "squared.h"
#include <jni.h>
JNIEXPORT jint JNICALL Java_org_edwards_1research_demo_jni_SquaredWrapper_squared
(JNIEnv * je, jclass jc, jint base)
{
return (base*base);
}
And here is what my .h file looks like:
enter code here/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_cookbook_jni_SquaredWrapper */
#ifndef _Included_com_cookbook_jni_SquaredWrapper
#define _Included_com_cookbook_jni_SquaredWrapper
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_cookbook_jni_SquaredWrapper
* Method: squared
* Signature: (I)I
*/
JNIEXPORT jint JNICALL Java_com_cookbook_jni_SquaredWrapper_squared
(JNIEnv *, jclass, jint);
#ifdef __cplusplus
}
#endif
#endif
Your JNI signature doesn't match. In your .c file, change:
JNIEXPORT jint JNICALL Java_org_edwards_1research_demo_jni_SquaredWrapper_squared
to
JNIEXPORT jint JNICALL Java_com_cookbook_jni_SquaredWrapper_squared
Generally there are two ways to "glue" native C through JNI to a Java function. The first is what you're attempting to do here, that is use a predetermined signature that JNI will recognize and associate with your appropriate Java code. The second is to pass function pointers, signatures, and Java class names into JNI when you include the library.
Here's the second method that would bind the native function to the appropriate Java code (this would be your .c file):
#include "squared.h"
#include <jni.h>
static const char* SquaredWrapper = "com/cookbook/jni/SquaredWrapper";
jint squared(JNIEnv * env, jobject this, jint base) {
return (base*base);
}
// Methods to register for SquaredWrapper
static JNINativeMethod SquareWrapperMethods[] = {
{"squared", "(I)I", squared}
};
jint JNI_OnLoad(JavaVM* vm, void* reserved) {
JNIEnv* env;
if ( (*vm)->GetEnv(vm, (void **) &env, JNI_VERSION_1_6) != JNI_OK)
return JNI_ERR;
jclass class = (*env)->FindClass(env, SquaredWrapper);
(*env)->RegisterNatives(env, class, SquaredWrapperMethods, sizeof(SquaredWrapperMethods)/sizeof(SquaredWrapperMethods[0]));
return JNI_VERSION_1_6;
}
void JNI_OnUnload(JavaVM* vm, void* reserved) {
JNIEnv* env;
if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_6) != JNI_OK)
return;
jclass class = (*env)->FindClass(env, SquaredWrapper);
(*env)->UnregisterNatives(env, class);
return;
}
It's a good deal longer but it gives you a lot more flexibility when binding native code. The definition for squared and the includes are as you would expect. on the 4th line, the static const char* SquaredWrapper is an escaped string with the fully qualified package name of the class you want to bind squared to. Near the bottom are the JNI_OnLoad and JNI_OnUnLoad functions that take care of binding and unbinding the functions on library load and unload. The last piece is the JNINativeMethod array. This array contains as each entry an array of size 3 whose components are the Java name of the method as a const char*, the JNI signature of the Java method, and the native C function pointer to bind to that method. The JNI function signature tells the environment the argument list format and return value of the Java function. The format is "(Arg1Arg2Arg3...)Ret", so a function that takes an int and double and returns a float would have a signature of "(ID)F", and a function that takes no arguments and returns void would be "()V". I use this handy cheat sheet to remember most of the shorthand:
http://dev.kanngard.net/Permalinks/ID_20050509144235.html
Good luck :)
Edit: Oh, BTW, you'll likely want to add the signatures for JNI_OnLoad and JNI_UnOnLoad to your header, and change the name of your native function prototype to reflect the new .c file.
This is kind of an obscure case, but if you get an access violation in your native code, Android will cover up the thrown exception and throw the error you got. In my case, the native code threw an access violation but Java kept running. It then tried to call a JNI method on the crashed NDK.
To find the access violation, I ended up moving the offending JNI method to another IDE to debug.
I hope this saves someone the amount of time it took me to figure this out.