* * *

Author Topic: Calling JavaVM from Pascal  (Read 6333 times)

BertVerhees

  • New member
  • *
  • Posts: 17
Calling JavaVM from Pascal
« on: November 01, 2016, 06:15:32 pm »
Hi, please help me. I already found a lot of information. But I need one more step. I think I am not very smart to understand how this can be done.
Maybe it is a piece of cake for an experienced FPC programmer (I hope so)

When you look at this URL:
http://www.pacifier.com/~mmead/jni/delphi/JEDI/DOCS/delphi-jni-3.html#Example

The problem is how to create the JavaVM object. It does not exist in FPC library (JNI.pas), this URL is Delphi code.
In Delphi it is done like this:

      // Create the JVM (using a wrapper class)
    JavaVM := TJavaVM.Create;

The rest of the code is very understandable, and very similar between Delphi, FPC, and also C-library which comes with Java (from which it is extracted)
So, please help me how to start the JVM, and I think I am perfectly happy.

Important to notice is that I am working in Linux.

As thank, I can write a wiki how to use Java-code from a Delphi application. because that is what I intent to do.

Thanks very much.
« Last Edit: November 01, 2016, 06:22:35 pm by BertVerhees »

Leledumbo

  • Hero Member
  • *****
  • Posts: 7649
  • Programming + Glam Metal + Tae Kwon Do = Me
Re: Calling JavaVM from Pascal
« Reply #1 on: November 01, 2016, 07:49:09 pm »
FPC's jni unit is a translation of Java's original jni header. The one in that article has an additional wrapper, whose link is provided in the quick start part. No idea if that wrapper is FPC compatible or not, you should figure it out yourself or ask the author. Using FPC jni unit, the manipulation is highly similar to how it's done in C.

Thaddy

  • Hero Member
  • *****
  • Posts: 4439
Re: Calling JavaVM from Pascal
« Reply #2 on: November 02, 2016, 09:32:58 am »
First: the features in FPC are rather better than that particular component. Like the pas2jni utility or even the PPCJVM to handle java code.
Second: I used that particular component in a professional settings a couple of years ago with D2006. I had to fix a lot of bugs in it, though.
The main one was jvm versioning. That still hasn't been fixed on that website so that code can never work with a modern jvm.

Since FPC in Delphi mode is (very close to) fully compatible with D2006 I suggest you use it in Delphi mode.
I don't expect you to have any problems because of the above to compile. If it still works is another matter.
Expect problems with current Java/JVM  versions with that particular code. Also under modern Delphi versions for the same reason.

I know that code you linked to is clear, but it is not complete and very old.
It simply compiles with d2006 and fpc in delphi mode, but only works with very old jvm's because of said bug. You don't want that!

If you explain what you want to do in a more specific manner we might be able to help.

(I also don't see what Leledumbo did wrong.)

I can't help you with my corrected code because it is only at binckbank, my former employer.
« Last Edit: November 02, 2016, 09:50:05 am by Thaddy »
"Logically, no number of positive outcomes at the level of experimental testing can confirm a scientific theory, but a single counterexample is logically decisive."

Leledumbo

  • Hero Member
  • *****
  • Posts: 7649
  • Programming + Glam Metal + Tae Kwon Do = Me
Re: Calling JavaVM from Pascal
« Reply #3 on: November 02, 2016, 10:42:15 am »
Dear Leledumbo,

I don't know what the purpose is of your answer. If you want to help me, please do, if not, don't, but then you don't need to tell me that.

So, if others can help me with this question, I am grateful, and maybe I can help you too, in another case.

Thanks
Bert
I guess old habit does die hard. Feel free to wait for our customer service :)

BertVerhees

  • New member
  • *
  • Posts: 17
Re: Calling JavaVM from Pascal
« Reply #4 on: November 02, 2016, 03:58:28 pm »
Leledumbo, Keep on trying  :P

Thaddy,

I found a better implementation of JNI:
https://sites.google.com/site/aminer68/jni-wrapper-for-delphi-and-freepascal

