I am trying to follow this tutorial
http://www3.ntu.edu.sg/home/ehchua/programming/java/JavaNativeInterface.html
but I'm in eclipse making an android app
So I create blank project and add a new class
package com.example.jpgtest;
import android.util.Log;
public class TestJNIPrimitive {
//static {
//System.loadLibrary("myjni"); // myjni.dll (Windows) or libmyjni.so (Unixes)
// System.load("d:/libjpeg.so");
// Log.d("TestJNIPrimitive", "load ok");
// }
// Declare a native method average() that receives two ints and return a double containing the average
private native double average(int n1, int n2);
// Test Driver
public static void main() {
System.load("d:/libjpeg.so");
Log.d("TestJNIPrimitive", "load ok");
double d = new TestJNIPrimitive().average(3, 2);
//double d = new TestJNIPrimitive().average(3, 2);
Log.d("TestJNIPrimitive", "d="+d);
}
}
and call it from onCreate()
TestJNIPrimitive t = new TestJNIPrimitive();
t.main();
I made the c and h file like this
TestJNIPrimitive.h
/
* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class TestJNIPrimitive */
#ifndef _Included_TestJNIPrimitive
#define _Included_TestJNIPrimitive
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: TestJNIPrimitive
* Method: average
* Signature: (II)D
*/
JNIEXPORT jdouble JNICALL Java_TestJNIPrimitive_average
(JNIEnv *, jobject, jint, jint);
#ifdef __cplusplus
}
#endif
#endif
TestJNIPrimitive.c
#include <jni.h>
#include "TestJNIPrimitive.h"
JNIEXPORT jdouble JNICALL Java_TestJNIPrimitive_average(JNIEnv *env, jobject thisObj, jint n1, jint n2)
{
jdouble result;
//printf("In C, the numbers are %d and %d\n", n1, n2);
result = ((jdouble)n1 + n2) / 2.0;
// jint is mapped to int, jdouble is mapped to double
return result;
}
i build it and make a libjpg.so on my d drive root
i check the symbols
$ readelf -Ws /cygdrive/d/libjpeg.so | grep average
272: 00003f5c 72 FUNC GLOBAL DEFAULT 7 Java_TestJNIPrimitive_average
1896: 00003f5c 72 FUNC GLOBAL DEFAULT 7 Java_TestJNIPrimitive_average
When I run it, the load works ok but the call to average fails with
No implementation found for native Lcom/example/jpegtest/TestJNIPrimative;.average (II)D
Any ideas please, I'm really stuck on this
Thanks
You are missing the packace into JNI. That's the reason the function is not found.
Your Java package is: com.example.jpgtest
So your Native function must be:
JNIEXPORT jdouble JNICALL Java_com_example_jpgtest_TestJNIPrimitive_average(JNIEnv *env, jobject thisObj, jint n1, jint n2)
{
...
}
After compile with ndk-build, and use your new libjpeg.so must work.
I have to add that your java looks weird (at less to my eyes). I will implement that as:
package com.example.jpgtest;
import android.util.Log;
public class TestJNIPrimitive
{
// Test Driver
public static void main()
{
Log.d("TestJNIPrimitive", "load ok");
double d = new TestJNIPrimitive().average(3, 2);
Log.d("TestJNIPrimitive", "d="+d);
}
// Native functions (Callbacks from Java to C++)
// =========================================================================
// Declare a native method average() that receives two ints and return a double containing the average
private native double average(int n1, int n2);
// Load Native Libraries
// =========================================================================
static
{
System.load("libjpeg.so");
}
}
And the so must be inside folder libs/armeabi-v7a
I you want a basic theory check this or the specifications
i was unable to figure out the problem, but instead i read this
https://developer.android.com/tools/sdk/ndk/index.html
i build and ran the hello-jni example and then i build a new project and then added in the jni folder from the test and change the package name and the class name to match the new project
then i started modifying the functions to my needs and its ok now
not easy stuff this
Related
I am working on an example of JNI in Android Studio, the goal it to generate a Random Value and pass it to a native function which will calculate its Square and return the result in this format ( Number/Square ).
I am passing the jint number as a parameter of the function, but the result is totally wrong (the result displayed is totaly wrong).
Here is my code :
Button Generating the number and calling the native function :
Button ButtonP = (Button)findViewById(R.id.button3);
ButtonP .setOnClickListener(
new View.OnClickListener()
{
public void onClick(View view)
{
Random r = new Random();
Integer valeur = 1 + r.nextInt(10 - 1);
Log.i("Tag", "Random Value BARRAK " + valeur);
TextView tv = (TextView) findViewById(R.id.sample_text);
tv.setText(stringFromJNIStop(valeur));
}
});
The Native function:
public native String stringFromJNIStop(Integer nombre);
The implementation of the function in the cpp file:
extern "C"
JNIEXPORT jstring JNICALL
Java_fr_utbm_testjniapplication1_MainActivity_stringFromJNIStop(
JNIEnv *env,
jobject, /* this */
jint nombre) {
jint CarreNombre = nombre*nombre;
//Convertir le carré en un jstring
char bufCarreNombre[64];
sprintf(bufCarreNombre, "%d", CarreNombre); // error checking omitted
jstring jStringCarre = (*env).NewStringUTF(bufCarreNombre);
//Le convertir en un char *
const char *strCarre= (*env).GetStringUTFChars(jStringCarre,0);
//Convertir le nombre en un jstring
char bufNombre[64];
sprintf(bufNombre, "%d", nombre); // error checking omitted
jstring jStringNombre = (*env).NewStringUTF(bufNombre);
//Le convertir en char *
const char *strNombre= (*env).GetStringUTFChars(jStringNombre,0);
//Concaténer les deux
char *concatenated;
concatenated = (char *) malloc(strlen(strNombre) + strlen("/") + strlen(strCarre) + 1);
strcpy(concatenated, strNombre);
strcat(concatenated, "/");
strcat(concatenated, strCarre);
/* Create java string from our concatenated C string */
jstring retval = (*env).NewStringUTF(concatenated);
//need to release this string when done with it in order to
//avoid memory leak
(*env).ReleaseStringUTFChars(jStringNombre,strNombre);
(*env).ReleaseStringUTFChars(jStringCarre,strCarre);
/* Free the memory in concatenated */
free(concatenated);
return retval;
}
The Native function:
public native String stringFromJNIStop(Integer nombre);
The implementation of the function in the cpp file:
extern "C"
JNIEXPORT jstring JNICALL
Java_fr_utbm_testjniapplication1_MainActivity_stringFromJNIStop(
JNIEnv *env,
jobject, /* this */
jint nombre) {
This does not agree with your Java. You've changed it from int to Integer in the Java without regenerating your .h file, or you've changed or invented your .h file without reference to your .java file; or your .cpp file doesn't agree with your .h/.hpp file. Don't do this. Use javah to generate your .h/.hpp file, and redo it every time you change the native declaration(s) in your .java files, and make sure your .cpp file agrees with the .h/.hpp file. It should be:
extern "C"
JNIEXPORT jstring JNICALL
Java_fr_utbm_testjniapplication1_MainActivity_stringFromJNIStop(
JNIEnv *env,
jobject, /* this */
jobject nombre) {
where nombre refers to an Integer. However it would be better, and would always have been better, to define your Java native method as:
public native String stringFromJNIStop(int nombre);
which will now agree with your existing .cpp.
Also your .cpp should #include your .h/.hpp. Then you wouldn't have needed extern "C" or JNI_EXPORT or JNI_CALL, and the compiler may have detected the signature disagreement between .cpp and .h/.hpp.
After some reflexion, problem was resolved, in fact, in order to implement the JNI function using jint, we have to adopt the exact conversion rules in the JAVA part, so instead of declaring the random value as an integer, we must declare it as a long ! so we must work as follows :
long valeur = 1 + r.nextInt(10 - 1);
I need to integrate C++ files into my Android application project.
I can build the files and .so file is generated.
This is the header file which has the function process().
I need to invoke this method from my .java file.
class PayrollGenerator {
public:
typedef void (* PAYROLL_READY_CALLBACK) (std::vector<int> list, int id);
typedef void (* PROGRESS_CALLBACK) (int progress);
private:
PAYROLL_READY_CALLBACK _dataReadyCallback;
PROGRESS_CALLBACK _progressCallback;
public:
DataProcessor(PAYROLL_READY_CALLBACK dataReadyCallback, PROGRESS_CALLBACK progressCallback);
void process(int data);
};
Two callbacks are there which will give me result and the progress data of the data being processed.
I am not able to design the JNI methods for it.
BTW, I am run simple C++ programs from .java files.
But this one is quite complex to me.
Please help !!
Progress -
I created a C++ file and wrote the wrapper
JNIEnv *global;
jobject impl;
struct DataProcessorStruct {
jobject callback_ptr;
DataProcessor *dataProcessor;
};
void dataReadyCallback(std::vector<jint> processedSamples, jint heartRate){
jintArray arr = global->NewIntArray( processedSamples.size() );
global->SetIntArrayRegion( arr, 0, processedSamples.size(), ( jint * ) &processedSamples[0] );
jclass clazz = global->FindClass("com/app/AudioActivity");
jmethodID method = global->GetMethodID(clazz, "dataReady","[Ljava/util/List;I)V");
global->CallVoidMethod(impl,method,arr,heartRate);
__android_log_print(ANDROID_LOG_INFO, "test", "C-dataReadyCallback");
}
void progressCallback(jint progress){
jclass clazz = global->FindClass("com/app/AudioActivity");
jmethodID method = global->GetMethodID(clazz, "dataProcessProgress","(I)V");
global->CallVoidMethod(impl, method,progress);
__android_log_print(ANDROID_LOG_INFO, "test", "C-progressCallback");
}
JNIEXPORT jlong JNICALL Java_com_app_AudioActivity_createDataProcessorObject (JNIEnv * env, jobject obj){
global = env;
impl = obj;
DataProcessorStruct *cpp_obj = new DataProcessorStruct();
if (cpp_obj == NULL) {
return 0;
}
DataProcessor *csObj = new DataProcessor(dataReadyCallback,progressCallback);
if (csObj == NULL) {
return 0;
}
cpp_obj->dataProcessor = csObj;
cpp_obj->callback_ptr = env->NewGlobalRef(obj);
return (jlong)cpp_obj;
}
JNIEXPORT void JNICALL Java_com_app_processData (JNIEnv * env, jobject obj,jlong objId,jint sample,jint dataLeft){
impl = obj;
DataProcessorStruct *cpp_obj = (DataProcessorStruct *)objId;
DataProcessor *dataProcessor=cpp_obj->dataProcessor;
if (dataProcessor != NULL){
dataProcessor->process(sample,dataLeft);
}
}
The native methods in Java are -
public native long createDataProcessorObject();
public native void processData(long dataProcessor,int sample, int dataLeft);
Is this the right way of doing so ?
And is there any way I don't have to call class Java methods directly from dataReadyCallback() and progressCallback() C++ methods but somehow I can call interface methods which are in Java to get invoked from these C++ methods so that any class listening to these callbacks should get notified and not a particular class?
You can easily define native functions in Java wich are callbacks in your C++ programme. Usually you start declaring a class and some functions in java:
class MyJNative {
static { // static bloc, executed when class is loaded => load your cpp library
System.out.print ("Load library in ");
System.out.println(System.getProperty("java.library.path"));
System.loadLibrary("MyNativeCPP");
}
private static int next_id=1; // every object gets a unique id
private int id; // unique object id
public MyJNative () { // constructor (sets unique id)
id = next_id++;
// ...
}
public native static void doSetup(); // initialisation for your factory function
public native void doSomething(); // example method
...
}
Then you'll let javah generate the header for the native funtions in C++. You shouldn't hand-write the header yourself ! Here a step by step tutorial, and the example for the code above:
...
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: MyJNative
* Method: doSetup
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_MyJNative_doSetup
(JNIEnv *, jclass);
/*
* Class: MyJNative
* Method: doSomething
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_MyJNative_doSomething
(JNIEnv *, jobject);
...
#ifdef __cplusplus
}
#endif
#endif
As you see, JNI is designed to call C++/C functions and not member functions of C++ class. So you need to define some global functions (or static member functions of your class), that will get as a (first) parameter some ID which can identify the C++ object, and will then forward the call to this object.
The example above is simplified, but the idea would be that, the Java class maintains a unique ID. You could imagine that at creation, after setting the ID, it calls a factory function in C++, that would maintain a map<jint, unique_ptr<MyCPPclass>> objects so that all other functions like doSomething() can be invoked with ID as parameter and then call objects[ID].get()->doSomething().
The problem with this approach is the life of your C++ object: you manage it's destruction: C++ objects created on the C++ side won't be garbage collected, so it's difficult to guess when they are no longer needed.
A variant of this approach would be to host ther you "host it" somewhere in a byte array of a java object. But in this case the problem is the other way round: when the Java object is no longer needed, the C++ side won't be informed that the object is destructed and so the destructor won't be called.
As you can see, the JNI design makes it rather difficult to manage objects on both sides. So the safest approach is to manage objects on one side only and on the other side expose only functions that will use/manipulate the object.
I am calling a native function that do a very simple summation operation, but it returns a wrong result why ?!
Here is my java code:
package com.example.sharedlibexample;
import android.app.Activity;
import android.os.Bundle;
import android.widget.TextView;
public class MainActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TextView tv = (TextView) findViewById(R.id.text);
tv.setText("Value = " + String.valueOf(addInJNI(15, 8)));
}
public native int addInJNI(int a, int b);
static {
System.loadLibrary("hello-jni");
}
}
and this is the native code:
int Java_com_example_sharedlibexample_MainActivity_addInJNI(int a, int b) {
return a + b;
}
The result of this sum example is:
-921636135
The two first parameters in the native function should be JNIEnv* env, jobject thiz (or JNIEnv* env, jclass clazz for a static method). What your code current actually does, is that it adds the values of the env and thiz pointers, instead of the real parameters you intended to pass.
You can use javah to generate the method signatures (in the form of a header file), which also includes other attributes which may be necessary in some cases (like JNIEXPORT, JNICALL).
With help from this blog
I could find the solution by using the jint rather than int
The solution is:
Create a header file bativeLib.h
#include <jni.h>
/* Header for class com_marakana_NativeLib */
#ifndef _Included_org_example_ndk_nativeLib
#define _Included_org_example_ndk_nativeLib
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_marakana_NativeLib
* Method: add
* Signature: (II)I
*/
JNIEXPORT jint JNICALL Java_com_example_sharedlibexample_MainActivity_addInJNI
(JNIEnv *, jobject, jint, jint);
#ifdef __cplusplus
}
#endif
#endif
and the C lib looks like:
#include "nativeLib.h"
JNIEXPORT jint JNICALL Java_com_example_sharedlibexample_MainActivity_addInJNI
(JNIEnv * env, jobject obj, jint value1, jint value2) {
return (value1 + value2);
}
I am a student. Recently I've been building a Face recognize project using opencv but I dont know where to start.
I successfully build my Face detection using OpenCv4Android by reading opencv face detection sample.
Now I start to build Face recognize (using LBPH algorithm) part, I read Opencv document and search google for tutorial that I can actually follow but i failed (there lots of tutorial using javacv but I want to use OpenCv4Android instead) :(
can anyone help me with the step by step tutorial about what should i do to using face recognize in OpenCV4Android SDK? Big thanks to you.
Additional:
I find out about FaceRecognizer.java class in opencv/contrib
I find facerec.java in OpenCV4android folder
I read somewhere and try the method FaceRecognize model = createLBPHFaceRecognizer() ---> but the method createLBPHFaceRecognizer() return error not found. where can i find and use this method?
Please help me what I need to do next? A lot of thanks!!!!!
The createFisherFaceRecognizer() method (as well as the other 2 createXXXFaceRecognizer()) were skipped during the java wrapper code generation, it's a known, yet unsolved problem.
The best solution could be implementing it with jni/ndk. You will have to build:
an .so file with the native c++ code, let's call it facerec.so an
additional java wrapper class calling that, FisherFaceRecognizer.java
sadly, can't help much with ndk(no such thing here), but it worked nicely on desktop/eclipse (the dll/so would go right into your project folder), so here's the code (quite a wall of it).
// --- 8< --------- facerec.cpp -------------------------------
#include "jni.h"
#include "opencv2/contrib/contrib.hpp"
#ifdef __cplusplus
extern "C" {
#endif
JNIEXPORT jlong JNICALL Java_FisherFaceRecognizer_createFisherFaceRecognizer_10(JNIEnv* env, jclass);
JNIEXPORT jlong JNICALL Java_FisherFaceRecognizer_createFisherFaceRecognizer_10(JNIEnv* env, jclass) {
try {
cv::Ptr<cv::FaceRecognizer> pfr = cv::createFisherFaceRecognizer();
pfr.addref(); // this is for the 2.4 branch, 3.0 would need a different treatment here
return (jlong) pfr.obj;
} catch (...) {
jclass je = env->FindClass("java/lang/Exception");
env->ThrowNew(je, "sorry, dave..");
}
return 0;
}
JNIEXPORT jlong JNICALL Java_FisherFaceRecognizer_createFisherFaceRecognizer_11(JNIEnv* env, jclass, jint num_components);
JNIEXPORT jlong JNICALL Java_FisherFaceRecognizer_createFisherFaceRecognizer_11(JNIEnv* env, jclass, jint num_components) {
try {
cv::Ptr<cv::FaceRecognizer> pfr = cv::createFisherFaceRecognizer(num_components);
pfr.addref();
return (jlong) pfr.obj;
} catch (...) {
jclass je = env->FindClass("java/lang/Exception");
env->ThrowNew(je, "sorry, dave..");
}
return 0;
}
JNIEXPORT jlong JNICALL Java_FisherFaceRecognizer_createFisherFaceRecognizer_12(JNIEnv* env, jclass, jint num_components, jdouble threshold);
JNIEXPORT jlong JNICALL Java_FisherFaceRecognizer_createFisherFaceRecognizer_12(JNIEnv* env, jclass, jint num_components, jdouble threshold) {
try {
cv::Ptr<cv::FaceRecognizer> pfr = cv::createFisherFaceRecognizer(num_components,threshold);
pfr.addref();
return (jlong) pfr.obj;
} catch (...) {
jclass je = env->FindClass("java/lang/Exception");
env->ThrowNew(je, "sorry, dave..");
}
return 0;
}
#ifdef __cplusplus
}
#endif
// --- 8< --------- FisherFaceRecognizer.java -----------------
import org.opencv.contrib.FaceRecognizer;
import org.opencv.core.Core;
public class FisherFaceRecognizer extends FaceRecognizer {
static{ System.loadLibrary("facerec"); }
private static native long createFisherFaceRecognizer_0();
private static native long createFisherFaceRecognizer_1(int num_components);
private static native long createFisherFaceRecognizer_2(int num_components, double threshold);
public FisherFaceRecognizer () {
super(createFisherFaceRecognizer_0());
}
public FisherFaceRecognizer (int num_components) {
super(createFisherFaceRecognizer_1(num_components));
}
public FisherFaceRecognizer (int num_components, double threshold) {
super(createFisherFaceRecognizer_2(num_components, threshold));
}
}
once you got all this compiled (congrats!!), you would call it like this:
FaceRecognizer facerec = new FisherFaceRecognizer();
facerec.load("/sdcard/smile.yml"); // note, that it can't read from apk or zip, so you need to copy it somewhere
Mat img = ...//test face
int [] label = new int[1];
double [] conf = new double[1];
facerec.predict(img, label, conf);
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.