Skip to content

Check for Windows OS or JULIA_COPY_STACKS on jcall, improve testing #108

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Mar 18, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 31 additions & 7 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,45 @@ language: julia
os:
- linux
- osx
- windows
before_install:
- source ${TRAVIS_BUILD_DIR}/test/windows_java.sh
julia:
- 1.0
- 1.3
- nightly
jdk:
- oraclejdk8
- openjdk10
- openjdk11

end:
env:
- JULIA_COPY_STACKS=0
- JULIA_COPY_STACKS=1
- JULIA_COPY_STACKS=0 JAVACALL_FORCE_ASYNC_INIT=1
- JULIA_COPY_STACKS=0 JAVACALL_FORCE_ASYNC_TEST=1

matrix:
jobs:
exclude:
- julia: 1.0
env: JULIA_COPY_STACKS=0
- julia: 1.0
env: JULIA_COPY_STACKS=0 JAVACALL_FORCE_ASYNC_INIT=1
- julia: 1.0
env: JULIA_COPY_STACKS=0 JAVACALL_FORCE_ASYNC_TEST=1
- os: mac
env: JULIA_COPY_STACKS=0 JAVACALL_FORCE_ASYNC_INIT=1
- os: mac
env: JULIA_COPY_STACKS=0 JAVACALL_FORCE_ASYNC_TEST=1
- os: windows
env: JULIA_COPY_STACKS=0 JAVACALL_FORCE_ASYNC_INIT=1
- os: windows
env: JULIA_COPY_STACKS=0 JAVACALL_FORCE_ASYNC_TEST=1
- julia: 1.0
os: windows
env: JULIA_COPY_STACKS=1
allow_failures:
- julia_version: nightly
- julia: nightly
- os: windows
env: JULIA_COPY_STACKS=1
- env: JULIA_COPY_STACKS=0 JAVACALL_FORCE_ASYNC_INIT=1
- env: JULIA_COPY_STACKS=0 JAVACALL_FORCE_ASYNC_TEST=1

branches:
only:
Expand Down
14 changes: 11 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,21 @@
# JavaCall

