native thread creation

2,201 views
Skip to first unread message

extrapedestrian

unread,
Apr 14, 2009, 6:31:39 AM4/14/09
to android-ndk
I want to start another thread in native function called by JNI,
I'd like to call Java static callback functions from this new thread.
is there any code example for this?

David Turner

unread,
Apr 14, 2009, 6:35:33 AM4/14/09
to andro...@googlegroups.com
I'm pretty sure that this is not supported by JNI. Besides, Dalvik needs to know about the threads you create if you ever intend to call a VM functions from them; so in the end you should create the threads from your Java code.

fadden might correct me if I'm wrong.

David Turner

unread,
Apr 14, 2009, 6:36:42 AM4/14/09
to andro...@googlegroups.com
To give more details, some parts of the platform do create threads in a JNI wrapper, but these never interact directly with the VM part of the platform (instead the usually communicate with another VM thread through a pipe or something like that).

Freepine

unread,
Apr 14, 2009, 9:14:44 AM4/14/09
to andro...@googlegroups.com
IMHO, if you invoke createThreadEtc (defined in utils/threads.h) or inherit Thread in JNI layer of a java app forked from app_process, then it can callback java functions by default?
Per my understanding, that's how binder thread manages to notify java listeners:)

It seems AndroidRuntime:: startReg will replace the thread entry function with AndroidRuntime::javaThreadShell which attaches the created thread to VM automatically.

David Turner

unread,
Apr 14, 2009, 11:45:35 AM4/14/09
to andro...@googlegroups.com
On Tue, Apr 14, 2009 at 3:14 PM, Freepine <free...@gmail.com> wrote:
IMHO, if you invoke createThreadEtc (defined in utils/threads.h) or inherit Thread in JNI layer of a java app forked from app_process, then it can callback java functions by default?
Per my understanding, that's how binder thread manages to notify java listeners:)

It seems AndroidRuntime:: startReg will replace the thread entry function with AndroidRuntime::javaThreadShell which attaches the created thread to VM automatically.

that may be possible, but I'm really unsure that this is the officially supported way to perform this action.
The 'utils' library is not part of the stable native APIs of the Android platform and may change in the future
for a variety of reasons.

I'm pretty sure that JNI doesn't support a liberally created pthread_t calling VM code (because JNI was designed to cope with green threads
and other system-specific oddities). I also know that any code executed by the VM must have a corresponding VM thread structure associated
to it that is vital for proper execution.

What you describe seems to be a valid ways to "attach" a pthread_t to the VM, but right now I would see this as an implementation detail
within the system that applications should not rely on. If fadden tells us that this is the *right* way to do it, then we will design a small public
headers to add to the NDK's set of public/stable APIs.

 

fadden

unread,
Apr 14, 2009, 7:41:44 PM4/14/09
to android-ndk
On Apr 14, 8:45 am, David Turner <di...@android.com> wrote:
> I'm pretty sure that JNI doesn't support a liberally created pthread_t
> calling VM code (because JNI was designed to cope with green threads
> and other system-specific oddities). I also know that any code executed by
> the VM must have a corresponding VM thread structure associated
> to it that is vital for proper execution.

The correct way to do this is:

(1) Get the JavaVM pointer. Since you didn't create the VM, you need
to get it from:

jint JNI_GetCreatedJavaVMs(JavaVM **vmBuf, jsize bufLen, jsize
*nVMs)

Use the first VM in the list (in Android, there will only be one).

(2) Attach the thread to the VM:

jint AttachCurrentThread(JavaVM *vm, JNIEnv **p_env, void *thr_args)

This creates a Thread object and various internal data structures,
allocates a JNIEnv, and returns a pointer to it. The JNIEnv is thread-
specific; you MUST NOT share JNIEnv between threads. (In environments
where CheckJNI is enabled, such as the emulator, using the wrong
pointer will abort the VM. In other environments, very strange things
will happen.)

(3) Do whatever. Be mindful of local reference creation -- any JNI
call that returns a reference creates them, and if you don't return to
Java they just build up. See also dalvik/docs/jni-tips.html in the
source tree.

(4) Before your thread exits, call DetachCurrentThread. If you fail
to do this, the VM won't know your thread is gone, and resources will
not be released. (Dalvik is a bit paranoid, and will detect the
situation and abort.)


