Sagewire Logo

Calling a C function from JAVA using the JNI

17 Message(s) by 5 Author(s) originally posted in java help


From: csharpdotcom Date:   Friday, October 19, 2007
Hi all, this is my first posting here, and'd most appreciate some
kind help.

I need to pass a string to the Linux shell from JAVA, and because of
the restrictions of the "getRuntime()" method , I'm trying to call up a
C program from JAVA to which is I passed a string. The C program then
execute s the "system()" function containing the string that I want to
pass to the Linux shell. The idea eventually is to execute the code
as a bean in Glassfish, but I'm having trouble testing it out from the
CLI. There seems to be some problem with packages and class path s.
The code is in a file in a folder tree , the last two levels being "/
com/corejsf", and in the programs I have the declaration
"package.com.corejsf;"

There are four list ings as follows:

// Listing 1
package com.corejsf;

import JAVA.io.*;
import JAVA.util.*;

class CallUserBean {
public static void main(String[] args) {
UserBean bean = new UserBean();
String s = new String();
s = "Passing this string to UserBean";
System.out.println(s);
bean.setName(s);
}
}

Which calls the class in "UserBean.JAVA"

// Listing 2
package com.corejsf;

import JAVA.io.*;
import JAVA.util.*;

public class UserBean {
private String name;
private String password ;

// PROPERTY: name
public String getName() { return name; }
public void setName(String newValue) {
System.out.println("Now in UserBean.setName()");
System.out.println(newValue);
String command = new String();
command = "ls -lt > jnioutput.txt";
System.out.println("Sending the string - " + command + " - to
native code");
CallSystem.system(command);

name = newValue;
}

// PROPERTY: password
public String getPassword() { return password; }
public void setPassword(String newValue) { password = newValue; }
}

At the moment for the test only the methods "getName", "setPassword"
and "getPassword" aren't used. This in turns calls the class in
"CallSystem.JAVA":

Listing 3
package com.corejsf;

class CallSystem {
public static native void system(String s);
static {
System.loadLibrary("CallSystem");
}
}

Which calls the native C code in "CallSystem.c" ("UserBean.h" was
created with the "JAVAh" command as required):

Listing 4
#include "UserBean.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

JNI EXPORT void JNICALL JAVA_CallSystem_system(JNIEnv* env, jclass cl,
jstring jcommand) {
const char *ccommand;

ccommand = (*env)->GetStringUTFChars(env, jcommand, NULL);
system(ccommand);

(*env)->ReleaseStringUTFChars(env, jcommand, ccommand);
}

which passes a string to the shell for execution. In this case a
simple test "ls" is executed, as you can see in listing 1.

After I compile the C code and link in the shared library ,
"libCallSystem.so", then compile the JAVA files with the "package
com.corejsf;" commented out in listings 1, 2 and 3, the code works
with the command:

"JAVA -DJAVA.library.path=. CallUserBean"

from inside /com/corejsf and generate s the test file "jnioutput.txt"
and write to it. This means that the native code is being called up
correctly. However, if I uncomment the package statements and
recompile the JAVA files using the statements "JAVAc -classpath
"../../" CallSystem.JAVA", and likewise for the other two files then
issue the command:

"JAVA -DJAVA.library.path=. -classpath ../../ CallUserBean"

the code in JAVA is executed correctly and generates the correct
output , but fails when it tries to call up the native code. I get the
message:

Exception in thread "main" JAVA.lang.UnsatisfiedLinkError:
com.corejsf.CallSystem.system(LJAVA/lang/String;)V
at com.corejsf.CallSystem.system(Native Method)
at com.corejsf.UserBean.setName(UserBean.JAVA:19)
at CallUserBean.main(CallUserBean.JAVA:11)

At best similar messages are obtained when I try other combinations of
"-DJAVA.library.path" and "-classpath", or the program does not even
get that far. The fact that I can get it to work with the "package"
statements commented out means that it must be something to do with
the way the code is linked, and'd be most grateful to have some
advice on this.

If this can be sorted out, the idea is to remove the code from listing
one, recompile listings 2 and 3, put them in a ".war" file, and put
the shared library in the appropriate folder for Glasssfish. I have
in fact done this, and get the same error as above.

Christopher Sharp


From: Gordon Beaton Date:   Friday, October 19, 2007
wrote in message:
I need to pass a string to the Linux shell from JAVA, and because of
the restrictions of the "getRuntime()" method, I'm trying to call up
a C program from JAVA to which is I passed a string. The C program
then executes the "system()" function containing the string that I
want to pass to the Linux shell.



There is absolutely no need to use JNI for this. This should work:

String[] cmd = { "/bin/sh", "-c", "ls -lt > jnioutput.txt" };
Runtime.getRuntime().exec(cmd);

I'm guessing that you tried to use redirection or quoting or other
shell features without specifying a shell explicitly. But since
Runtime.exec() does not run your command in a shell unless you specify
one, those things are not available by default.

If that's "just an example" then post the real original string you're
having trouble with, and the Runtime.exec() code you tried to use. I
can not really tell since your setName() method looks a little
nonsensical: I can not see how "ls -l" relates to the command in
question.

If all you need to do is get a file list into "jnioutput.txt" then you
really do not even need Runtime.exec, since you can get the same
information with File.listFiles() (etc) and then write to the file
with a PrintWriter.

Finally, your JNI exception is due to the fact that you failed to
specify the fully qualified classname (i.e. *with* package name) when
you ran JAVAh to generate UserBean.h, and so your native code does not
match what's expected by the JVM.

/gordon--


From: csharpdotcom Date:   Friday, October 19, 2007
Hi Gordon,

Many thanks for your reply.
There is absolutely no need to use JNI for this. This should work:
String[] cmd = { "/bin/sh", "-c", "ls -lt > jnioutput.txt" };
Runtime.getRuntime().exec(cmd);



In fact that is exactly what I did in Glassfish, but it'll only work
with simple commands, and appears to fail when I include a "cd" in the
string. I'm just using this particular string to test things out.

I'm guessing that you tried to use redirection or quoting or other
shell features without specifying a shell explicitly. But since
Runtime.exec() does not run your command in a shell unless you specify
one, those things are not available by default.
If that's "just an example" then post the real original string you're
having trouble with, and the Runtime.exec() code you tried to use. I
can not really tell since your setName() method looks a little
nonsensical: I can not see how "ls -l" relates to the command in
question.



The original string gave the full path to a particular folder and
included the "cd" command, which caused the server to fail.> If all you need to do is get a file list into "jnioutput.txt" then you
really do not even need Runtime.exec, since you can get the same
information with File.listFiles() (etc) and then write to the file
with a PrintWriter.



That isn't all I want to do, I'm just trying a simple "Hello World"
type example first to get the wringles out of the linking problems.
Finally, your JNI exception is due to the fact that you failed to
specify the fully qualified classname (i.e. *with* package name) when
you ran JAVAh to generate UserBean.h, and so your native code does not
match what's expected by the JVM.
/gordon



I've just tried "JAVAh -classpath ../../ UserBean" , but it does not
find the class. Obviously there is something simple that I'm missing.

Christopher Sharp


From: csharpdotcom Date:   Friday, October 19, 2007
OK, I have just got that part to work, many thanks, I typed:

JAVAh -classpath ../../ -o UserBean.h com.corejsf.UserBean

The "-classpath ../../" part because I have to go up in the tree as I
am in the folder with the code, and the output filename, otherwise I
get "com_corejsf_UserBean.h". I then compiled the C code with:

gcc -D_REENTRANT -fPIC -I/<path-to-jdk>/include -I/<path-to-jdk>/
include/linux -c CallSystem.c

which generated the object file, then typed:

gcc -shared CallSystem.o -o libCallSystem.so

to create the shared library. However, on attempting to execute:

JAVA -DJAVA.library.path=. -classpath "../.." CallUserBean

I get the same link errors as before, so something is still wrong.

Christopher


From: Gordon Beaton Date:   Friday, October 19, 2007
wrote in message:
In fact that is exactly what I did in Glassfish, but it'll only
work with simple commands, and appears to fail when I include a "cd"
in the string. I'm just using this particular string to test things
out.



Again, please post a real example of a real command string, so I do not
need to make incorrect assumptions.

You can not use cd by itself (well you can, but its effect is lost when
the process ends, and it certainly won't affect the JAVA application).
The same is true of system().

You can do this though:

String[] cmd = { "/bin/sh", "-c", "cd /tmp; ls -lt > jnioutput.txt" };
Runtime.getRuntime().exec(cmd);

In this example, "ls -lt > jnioutput" will run in the /tmp directory .
Note that semicolon is necessary to separate the different commands.

I've just tried "JAVAh -classpath ../../ UserBean" , but it does not
find the class. Obviously there is something simple that I'm
missing.



The name of the class is something like com.foo.UserBean, where the
first part comes from the package declaration in the JAVA source , so
your command should be: "JAVAh com.foo.UserBean". If you need to
specify a classpath, you can do that too, but "UserBean" simply is not
the full name of the class.

I'm still convinced that Runtume.exec() is sufficient, but you need to
post a real example or I can not be more specific.

/gordon

--


From: Gordon Beaton Date:   Friday, October 19, 2007
wrote in message:
I get the same link errors as before, so something is still wrong.



After running JAVAh with the correct classname, did you modify the C
source so the native method matches the declaration in the new header
file?

/gordon

--


From: csharpdotcom Date:   Friday, October 19, 2007
After running JAVAh with the correct classname, did you modify the C
source so the native method matches the declaration in the new header
file?
/gordon
--



In fact I sent a post where I had created the new header file with the
same name as before, and the C code compiled and linked OK, and I
created the shared library, but I'm still having trouble running the
JAVA. For some reason the post has not appeared yet.

If I do not get it solved in the next few minutes, it'll have to wait
until tomorrow morning, as I'm going out for the evening.

Christopher


From: Gordon Beaton Date:   Friday, October 19, 2007
wrote in message:
In fact I sent a post where I had created the new header file with
the same name as before, and the C code compiled and linked OK, and
I created the shared library, but I'm still having trouble running
the JAVA. For some reason the post has not appeared yet.



Whatever - I do not think you're reading what I have written.

It does not matter how many times you re-run JAVAh and recompile your
code. The important thing is that the name of the function itself in
the C source is correct with respect to the fully qualified name of
the class, i.e. with the package declaration.

The generated header file shows you what the correct name is, but only
if you run JAVAh correctly. Make sure you do not have old copies of the
header file or the shared library that might disturb your
observations.

For about the fifth time though, you can probably solve this with
Runtime.exec().

/gordon

--


From: Lothar Kimmeringer Date:   Friday, October 19, 2007
wrote in message:

String[] cmd = { "/bin/sh", "-c", "cd /tmp; ls -lt > jnioutput.txt" };



Better "ls /tmp && ls -lt > jnioutput.txt" to make sure that
the second part isn't executed if /tmp does not exist or is
restricted in access.

To make sure that the process does not block STDERR must be
read or a 2> /dev/null must be added to the ls.

If there are many commands to be executed I think a shell-script
should be considered, that is executed by JAVA the same way as
already shown. I really do not see the need for a JNI-Wrapper,
either.Regards, Lothar
--
Lothar Kimmeringer E-Mail: spamfang@xxxxxxxxxxx
PGP-encrypted mails preferred (Key-ID: 0x8BC3CD81)

Always remember: The answer is forty-two, there can only be wrong
questions!


From: Roedy Green Date:   Saturday, October 20, 2007
On Fri, 19 Oct 2007 06:42:36 -0700, csharpdotcom <cmsharp01@xxxxxxxxxxx>
wrote in message, quoted or indirectly quoted someone who said :

getRuntime()"



What you want to do sounds quite doable with an ordinary exec, which
is much simpler than using JNI. See
http://mindprod.com/jgloss/exec.html

It has many variations. Do not give up on it too quickly.
--
Roedy Green Canadian Mind Products
The JAVA Glossary
http://mindprod.com


From: csharpdotcom Date:   Saturday, October 20, 2007
Hi Gordon and Roedy,

I have just logged on since yesterday and see some further very valuable
comments - many thanks.

Well I just got the JNI to work at last! On my way back from party
last night I had the idea of changing the name of the function in the
C program, and only just had the chance to try it out, and it worked!
What I did was first to create the ".h" file with the default name
generated by JAVAh, rather than giving it the name I want it to have
using the command:

JAVAh -classpath ../../ com.corejsf.UserBean

which creates the filename "com_corejsf_UserBean.h", then in
"CallSystem.c" I put in that filename in the "include" statement, but
then I changed the name of the function in the C program called from
JAVA from "JAVA_CallSystem_system" to
"JAVA_com_corejsf_CallSystem_system". That is the critical change
which caused the program to work.

Nowhere had I seen any documentation on this detail, either in one of
my books or online. I'm now going to try this out with Glassfish, as
well as looking again at the "getRuntime()" method, links to which you
kindly sent me.

Christopher


From: Gordon Beaton Date:   Saturday, October 20, 2007
wrote in message:
Nowhere had I seen any documentation on this detail, either in one of
my books or online.



The JAVAh documentation is pretty clear on this point:

SYNOPSIS
JAVAh [options] fully-qualified-classname

DESCRIPTION
[...]
If the class passed to JAVAh is inside a package, the package name is
prepended to both the header file name and the structure name.

It's also in my *first* response to you in this thread.

/gordon

--


From: Lew Date:   Saturday, October 20, 2007
wrote in message:
wrote in message:
Nowhere had I seen any documentation on this detail, either in one of
my books or online.
The JAVAh documentation is pretty clear on this point:
SYNOPSIS
JAVAh [options] fully-qualified-classname
DESCRIPTION
[...]
If the class passed to JAVAh is inside a package, the package name is
prepended to both the header file name and the structure name.
It's also in my *first* response to you in this thread.



Yeah, when I read the OP's
On my way back from party
last night I had the idea of changing the name of the function in the
C program,



I thought to myself, "Self, surely we remember that advice from Gordon's
*first* response in this thread?"

to the OP: it seems a bit strange to claim credit for having just, on 20
October, getting the idea that was given to you on 19 October.

--
Lew


From: csharpdotcom Date:   Saturday, October 20, 2007
Yes, OK, many thanks, somehow I missed the earlier comments - sorry
about that, but I was getting tired.

OK, I got the JNI to work with Glassfish, which is great. Given
http://mindprod.com/jgloss/exec.htm, in particular the "gotcha" that
commands like "dir" can not be executed directly I'm going through the
advice here on how to execute commands like "cd /home/csharp/tempdir;
ls -l > tempout.txt" etc. It appears to work from JAVA and C when run
in the CLI, which is reasonable, but not from Glassfish, so as advised
a shell should be invoked given the comments above.

And yes, it's probably better to use runtime().exec(), but the server
I'll eventually be working on has a lot of code in C and FORTRAN, so
I may still need to use JNI at some stage, and in any case it's useful
to know how to do this. For the time being I'm just testing out code
on my laptop.

Christopher


From: Roedy Green Date:   Monday, October 22, 2007
On Sat, 20 Oct 2007 08:56:45 -0700, csharpdotcom <cmsharp01@xxxxxxxxxxx>
wrote in message, quoted or indirectly quoted someone who said :

Nowhere had I seen any documentation on this detail, either in one of
my books or online.



I had the same problem years ago -- detailed docs, but nothing on the
overall process and in general what was happening. It didn't make
sense until I got a textbook. See http://mindprod.com/jgloss/jni.html
--
Roedy Green Canadian Mind Products
The JAVA Glossary
http://mindprod.com


From: csharpdotcom Date:   Monday, October 22, 2007
Hi Roedy,

OK, I also got the code to work using the ProcessBuilder class, which
is new in JAVA 5,
using your very helpful and extensive website (plus lots of other
interesting things
outside of programming).

I modified my bean so that it is now:

package com.corejsf;

import JAVA.io.*;
import JAVA.util.*;

public class UserBean {
private String name;
private String password;

// PROPERTY: name
public String getName() { return name; }
public void setName(String newValue) {
System.out.println("Now in UserBean.setName()");
System.out.println(newValue);
try {
ProcessBuilder pb = new ProcessBuilder("/home/csharp/
shelltests/called-from-JAVA",
"first_argument", "second_argument",
"third_argument");
pb.directory(new File("/home/csharp"));
pb.start();
} catch (Exception e) {
System.out.println("Error executing script");
}
name = newValue;
}

// PROPERTY: password
public String getPassword() { return password; }
public void setPassword(String newValue) { password = newValue; }
}

with the JNI removed. It calls up the script in the path given above,
and the contents of
"called-from-JAVA" are:

#!/bin/sh
ls -lt > scriptout
echo $0 scriptout
echo $1 scriptout
echo $2 scriptout
echo $3 scriptout

which lists the files in the directory specified in the "directory()"
method. If I do not do
that, Glassfish writes to a file with root privileges in its own
directory, which is highly
undesirable. One thing is that the statement

pb.directory("/home/csharp");

doesn't compile and generates a syntax error, even though you've
something like that on
your website. The "new FILE(....)" has to be nested in the method
call for the code to
compile, at least on my computer, which has JAVA 6.

Anyway, although the JNI is not required here, as was mentioned
earlier, at least I now know
how to use it.

Christopher


From: Roedy Green Date:   Friday, October 26, 2007
On Fri, 19 Oct 2007 07:46:36 -0700, csharpdotcom <cmsharp01@xxxxxxxxxxx>
wrote in message, quoted or indirectly quoted someone who said :

In fact that is exactly what I did in Glassfish, but it'll only work
with simple commands, and appears to fail when I include a "cd" in the
string. I'm just using this particular string to test things out.



that's because CD isn't an executable but a command to the command
interpreter . See http://mindprod.com/jgloss/exec.html for how to
spawn a command interpreter to handle these.
--
Roedy Green Canadian Mind Products
The JAVA Glossary
http://mindprod.com



Next Message: 2d graphics and 2 other questions


Blogs related to Calling a C function from JAVA using the JNI

Java Database Connectivity Tutorial part 1
And the second option given was using ODBC API which can solve the 1.a problem but again this ODBC API is also a Native API, so we have to use JNI in our Java applications which lead to the 1.b described problem. ...

JAVA JDBC QUESTIONS
The JDBC-ODBC bridge doesn't allow untrusted code to call it for security reasons. This is good because it means that an untrusted applet that is downloaded by the browser can't circumvent Java security by calling ODBC. ...

Java Interview Questions, FAQs
JNI is an acronym of Java Native Interface. Using JNI we can call functions which are written in other languages from Java. Following are its advantages and disadvantages. Advantages: * You want to use your existing library which was ...

MSV8 JNI Functions?
I am trying to use JNI to call Java code from C++. I am getting kink errors because of missing library functions, and I can't find the right libraries. Can anyone help? I am using MS Visual Studio 8. ...

Re: Calling Java class files from PLSQL
As an update, Changing the Parameters of the Java Function was not a good Idea. Back to calling the Webservice from Oracle Forms, I managed to IMPORT the Element class from: org.w3c.dom.Element Now the issue is here, when I call the ...

Excellent Senior JAVA Profile.... US Citizen
Web development using Java, Autonomy, WebLogic, EJB, JNI, RMI, XML, and IBM Websphere with servlets, JSP's, C and C++ for small E-Commerce Company. Also setup Microsoft IIS web server, SQL Server database tables, and performed various ...


Programming | Sports | Autos

copyright 2006
Valid XHTML 1.0 Transitional