It has version 2.85, and the according text is written in March 2016, but it is written for Windows.

But I had no problems skipping/removing all the Windows related stuff (what Lazarus did not remove), but my work still fails

I did it like this:

procedure TJavaRuntime.Initialize;
  begin
   if DLLHandle <> 0 then
      exit; // already initialized.
    {$IFDEF FPC}
    FRuntimeLib := '/usr/lib/jvm/java-8-oracle/jre/lib/amd64/server/libjvm.so';
    DLLHandle := LoadLibrary(PChar(FRuntimeLib));
   {$ELSE}
    DLLHandle := SafeLoadLibrary(FRuntimeLib);
   {$endif}

    if DLLHandle = 0 then
      raise EJavaRuntimeCreation.Create('Could not load DLL ' + FRuntimeLib);
    @CreateVM := getProcAddress(DLLHandle, 'JNI_CreateJavaVM');
    @GetDefaultArgs := getProcAddress(DLLHandle, 'JNI_GetDefaultJavaVMInitArgs');
    @GetCreatedVMs := getProcAddress(DLLHandle, 'JNI_GetCreatedJavaVMs');
    if (@CreateVM = Nil) or (@GetDefaultArgs = Nil) or (@GetCreatedVMs = Nil) then
      raise EJavaRuntimeCreation.Create('Dynamic Link Library ' + FRuntimeLib + ' is not valid.');
    vmargs2.version := $00010008;
GetDefaultArgs(@vmargs2);

  end;


You see, I load the jvm-library as needed in Linux. DLLHandle become non-zero, so I think it loads it (if I create a spelling-error in the library-name, DLLHandle remains zero.)

The finding of the procadresses seems to fail.
@CreateVM: <Error: <Error>>

Some background on CreateVM:
CreateVM : TCreateVM; 
TCreateVM = function (vm : PPJavaVM ; penv : PPJNIEnv ; p : Pointer) : jint; {$IFDEF WIN32} stdcall; {$ENDIF} {$IFDEF LINUX} cdecl; {$ENDIF}

(by the way, cdecl is active, stdcall is grayed out in the Lazarus editor)

I try to call one, like this in function TJavaRuntime.GetVM

Like here

if CreateVM(@pvm, @penv, args) <>0 then
      raise EJavaRuntimeCreation.Create('Could not create JVM');


It does not go to the exception, but raises another Exception:
Project jtest1 raised exception class 'External: SIGSEGV'.  At address 7FFFE62582B4

I read somewhere that SIGSEGV is entering an memory location which is  a fault of illegal memory access.
That brings me back to the code where the address of CreateVM is retrieved in this way
@CreateVM := getProcAddress(DLLHandle, 'JNI_CreateJavaVM');


I am sure that a function with that name exist in that library:
~ > nm -D /usr/lib/jvm/java-8-oracle/jre/lib/amd64/server/libjvm.so

fragment
000000000070e0f0 T jio_snprintf
000000000070de80 T jio_vfprintf
000000000070e0d0 T jio_vsnprintf
00000000006d0880 T JNI_CreateJavaVM
00000000006cd1d0 T JNI_GetCreatedJavaVMs
00000000006cd210 T JNI_GetDefaultJavaVMInitArgs
000000000070ee00 T JVM_Accept
0000000000713990 T JVM_ActiveProcessorCount
0000000000715a20 T JVM_AllocateNewArray
0000000000727340 T JVM_AllocateNewObj