None of this is Android-specific. More details are available in the
JNI spec:

http://java.sun.com/javase/6/docs/technotes/guides/jni/spec/invocation.html#wp9502

As digit noted, using internal calls like createThreadEtc() is a
really bad idea.

Regarding "green" threads, there are some notes in the JNI reference
manual about thread model mismatches. See http://java.sun.com/docs/books/jni/html/other.html#29406
.

Freepine

unread,
Apr 14, 2009, 9:59:28 PM4/14/09
to andro...@googlegroups.com
I didn't realize that libutils won't be part of ndk.Yes, then you have to do it manually in an official way as fadden described. I think that's exactly what's wrapped in createThreadEtc:)
Thanks for the clarification and 

BTW, will binder IPC be part of ndk?

Dianne Hackborn

unread,
Apr 14, 2009, 10:06:10 PM4/14/09
to andro...@googlegroups.com
On Tue, Apr 14, 2009 at 6:59 PM, Freepine <free...@gmail.com> wrote:
BTW, will binder IPC be part of ndk?

Not at this point, there are no C++ APIs in the initial NDK.

--
Dianne Hackborn
Android framework engineer
hac...@android.com

Note: please don't send private questions to me, as I don't have time to provide private support, and so won't reply to such e-mails.  All such questions should be posted on public forums, where I and others can see and answer them.

Freepine

unread,
Apr 14, 2009, 10:08:17 PM4/14/09
to andro...@googlegroups.com
On Wed, Apr 15, 2009 at 10:06 AM, Dianne Hackborn <hac...@android.com> wrote:
On Tue, Apr 14, 2009 at 6:59 PM, Freepine <free...@gmail.com> wrote:
BTW, will binder IPC be part of ndk?

Not at this point, there are no C++ APIs in the initial NDK.