[![Build Status](https://travis-ci.org/JuliaInterop/JavaCall.jl.png)](https://travis-ci.org/JuliaInterop/JavaCall.jl) [![Build status](https://ci.appveyor.com/api/projects/status/qeu6ul9o9s6t5tiw?svg=true)](https://ci.appveyor.com/project/aviks/javacall-jl-6c24s)
[![JavaCall](http://pkg.julialang.org/badges/JavaCall_0.3.svg)](http://pkg.julialang.org/?pkg=JavaCall) [![JavaCall](http://pkg.julialang.org/badges/JavaCall_0.4.svg)](http://pkg.julialang.org/?pkg=JavaCall) [![JavaCall](http://pkg.julialang.org/badges/JavaCall_0.5.svg)](http://pkg.julialang.org/?pkg=JavaCall)
[![JavaCall](http://pkg.julialang.org/badges/JavaCall_0.6.svg)](http://pkg.julialang.org/?pkg=JavaCall)
[![Build Status](https://travis-ci.org/JuliaInterop/JavaCall.jl.png)](https://travis-ci.org/JuliaInterop/JavaCall.jl)
[![Build status](https://ci.appveyor.com/api/projects/status/qeu6ul9o9s6t5tiw?svg=true)](https://ci.appveyor.com/project/aviks/javacall-jl-6c24s)


Call Java programs from Julia.

Documentation is available at http://juliainterop.github.io/JavaCall.jl

## Non-Windows Operating Systems

_JavaCall and it's derivatives do not work correctly Julia 1.1 and Julia 1.2. On Julia 1.3, please set the environment variable `JULIA_COPY_STACKS`. On 1.1 and 1.2, and on 1.3 without `JULIA_COPY_STACKS` set, you may see segfaults or incorrect results. This is typically due to stack corruption. The Julia long-term-support version of 1.0.x continues to work correctly as before._

## Windows Operating System

Do not set the environmental variable `JULIA_COPY_STACKS`. To use `jcall` with `@async` start Julia in the following way:

```
$ julia -i -e "using JavaCall; JavaCall.init()"
```
224 changes: 117 additions & 107 deletions doc/index.md
Original file line number Diff line number Diff line change
@@ -1,107 +1,117 @@
---
layout: default
---

# Call Java programs from Julia

The JavaCall package allows calling Java programs from within Julia code. It uses the Java Native Interface ([JNI][]) to call into an in-process Java Virtual Machine (JVM). The primary entry point to Java is the `jcall` function. This is modeled on the Julia `ccall` function, and takes as input the receiver object (or class, for static methods), the method name, the output type, a tuple of the method parameter types, and the parameters themselves.

This package has been tested using Oracle JDK 7, 8 and 9 on MacOSX and Ubuntu on 64 bit environments. It has also been shown to work with `OpenJDK` flavour of java. It has also been tested on Windows 64 bit environments. However, it does not work on 32 bit environments. It will not work with the Apple 1.6 JDK since that is a 32 bit JVM, and Julia is typically built as a 64 bit executable on OSX.

[JNI]: http://docs.oracle.com/javase/1.5.0/docs/guide/jni/spec/jniTOC.html

## Installation

```julia
Pkg.add("JavaCall")
```

This package has a dependency on the `WinReg` package which, on Windows, is used to derive the location of the JDK automatically.

## Usage

Static and instance methods with primitive or object arguments and return values are callable. Array arguments and return values are also supported. Primitive, string, object or array arguments are converted as required.


```jlcon

julia> using JavaCall

julia> JavaCall.init(["-Xmx128M"])

julia> jlm = @jimport java.lang.Math
JavaObject{:java.lang.Math} (constructor with 2 methods))

julia> jcall(jlm, "sin", jdouble, (jdouble,), pi/2)
1.0

julia> jnu = @jimport java.net.URL
JavaObject{:java.net.URL} (constructor with 2 methods)

julia> gurl = jnu((JString,), "http://www.google.com")
JavaObject{:java.net.URL}(Ptr{Void} @0x0000000108ae2aa8)

julia> jcall(gurl, "getHost", JString,())
"www.google.com"

julia> j_u_arrays = @jimport java.util.Arrays
JavaObject{:java.util.Arrays} (constructor with 2 methods)

julia> jcall(j_u_arrays, "binarySearch", jint, (Array{jint,1}, jint), [10,20,30,40,50,60], 40)
3

```

## Usage from a running JVM

Use JNI or JNA to initialize a Julia VM, then call `JavaCall.init_current_vm()`. Here's an example using JNA:

```java
package zot.julia;

import com.sun.jna.Native;

public class Julia {
static {
Native.register("julia");
jl_init__threading();
}

public static double bubba = Math.random();

public static native void jl_init__threading();
public static native void jl_eval_string(String code);
public static native void jl_atexit_hook(int status);

public static void main(String args[]) {
System.out.println("test");
jl_eval_string("println(\"test from Julia\")");
jl_eval_string("using JavaCall");
jl_eval_string("JavaCall.init_current_vm()");
jl_eval_string("println(\"initialized VM\")");
jl_eval_string("jlm = @jimport java.lang.Math");
jl_eval_string("println(jcall(jlm, \"sin\", jdouble, (jdouble,), pi/2))");
jl_eval_string("jl = @jimport zot.julia.Julia");
System.out.println("Bubba should be " + bubba);
jl_eval_string("println(\"bubba: \", jfield(jl, \"bubba\", jdouble))");
jl_eval_string("println(\"Done with tests\")");
jl_atexit_hook(0);
}
}
```

## Major TODOs and Caveats

* Currently, only a low level interface is available, via `jcall`. As a result, this package is best suited for writing libraries that wrap around existing java packages. Writing user code direcly using this interface might be a bit tedious at present. A high level interface using reflection will eventually be built.

* While basic memory management has been implemented, there is the possibility of some remaining memory leaks in this system. While this is stable enough for scripting style tasks, please test thoroughly before deploying this to long running tasks.

* JavaCall, (and other projects that depend on it, such as JDBC and Spark) do not work on Julia versions 1.1 or 1.2, when using JDK versions less than 11.

* On Julia 1.3 onwards, set the environment variable `JULIA_COPY_STACKS=yes` before starting Julia in order to use JavaCall. Setting this option is required to use JavaCall in the REPL. Using this environment variable does makes multithreading slightly slower in Julia.

* Setting `JULIA_COPY_STACKS=yes` in startup.jl will not work. It must be set before Julia starts. On \*nix based systems, this can be done from the shell by using `$ JULIA_COPY_STACKS=yes julia` from a shell.

* JavaCall can be used in a limited capacity from the root `Task` of Julia without `JULIA_COPY_STACKS=yes`. For example, using JavaCall in a programfile or via `julia --eval` will work. However, JavaCall will not function with `@async` or the standard REPL backend.

* Alternatively, Julia 1.0.x works with all versions of Java.
---
layout: default
---

# Call Java programs from Julia

The JavaCall package allows calling Java programs from within Julia code. It uses the Java Native Interface ([JNI][]) to call into an in-process Java Virtual Machine (JVM). The primary entry point to Java is the `jcall` function. This is modeled on the Julia `ccall` function, and takes as input the receiver object (or class, for static methods), the method name, the output type, a tuple of the method parameter types, and the parameters themselves.

This package has been tested using Oracle JDK 7, 8 and 9 on MacOSX and Ubuntu on 64 bit environments. It has also been shown to work with `OpenJDK` flavour of java. It has also been tested on Windows 64 bit environments. However, it does not work on 32 bit environments. It will not work with the Apple 1.6 JDK since that is a 32 bit JVM, and Julia is typically built as a 64 bit executable on OSX.

[JNI]: http://docs.oracle.com/javase/1.5.0/docs/guide/jni/spec/jniTOC.html

## Installation

```julia
Pkg.add("JavaCall")
```

This package has a dependency on the `WinReg` package which, on Windows, is used to derive the location of the JDK automatically.

## Usage

Static and instance methods with primitive or object arguments and return values are callable. Array arguments and return values are also supported. Primitive, string, object or array arguments are converted as required.


```jlcon

julia> using JavaCall

julia> JavaCall.init(["-Xmx128M"])

julia> jlm = @jimport java.lang.Math
JavaObject{:java.lang.Math} (constructor with 2 methods))

julia> jcall(jlm, "sin", jdouble, (jdouble,), pi/2)
1.0

julia> jnu = @jimport java.net.URL
JavaObject{:java.net.URL} (constructor with 2 methods)

julia> gurl = jnu((JString,), "http://www.google.com")
JavaObject{:java.net.URL}(Ptr{Void} @0x0000000108ae2aa8)

julia> jcall(gurl, "getHost", JString,())
"www.google.com"

julia> j_u_arrays = @jimport java.util.Arrays
JavaObject{:java.util.Arrays} (constructor with 2 methods)

julia> jcall(j_u_arrays, "binarySearch", jint, (Array{jint,1}, jint), [10,20,30,40,50,60], 40)
3

```

## Usage from a running JVM

Use JNI or JNA to initialize a Julia VM, then call `JavaCall.init_current_vm()`. Here's an example using JNA:

```java
package zot.julia;

import com.sun.jna.Native;

public class Julia {
static {
Native.register("julia");
jl_init__threading();
}

public static double bubba = Math.random();

public static native void jl_init__threading();
public static native void jl_eval_string(String code);
public static native void jl_atexit_hook(int status);

public static void main(String args[]) {
System.out.println("test");
jl_eval_string("println(\"test from Julia\")");
jl_eval_string("using JavaCall");
jl_eval_string("JavaCall.init_current_vm()");
jl_eval_string("println(\"initialized VM\")");
jl_eval_string("jlm = @jimport java.lang.Math");
jl_eval_string("println(jcall(jlm, \"sin\", jdouble, (jdouble,), pi/2))");
jl_eval_string("jl = @jimport zot.julia.Julia");
System.out.println("Bubba should be " + bubba);
jl_eval_string("println(\"bubba: \", jfield(jl, \"bubba\", jdouble))");
jl_eval_string("println(\"Done with tests\")");
jl_atexit_hook(0);
}
}
```

## Major TODOs and Caveats

* Currently, only a low level interface is available, via `jcall`. As a result, this package is best suited for writing libraries that wrap around existing java packages. Writing user code direcly using this interface might be a bit tedious at present. A high level interface using reflection will eventually be built.

* While basic memory management has been implemented, there is the possibility of some remaining memory leaks in this system. While this is stable enough for scripting style tasks, please test thoroughly before deploying this to long running tasks.

### Non-Windows Operating Systems (Linux, MacOS, FreeBSD)

* JavaCall, (and other projects that depend on it, such as JDBC and Spark) do not work on Julia versions 1.1 or 1.2, when using JDK versions less than 11.

* On Julia 1.3 onwards, set the environment variable `JULIA_COPY_STACKS=yes` before starting Julia in order to use JavaCall. Setting this option is required to use JavaCall in the REPL. Using this environment variable does makes multithreading slightly slower in Julia.

* Setting `JULIA_COPY_STACKS=yes` in startup.jl will not work. It must be set before Julia starts. On \*nix based systems, this can be done from the shell by using `$ JULIA_COPY_STACKS=yes julia` from a shell.

* JavaCall can be used in a limited capacity from the root `Task` of Julia without `JULIA_COPY_STACKS=yes`. For example, using JavaCall in a programfile or via `julia --eval` will work. However, JavaCall will not function with `@async` or the standard REPL backend.

* Alternatively, Julia 1.0.x works with all versions of Java.

### Windows Operating System

* Do not set the environmental variable `JULIA_COPY_STACKS`

* To use `@async` with JavaCall, start JavaCall on the root Task when running Julia:
```
$ julia -i -e "using JavaCall; JavaCall.init()"
19 changes: 11 additions & 8 deletions src/JavaCall.jl
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,17 @@ include("reflect.jl")

function __init__()
global JULIA_COPY_STACKS = get(ENV, "JULIA_COPY_STACKS", "") ∈ ("1", "yes")
if VERSION ≥ v"1.1-" && VERSION < v"1.3-"
@warn("JavaCall does not work correctly on Julia v$VERSION. \n" *
"Either use Julia v1.0.x, or v1.3.0 or higher.\n"*
"For 1.3 onwards, please also set the environment variable `JULIA_COPY_STACKS` to be `1` or `yes`")
end
if VERSION ≥ v"1.3-" && ! JULIA_COPY_STACKS
@warn("JavaCall needs the environment variable `JULIA_COPY_STACKS` to be `1` or `yes`.\n"*
"Calling the JVM may result in undefined behavior.")
if ! Sys.iswindows()
# On Windows, JULIA_COPY_STACKS is not needed and causes crash
if VERSION ≥ v"1.1-" && VERSION < v"1.3-"
@warn("JavaCall does not work correctly on Julia v$VERSION. \n" *
"Either use Julia v1.0.x, or v1.3.0 or higher.\n"*
"For 1.3 onwards, please also set the environment variable `JULIA_COPY_STACKS` to be `1` or `yes`")
end
if VERSION ≥ v"1.3-" && ! JULIA_COPY_STACKS
@warn("JavaCall needs the environment variable `JULIA_COPY_STACKS` to be `1` or `yes`.\n"*
"Calling the JVM may result in undefined behavior.")
end
end
findjvm()
global create = Libdl.dlsym(libjvm, :JNI_CreateJavaVM)
Expand Down
7 changes: 6 additions & 1 deletion src/core.jl
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ jvalue(v::Float64) = jvalue(reinterpret(Int64, v))
jvalue(v::Ptr) = jvalue(Int(v))


function _jimport(juliaclass)
function _jimport(juliaclass)
for str ∈ [" ", "(", ")"]
juliaclass = replace(juliaclass, str=>"")
end
Expand All @@ -122,6 +122,7 @@ end


function jnew(T::Symbol, argtypes::Tuple, args...)
assertroottask_or_goodenv()
sig = method_signature(Nothing, argtypes...)
jmethodId = ccall(jnifunc.GetMethodID, Ptr{Nothing},
(Ptr{JNIEnv}, Ptr{Nothing}, Ptr{UInt8}, Ptr{UInt8}), penv, metaclass(T),
Expand All @@ -135,6 +136,7 @@ end
# Call static methods
function jcall(typ::Type{JavaObject{T}}, method::AbstractString, rettype::Type, argtypes::Tuple,
args... ) where T
assertroottask_or_goodenv()
sig = method_signature(rettype, argtypes...)
jmethodId = ccall(jnifunc.GetStaticMethodID, Ptr{Nothing},
(Ptr{JNIEnv}, Ptr{Nothing}, Ptr{UInt8}, Ptr{UInt8}), penv, metaclass(T),
Expand All @@ -145,6 +147,7 @@ end

# Call instance methods
function jcall(obj::JavaObject, method::AbstractString, rettype::Type, argtypes::Tuple, args... )
assertroottask_or_goodenv()
sig = method_signature(rettype, argtypes...)
jmethodId = ccall(jnifunc.GetMethodID, Ptr{Nothing},
(Ptr{JNIEnv}, Ptr{Nothing}, Ptr{UInt8}, Ptr{UInt8}), penv, metaclass(obj),
Expand All @@ -154,6 +157,7 @@ function jcall(obj::JavaObject, method::AbstractString, rettype::Type, argtypes:
end

function jfield(typ::Type{JavaObject{T}}, field::AbstractString, fieldType::Type) where T
assertroottask_or_goodenv()
jfieldID = ccall(jnifunc.GetStaticFieldID, Ptr{Nothing},
(Ptr{JNIEnv}, Ptr{Nothing}, Ptr{UInt8}, Ptr{UInt8}), penv, metaclass(T),
String(field), signature(fieldType))
Expand All @@ -162,6 +166,7 @@ function jfield(typ::Type{JavaObject{T}}, field::AbstractString, fieldType::Type
end

function jfield(obj::JavaObject, field::AbstractString, fieldType::Type)
assertroottask_or_goodenv()
jfieldID = ccall(jnifunc.GetFieldID, Ptr{Nothing},
(Ptr{JNIEnv}, Ptr{Nothing}, Ptr{UInt8}, Ptr{UInt8}), penv, metaclass(obj),
String(field), signature(fieldType))
Expand Down
Loading