So the call with getProcAddress(.....) does not lead to an valid address.
It can be the case that the LoadLibrary(PChar(FRuntimeLib)) function does not load the library well, allthough it has a valid handle.
(I also tried SafeLoadLibrary(FRuntimeLib);
Or the getProcAddress(.....) has some problems. (By the way, I also tried GetProcedureAddress).

I also tried other jvm-libraries, so that cannot be the problem.

Who knows what is going wrong here?

Thanks in advance

Best regards
Bert Verhees
« Last Edit: November 03, 2016, 08:45:00 am by BertVerhees »

Thaddy

  • Hero Member
  • *****
  • Posts: 4439
Re: Calling JavaVM from Pascal
« Reply #5 on: November 03, 2016, 08:59:20 am »
There is a huge bug where it says:
Code: Pascal  [Select]
  1. {$IFDEF FPC}
  2.     FRuntimeLib := '/usr/lib/jvm/java-8-oracle/jre/lib/amd64/server/libjvm.so';
  3.     DLLHandle := LoadLibrary(PChar(FRuntimeLib));
  4.    {$ELSE}
  5.  

Why? Because you are using the code for windows with fpc, but here you define a linux path.
fpc is always defined for all versions of fpc, because it is fpc ;)
That code should at the minimum be:
Code: Pascal  [Select]
  1. {$IFDEF UNIX}  // not $ifdef fpc
  2.     FRuntimeLib := '/usr/lib/jvm/java-8-oracle/jre/lib/amd64/server/libjvm.so';
  3.     DLLHandle := LoadLibrary(PChar(FRuntimeLib));
  4.    {$ELSE}
  5.  
That works probably better for you but is still not valid for all platforms. Just nix and windows.

That's a first glance. Also get rid of the PChar cast. That means that class field is defined wrong.
There's probably more to fix, but this one is a huge bug and may help you further.

Also: don't use a full path. Both under nixes and windows and  properly installed java, you can simply use just the library name without .so or .dll and the path.
The full path is useful for debugging, though.

There may be is also an issue with the procedure names for getprocaddress. These are case sensitive. even under windows. So don't Pascallify/ProperCase them.Check those names. Although I see you tried to do that.
« Last Edit: November 03, 2016, 09:37:43 am by Thaddy »
"Logically, no number of positive outcomes at the level of experimental testing can confirm a scientific theory, but a single counterexample is logically decisive."

BertVerhees

  • New member
  • *
  • Posts: 17
Re: Calling JavaVM from Pascal
« Reply #6 on: November 03, 2016, 09:50:23 am »
Thank you Thaddy, for your improvements. You are right.

But thanks to the excellent quality of the Lazarus code-editor, the non-active code-path is grayed out. And it can also be checked by step by step debugging.

Regarding to the librarypath, you are right too, but the library is loaded well, the library-handle has a value, and I tested it, when having a wrong spelling in the path, the handle becomes zero.

The PChar casting, is needed by dynlibs. That maybe wrong or not. I do not have strong opinion on that. I also tried removing that by using the SafeLoadLibrary, same effect.

I will use your tips when I have it working.

The problem must be, I think, in the requesting for the function-address. But I don't know what the problem is. I am spending more than a day on this.
« Last Edit: November 03, 2016, 09:57:27 am by BertVerhees »

marcov

  • Global Moderator
  • Hero Member
  • *****
  • Posts: 5644
Re: Calling JavaVM from Pascal
« Reply #7 on: November 03, 2016, 10:25:48 am »
Maybe stracing the resulting binary gives some information about what goes wrong?

BertVerhees

  • New member
  • *
  • Posts: 17
Re: Calling JavaVM from Pascal
« Reply #8 on: November 03, 2016, 10:51:54 am »
That can be a good idea, but I am not able to get any wiser from a strace-output. I have attached it. Hopefully someone can give a clue?
If you need more info, I will happily provide it.

Bert

marcov

  • Global Moderator
  • Hero Member
  • *****
  • Posts: 5644
Re: Calling JavaVM from Pascal
« Reply #9 on: November 03, 2016, 03:49:52 pm »
There are two sigsegv's. The first internally in Java (?!?!)