Oh:( Thanks

extrapedestrian

unread,
Apr 16, 2009, 5:42:09 AM4/16/09
to android-ndk
Thank you for your replies.

I created small example:
Android application communicates with native server through JNI:
Native function opens unix socket and sends message to server (native
server app running in adb shell listening on unix socket)
Native function also starts new thread which waits for server
response. And then returns to Java.
Server answer after 5 seconds. Thread catches reply, calls JNI
callback function and sends server response up to java.

Now I have one last problem: Activity detects its been accessed from
different thread and stops. So I can not poke activity directly from
native thread. But I would like to refresh Activity screen somehow to
display server response when it arrives. How can I do this?

Here is my code (for other lost souls):

Native c file:
--------------------------------------------------------------------------
#include "NativeMessenger.h"
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <pthread.h>


#define SOCK_PATH "/data/data/com.rtrk.jnicbus/echo_socket"
jmethodID g_mid;
pthread_t thr_rcv;
JavaVM *g_vm;
jobject g_obj;
FILE *logfile;
int s;

//this thread waits for server to respond
//when message is received, java callback
//function is called and message is sent as argument
static void *receive_thr(void *vptr_args)
{
int t;
char str_l[100];
JNIEnv *t_env;
int envErr = 0;

fprintf(logfile,"Receive thread: Logging started\n");

// get a local java env
envErr = (*g_vm)->AttachCurrentThread( g_vm, (void**)&t_env, NULL );
if ( envErr != 0 ){
if ( (*t_env)->ExceptionCheck( t_env ) == JNI_TRUE ){
(*t_env)->ExceptionDescribe( t_env );
}
fprintf(logfile,"Receive thread: error exception\n");
(*g_vm)->DetachCurrentThread(g_vm);
return NULL;
}
if((*t_env)->ExceptionOccurred(t_env)){
(*t_env)->ExceptionDescribe(t_env);
}
if (t_env == NULL) {
fprintf(logfile,"Receive thread: failed to get ENV pointer in
pushContext\n");
(*g_vm)->DetachCurrentThread(g_vm);
return NULL;
}

//thread could run indefinitely in while loop
//while(1)
//{
fprintf(logfile,"Receive thread: Start receive on socket: %d\n", s);
if ((t=recv(s, str_l, 100, 0)) > 0)
{
str_l[t] = '\0';
fprintf(logfile,"Receive thread: Server response: \"%s\"\n",
str_l);
(*t_env)->CallVoidMethod(t_env,g_obj,g_mid,(*t_env)->NewStringUTF
(t_env, str_l));
}
else
{
if (t < 0) perror("recv");
else printf("Receive thread: Server closed connection\n");
(*g_vm)->DetachCurrentThread(g_vm);
return NULL;
}
//}

fclose(logfile);

(*g_vm)->DetachCurrentThread(g_vm);

return NULL;
}

//This function is called from java class
//it connects to unix socket and sends message to server
//if server is not up and listening, exception will occure
//this function also starts new thread wich will wait for server
response
JNIEXPORT jstring JNICALL
Java_com_rtrk_jnicbus_NativeMessenger_sendMessage(JNIEnv *env, jclass
cls, jstring question)
{
char *str;
int t, len, strsize;
struct sockaddr_un remote;
int pterr = 0;
pid_t pid;
pthread_t main_thr;

//get string parameter passed from java function
str = (char*) (*env)->GetStringUTFChars(env, question, NULL);
strsize = (*env)->GetStringLength(env,question);

//log this thread just for info
main_thr = pthread_self();
fprintf(logfile,"Main thread = %d\n", main_thr);

//open unix socket
if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
fprintf(logfile,"error: socket\n");
return NULL;
}

//connect to socket
remote.sun_family = AF_UNIX;
strcpy(remote.sun_path, SOCK_PATH);
len = strlen(remote.sun_path) + sizeof(remote.sun_family);
if (connect(s, (struct sockaddr *)&remote, len) == -1) {
fprintf(logfile,"error: connect\n");
return NULL;
}
fprintf(logfile,"Socket:%d\n",s);

//send message to socket
fprintf(logfile,"Sending string: \"%s\"\n",str);
if (send(s, str, strsize, 0) == -1)
{
fprintf(logfile,"error: send\n");
return NULL;
}

//start receiving thread
if (pterr = pthread_create(&thr_rcv, NULL, receive_thr, NULL) !=
0)
{
fprintf(logfile,"Thread create failed, error: \"%d\"\n",pterr);
return NULL;
}

fprintf(logfile,"Receive thread started = %d\n", thr_rcv);

(*env)->ReleaseStringUTFChars(env, question, str);

return (*env)->NewStringUTF(env, "\n\nWait 5 seconds then refresh
to see server response! \n\n");
}

//startCallback is called to store callback function ID
//java class object is stored in global variable and used
//in receiving thread to call callback function
JNIEXPORT void JNICALL
Java_com_rtrk_jnicbus_NativeMessenger_startCallback(JNIEnv *env,
jobject obj, jint num)
{

//open log file
logfile = fopen ("/data/data/com.rtrk.jnicbus/android_native.log",
"wt");
fprintf(logfile,"Logging started");

jclass cls = (*env)->GetObjectClass(env, obj);

g_mid = (*env)->GetMethodID(env, cls, "callback", "(Ljava/lang/
String;)V");
if (g_mid == 0) {
return;
}
g_obj = (*env)->NewGlobalRef(env, obj);

}

//JNI_OnLoad is used to save virtual machine reference
JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved)
{
//store java virtual machine in global variable
g_vm = vm;
return JNI_VERSION_1_6;
}

//OnUnload is unused in example
JNIEXPORT void JNI_OnUnload(JavaVM *vm, void *reserved)
{
}
--------------------------------------------------------------------------

java code:

package com.rtrk.jnicbus;

import java.io.IOException;

import android.util.Log;

public class NativeMessenger {

public String m_Callback = "";
JNICallbackSockets m_hc;
public Boolean m_NewMsgArrived;

public NativeMessenger(JNICallbackSockets obj)
{
m_hc = obj;
}

public static native String sendMessage(String str) throws
IOException;
public native void startCallback(int num);

public void callback(String str) {
Log.i("JNI", "Callback from Native Messenger to Java Hello
Chat");
m_Callback = "Server response: " + str;
m_NewMsgArrived = true;
//This is not working because activity is called from
different thread!
//m_hc.display();
}

public void onLoad ()
{
try {
Log.i("JNI", "Trying to load NativeMessengerLib.so");
System.load("/data/data/com.rtrk.jnicbus/
NativeMessengerLib.so");
}
catch (UnsatisfiedLinkError ule) {
Log.e("JNI", "WARNING: Could not load
NativeMessengerLib.so");
}
}
}

Dianne Hackborn

unread,
Apr 16, 2009, 12:46:13 PM4/16/09
to andro...@googlegroups.com
This is just normal SDK programming.  You need to post a message to the main thread and do your work there.  (By stop I assume you mean throws an exception about accessing the view hierarchy from the wrong thread.)

fadden

unread,
Apr 16, 2009, 2:10:21 PM4/16/09
to android-ndk
On Apr 16, 2:42 am, extrapedestrian <extra.pedestr...@gmail.com>
wrote:
[...]

I commented on a few things I noticed in the code.


>         // get a local java env
>         envErr = (*g_vm)->AttachCurrentThread( g_vm, (void**)&t_env, NULL );
>         if ( envErr != 0 ){
>                 if ( (*t_env)->ExceptionCheck( t_env ) == JNI_TRUE ){
>                   (*t_env)->ExceptionDescribe( t_env );
>                 }
>                 fprintf(logfile,"Receive thread: error exception\n");
>                 (*g_vm)->DetachCurrentThread(g_vm);
>                 return NULL;
>         }
>         if((*t_env)->ExceptionOccurred(t_env)){
>             (*t_env)->ExceptionDescribe(t_env);
>     }
> if (t_env == NULL) {
> fprintf(logfile,"Receive thread: failed to get ENV pointer in
> pushContext\n");
> (*g_vm)->DetachCurrentThread(g_vm);
> return NULL;
> }

It's good to check for exceptions after making JNI calls. In this
case, however, you should *not* check for exceptions if
AttachCurrentThread fails. If it fails, t_env will not be valid, and
calls that use it will likely crash.

If the call succeeds, no exceptions will be pending in this thread,
and you can omit the second check too. Also, if the call succeeds,
you don't need to compare t_env to NULL.


>         //thread could run indefinitely in while loop
>         //while(1)
>         //{
>                 fprintf(logfile,"Receive thread: Start receive on socket: %d\n", s);
>                 if ((t=recv(s, str_l, 100, 0)) > 0)
>                 {
>                                 str_l[t] = '\0';
>                                 fprintf(logfile,"Receive thread: Server response: \"%s\"\n",
> str_l);
>                                 (*t_env)->CallVoidMethod(t_env,g_obj,g_mid,(*t_env)->NewStringUTF
> (t_env, str_l));
>                 }
>                 else
>                 {
>                         if (t < 0) perror("recv");
>                         else printf("Receive thread: Server closed connection\n");
>                         (*g_vm)->DetachCurrentThread(g_vm);
>                         return NULL;
>                 }
>         //}
>
>     fclose(logfile);
>
>         (*g_vm)->DetachCurrentThread(g_vm);
>
>         return NULL;
>
> }

Two thoughts here:

(1) If you do choose to loop forever, be aware that NewStringUTF
creates a local reference. If you don't return to Java and don't
delete it, they will build up and eventually overflow the table.

(2) Every Attach / Detach creates a Thread object and does some
internal housekeeping. If you're doing this frequently the overhead
may become problematic.


> JNIEXPORT jstring JNICALL
> Java_com_rtrk_jnicbus_NativeMessenger_sendMessage(JNIEnv *env, jclass
> cls, jstring question)
> {
[...]
>         //get string parameter passed from java function
>     str = (char*) (*env)->GetStringUTFChars(env, question, NULL);
[...]
>         //open unix socket
>     if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
>                 fprintf(logfile,"error: socket\n");
>         return NULL;
>     }

There are a couple of places where you "return NULL" without calling
ReleaseStringUTFChars. You will leak memory whenever there is an
error.


> //startCallback is called to store callback function ID
> //java class object is stored in global variable and used
> //in receiving thread to call callback function
[...]
>         g_obj = (*env)->NewGlobalRef(env, obj);

If this can be called more than once, you should DeleteGlobalRef
(g_obj) before reassigning it. If it can't, an "assert(g_obj ==
NULL)" would help ensure that. Otherwise, you will leak global
references.

extrapedestrian

unread,
Apr 21, 2009, 5:44:19 AM4/21/09
to android-ndk
Thanks for replies and comments!

rafe...@acsiatech.com

unread,
Jul 11, 2017, 3:17:40 PM7/11/17
to android-ndk
Actually, i want to communicate between android application and native c program. can i use this same code? How can we work with this code? can you  please help me?
Reply all
Reply to author
Forward
0 new messages