Home | Contact Us | FAQ | Search & Site Map | Link to Us
Sign In | Join | Other 45 Sites in Network
HomeAnnouncementsWhite Papers
Discussion GroupsFirst AidDatabasesJavaBeansGUIJava 3DVirtual MachineCORBASecurityToolsGeneral
Java DirectoryOpen Source ProjectsSample Book ChaptersUser GroupsWeb Resources
Related Topics
Databases.NETMore Topics ...

Java Forum / First Aid / October 2007

Tip: Looking for answers? Try searching our database.

Calling a C function from Java using the JNI

Thread view: 
csharpdotcom - 19 Oct 2007 14:42 GMT
Hi all, this is my first posting here, and would 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
executes 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 classpaths.
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 listings 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" are not 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>

JNIEXPORT 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 generates 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 doesn't 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 would 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
Gordon Beaton - 19 Oct 2007 15:15 GMT
> 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() doesn't run your command in a shell unless you specify
one, those things aren't 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't really tell since your setName() method looks a little
nonsensical: I can't 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 don't 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 doesn't
match what's expected by the JVM.

/gordon

--
csharpdotcom - 19 Oct 2007 15:46 GMT
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 will 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
[quoted text clipped - 6 lines]
> nonsensical: I can't 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 don't 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 is not 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 doesn't
> match what's expected by the JVM.
>
> /gordon

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

Christopher Sharp
csharpdotcom - 19 Oct 2007 16:34 GMT
OK, I've 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
Gordon Beaton - 19 Oct 2007 16:40 GMT
> 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

--
csharpdotcom - 19 Oct 2007 16:48 GMT
> After running javah with the correct classname, did you modify the C
> source so the native method matches the declaration in the new header
[quoted text clipped - 3 lines]
>
> --

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 hasn't appeared yet.

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

Christopher
Gordon Beaton - 19 Oct 2007 17:06 GMT
> 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 hasn't appeared yet.

Whatever - I don't think you're reading what I've written.

It doesn't 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 don't 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

--
Gordon Beaton - 19 Oct 2007 16:38 GMT
> In fact that is exactly what I did in Glassfish, but it will 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 don't
need to make incorrect assumptions.

You can't 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 doesn't
> 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 isn't
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't be more specific.

/gordon

--
Lothar Kimmeringer - 19 Oct 2007 17:29 GMT
>    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 is not executed if /tmp doesn't exist or is
restricted in access.

To make sure that the process doesn't 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 don't see the need for a JNI-Wrapper,
either.

Regards, Lothar
Signature

Lothar Kimmeringer                E-Mail: spamfang@kimmeringer.de
              PGP-encrypted mails preferred (Key-ID: 0x8BC3CD81)

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

Roedy Green - 26 Oct 2007 22:29 GMT
>In fact that is exactly what I did in Glassfish, but it will 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 is not 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.
Signature

Roedy Green Canadian Mind Products
The Java Glossary
http://mindprod.com

csharpdotcom - 29 Oct 2007 22:13 GMT
On 26 Oct, 22:29, Roedy Green <see_webs...@mindprod.com.invalid>
wrote:

> >In fact that is exactly what I did in Glassfish, but it will only work
> >with simple commands, and appears to fail when I include a "cd" in the
[quoted text clipped - 6 lines]
> Roedy Green Canadian Mind Products
> The Java Glossaryhttp://mindprod.com

Many thanks - in fact I had found a solution to this.  I used the
newer
ProcessBuilder class, and the directory and start methods to invoke a
bash script in a specified directory, then passed some arguments as a
test.  Once a shell script is invoked, it can do anything.

Obviously in a real world application precautions have to be taken to
make sure that the system stays secure.

Christopher Sharp
Roedy Green - 29 Oct 2007 22:23 GMT
> I used the
>newer
>ProcessBuilder class, and the directory and start methods to invoke a
>bash script in a specified directory, then passed some arguments as a
>test.  Once a shell script is invoked, it can do anything.

The problem is this becomes platform specific.  The link I gave you
also covers ProcessBuilder.

http://mindprod.com/jgloss/exec.html
Signature

Roedy Green Canadian Mind Products
The Java Glossary
http://mindprod.com

Roedy Green - 20 Oct 2007 06:29 GMT
>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. Don't give up on it too quickly.
Signature

Roedy Green Canadian Mind Products
The Java Glossary
http://mindprod.com

csharpdotcom - 20 Oct 2007 16:56 GMT
Hi Gordon and Roedy,

I've 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
Gordon Beaton - 20 Oct 2007 17:54 GMT
> 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

--
Lew - 20 Oct 2007 17:58 GMT
>> Nowhere had I seen any documentation on this detail, either in one of
>> my books or online.
[quoted text clipped - 10 lines]
>
> 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.

Signature

Lew

csharpdotcom - 20 Oct 2007 19:13 GMT
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't 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 will 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
Roedy Green - 22 Oct 2007 06:06 GMT
>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 did not make
sense until I got a textbook.  See http://mindprod.com/jgloss/jni.html
Signature

Roedy Green Canadian Mind Products
The Java Glossary
http://mindprod.com

csharpdotcom - 22 Oct 2007 20:58 GMT
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 don't 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");

does not compile and generates a syntax error, even though you have
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 isn't required here, as was mentioned
earlier, at least I now know
how to use it.

Christopher


Free Magazines

Get these publications absolutely FREE for up to 12 months. There are no hidden fees and no obligation. Simply choose a title, complete the application form and submit it. Read more ...

Oracle MagazineNetwork ComputingComputer WorldBio-IT WorldeWeekInformation WeekInfosecurity
 
Sign In
Join
My Latest Posts
My Monitored Threads
My Blog
My Photo Gallery
My Profile
My Homepage

Start New Thread
Enable EMail Alerts
Rate this Thread



©2008 Advenet LLC   Privacy Policy - Terms of Use
This website includes both content owned or controlled by Advenet as well as content owned or controlled by third parties.