Code: [Select]
open("/usr/lib/jvm/java-8-oracle/jre/lib/meta-index", O_RDONLY) = 3
fstat(3, {st_mode=S_IFREG|0755, st_size=2034, ...}) = 0
read(3, "% VERSION 2\n% WARNING: this file"..., 4096) = 2034
read(3, "", 4096)                       = 0
close(3)                                = 0
mmap(NULL, 251658240, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_NORESERVE, -1, 0) = 0x7f73f7c8c000
mmap(0x7f73f7c8c000, 2555904, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f73f7c8c000
mmap(NULL, 3932160, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_NORESERVE, -1, 0) = 0x7f73f78cc000
mmap(0x7f73f78cc000, 40960, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f73f78cc000
--- SIGSEGV {si_signo=SIGSEGV, si_code=SEGV_MAPERR, si_addr=0} ---
rt_sigreturn({mask=[QUIT]})             = 6
futex(0x8efc54, FUTEX_WAIT_BITSET_PRIVATE, 1, {10757, 945339826}, ffffffff) = -1 ETIMEDOUT (Connection timed out)
futex(0x8efc28, FUTEX_WAKE_PRIVATE, 1)  = 0
futex(0x8efc54, FUTEX_WAIT_BITSET_PRIVATE, 1, {10757, 946451175}, ffffffff) = -1 ETIMEDOUT (Connection timed out)
futex(0x8efc28, FUTEX_WAKE_PRIVATE, 1)  = 0
futex(0x8efc54, FUTEX_WAIT_BITSET_PRIVATE, 1, {10757, 947539238}, ffffffff) = -1 ETIMEDOUT (Connection timed out)
futex(0x8efc28, FUTEX_WAKE_PRIVATE, 1)  = 0
mmap(0x6c9c00000, 4131389440, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_NORESERVE, -1, 0) = 0x6c9c00000
mmap(NULL, 8073216, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_NORESERVE, -1, 0) = 0x7f73f7119000
mmap(0x7f73f78cb000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f73f78cb000

The later one is possibly during a very prolongued loading of

Code: [Select]
open("/usr/lib/jvm/java-8-oracle/jre/lib/rt.jar", O_RDONLY) = 3

But it could be that that is after RT has been mostly initialized.

Note that crashing doesn't necessarily mean that the geprocedureaddress goes wrong, it can be the function signature or the parameters too. But I don't work with Java, so that is beyond me.

Thaddy

  • Hero Member
  • *****
  • Posts: 4439
Re: Calling JavaVM from Pascal
« Reply #10 on: November 03, 2016, 04:04:54 pm »
The mmap suggests also maybe an alignment issue. Because that comes maybe from fpc's memorymanager?
Try to add {$PACKRECORDS C} if it is not there.
I have time to test but not to rewrite ;)
So code is more welcome than straces.
Where are you now? Codewise?

I do quite a lot with Java.
« Last Edit: November 03, 2016, 04:06:50 pm by Thaddy »
"Logically, no number of positive outcomes at the level of experimental testing can confirm a scientific theory, but a single counterexample is logically decisive."

BertVerhees

  • New member
  • *
  • Posts: 17
Re: Calling JavaVM from Pascal
« Reply #11 on: November 03, 2016, 04:57:58 pm »
Hi Marco, thanks for your research.

(Let me introduce a little-bit about myself: I am quite good at java, and also, I am quite good at Delphi (I even worked for the Borland helpdesk a long time ago).
But I do not have much knowledge of FPC-libraries. And I was reasonable good in C. I used to work with it, a long time ago. I can still read it, but I make many errors when I program in it, so I don't do that anymore.)


But I found yesterday a Youtube instruction about how to connect to libjvm, it had code in C. And it worked. So there cannot be a problem with the library itself.

You see in bold (below) how the call needs to be done in C. I must regret to say, that I am not able to translate that with certainty to Pascal.
We must say that the Pascal JNI code works fine in Windows, but maybe the Java-dll is more tolerant against errors? (The windows counterpart is called javai.dll)

I can create tomorrow a very simple small testfile which should do the job. Say, a translation of the C-program as I think it should be translated (which is, like I said, not necessary right)
Here is the code, that works, maybe you can indicate something from it:

#include <stdio.h>
#include <stdlib.h>
#include <jni.h>

JNIEnv* create_jvm(JavaVM **java)
{
   JNIEnv *env;
   JavaVMInitArgs args;
   JavaVMOption options;
   int ret;

   args.version = JNI_VERSION_1_6;
   args.nOptions = 1;
   args.options = &options;
   args.ignoreUnrecognized = 0;

   options.optionString = "-Djava.class.path=./";

   ret = JNI_CreateJavaVM(java, (void**)&env, &args); 

   if(ret < 0 || !env)
      printf("unable to launch JVM %d\n", ret);
   else
      printf("Launched JVM\n");

   return env;
}

int main(int argc, char *argv[])
{
   JavaVM *jvm;
   JNIEnv *env;

   jclass myclass;
   jmethodID main_method;
   jmethodID addnumber_method;

   jint a_num = 5;
   jint b_num = 3;

   env = create_jvm(&jvm);

   if(env == NULL)
      exit(1);

   myclass = (*env)->FindClass(env, "myjava");
   main_method = (*env)->GetStaticMethodID(env, myclass, "main", "([Ljava/lang/String;)V");
   addnumber_method = (*env)->GetStaticMethodID(env, myclass, "add_numbers", "(II)I");
   (*env)->CallStaticVoidMethod(env, myclass, main_method, NULL);
   printf("sum = %d\n", (*env)->CallStaticIntMethod(env, myclass, addnumber_method, a_num, b_num));
   return 0;
}

This is the small Java Class which is used for test in the C-code

public class myjava{
   public static void main(String[] args){
      System.out.println("C calling java");
   }
   public static int add_numbers(int a, int b){
      int sum = a + b;
      return sum;
   }
}   
« Last Edit: November 03, 2016, 05:22:45 pm by BertVerhees »

Cyrax

  • Hero Member
  • *****
  • Posts: 537
Re: Calling JavaVM from Pascal
« Reply #12 on: November 03, 2016, 05:32:44 pm »
Perhaps your problems stir from calling convention. Windows generally uses stdcall, other OS environments cdecl calling convention.

BertVerhees

  • New member
  • *
  • Posts: 17
Re: Calling JavaVM from Pascal
« Reply #13 on: November 03, 2016, 05:36:36 pm »
Thanks for your suggestion. It must be hard to read everything.

So, let me tell you, I do cdecl.
See my message from: November 02, 2016, 03:58:28 pm

BertVerhees

  • New member
  • *
  • Posts: 17
Re: Calling JavaVM from Pascal
« Reply #14 on: November 03, 2016, 06:02:21 pm »
Here is my very short Pascal program which does nothing then initializing the JVM, and crashes while doing that
And you need my JNI.pas, which I attach. Maybe it is the same as the one with FPC, but I am not sure about that.

program project1;

{$MODE Delphi}

uses
  {$IFDEF UNIX}{$IFDEF UseCThreads}
  cthreads,
  {$ENDIF}{$ENDIF}
  Classes, JNI, dynLibs
  { you can add units after this };

type
  TCreateVM = function (vm : PPJavaVM ; penv : PPJNIEnv ; p : Pointer) : jint; cdecl;

var
  runtimeLib: AnsiString;
  libHandle : THandle;
  CreateVM : TCreateVM;

  PVM : PJavaVM;
  penv : PJNIEnv;
  args : Pointer;
begin
  runtimeLib := '/usr/lib/jvm/java-8-oracle/jre/lib/amd64/server/libjvm.so';
  libHandle :=  LoadLibrary(PChar(runtimeLib));
  @CreateVM := getProcAddress(libHandle, 'JNI_CreateJavaVM');
  CreateVM(@pvm, @penv, args);
end.           
« Last Edit: November 03, 2016, 06:14:04 pm by BertVerhees »

 

Recent

Get Lazarus at SourceForge.net. Fast, secure and Free Open Source software downloads Open Hub project report for Lazarus