From 20bc97d3794f54ba0f25bb320dec71d4f637609b Mon Sep 17 00:00:00 2001 From: drrb Date: Sat, 30 May 2015 18:51:39 +1000 Subject: [PATCH 01/16] Configured Appveyor build --- appveyor.yml | 21 +++++++++++++++++++++ src/build/scripts/rustup.ps1 | 23 +++++++++++++++++++++++ 2 files changed, 44 insertions(+) create mode 100644 appveyor.yml create mode 100644 src/build/scripts/rustup.ps1 diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 0000000..7f06f19 --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,21 @@ +version: '{build}' +os: Windows Server 2012 +environment: + matrix: + - JAVA_HOME: C:\Program Files\Java\jdk1.7.0 + - JAVA_HOME: C:\Program Files\Java\jdk1.8.0 +install: + - ps: choco install maven + - ps: src/build/scripts/rustup.ps1 + - ps: $env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path","User") + - mvn --version + - rustc --version + - cargo --version +build_script: + - mvn --batch-mode clean package -DskipTests +test_script: + - mvn --batch-mode clean package +artifacts: + - path: target/rust-libs/* +cache: + - C:\Users\appveyor\.m2 diff --git a/src/build/scripts/rustup.ps1 b/src/build/scripts/rustup.ps1 new file mode 100644 index 0000000..fbb9138 --- /dev/null +++ b/src/build/scripts/rustup.ps1 @@ -0,0 +1,23 @@ +$ErrorActionPreference = "Stop" + +if ([environment]::Is64BitOperatingSystem) { + $arch = "x86_64" + $install_dir = "C:\Program Files\Rust" +} else { + $arch = "i686" + $install_dir = "C:\Program Files (x86)\Rust" +} + +$package = "rust-nightly-$($arch)-pc-windows-gnu.exe" +$url = "https://static.rust-lang.org/dist/$($package)" + +echo "Downloading Rust and Cargo from $($url)" +Start-FileDownload $url + +echo "Installing Rust" +Start-Process ".\$($package)" -ArgumentList "/VERYSILENT /NORESTART" -NoNewWindow -Wait + +echo "Refreshing Path" +$env:Path = [System.Environment]::GetEnvironmentVariable("Path", "User") + ";" + [System.Environment]::GetEnvironmentVariable("Path", "Machine") + +echo "Rust and Cargo are ready to roll!" From 2baf4f663937a4c05fc326cf5a8825ac4065a087 Mon Sep 17 00:00:00 2001 From: drrb Date: Sat, 30 May 2015 19:01:23 +1000 Subject: [PATCH 02/16] Added Appveyor badge --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 51b7ccb..c0754f2 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,9 @@ An example project showing how to call into Rust code from Java. -[![Build Status](https://travis-ci.org/drrb/java-rust-example.svg?branch=master)](https://travis-ci.org/drrb/java-rust-example) +| OSX | Linux | Windows | +| --- | ----- | ------- | +| ![OSX Build Status](https://img.shields.io/badge/build-passing%20on%20my%20laptop-brightgreen.svg) | [![Linux Build Status](https://travis-ci.org/drrb/java-rust-example.svg?branch=master)](https://travis-ci.org/drrb/java-rust-example) | [![Windows Build status](https://ci.appveyor.com/api/projects/status/4yygb3925k7p87de/branch/master?svg=true)](https://ci.appveyor.com/project/drrb/java-rust-example/branch/master) | ## Requirements From cbdad0734250216a9d90751ab9cc99327fa5fd7f Mon Sep 17 00:00:00 2001 From: drrb Date: Sat, 30 May 2015 19:20:36 +1000 Subject: [PATCH 03/16] Added test fix for Appveyor --- pom.xml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pom.xml b/pom.xml index 0c0c607..23db604 100644 --- a/pom.xml +++ b/pom.xml @@ -81,6 +81,12 @@ along with this program. If not, see . org.apache.maven.plugins maven-surefire-plugin 2.18.1 + + + -Djna.nosys=true + 1 + false + From 11e4e6865197b7d785fc0808696ad1f70dd478a3 Mon Sep 17 00:00:00 2001 From: drrb Date: Sat, 30 May 2015 22:31:59 +1000 Subject: [PATCH 04/16] Claim support for Windows --- README.md | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index c0754f2..25d5a1a 100644 --- a/README.md +++ b/README.md @@ -8,9 +8,8 @@ An example project showing how to call into Rust code from Java. ## Requirements -- Tested on OSX and Linux. May also work on Windows! - Java 7+ -- Rust (tested 1.0 Beta) +- Rust (tested with 1.0, nightly) ## Contents @@ -56,12 +55,11 @@ Hello from Rust, John ## Platform Support -This project is tested on OSX and Ubuntu. It should work on OSX, and any 32 bit -or 64 bit Gnu/Linux system. +This project is tested on OSX, Ubuntu, and Windows. It should also work on any 32 bit or 64 bit Gnu/Linux system. ## Limitations -Some of the examples leak memory. Any memory that is allocated in Rust needs to be freed manually because it's not managed by JNA. Some examples pass objects back into Rust to be dropped for this reason, but we don't clean up every thing properly (strings, for example). This is almost certainly not a limitation of Rust, but a limitation of my current understanding of Rust. +Some of the examples leak memory. Any memory that is allocated in Rust needs to be freed manually because it's not managed by JNA. Some examples pass objects back into Rust to be dropped for this reason, but we don't clean up everything properly (strings, for example). This is almost certainly not a limitation of Rust, but a limitation of my current understanding of Rust. ## License From 01ca765e8ffa6866e5193121247a30fdffa8baeb Mon Sep 17 00:00:00 2001 From: drrb Date: Sat, 30 May 2015 22:36:38 +1000 Subject: [PATCH 05/16] Test against stable and nightly on Travis --- .travis.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 042bdb9..2cd5d1a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,11 @@ language: java jdk: - oraclejdk8 +env: + - RUST_CHANNEL=stable + - RUST_CHANNEL=nightly before_install: - yes | sudo add-apt-repository ppa:hansjorg/rust - sudo apt-get update install: - - sudo apt-get install rust-nightly + - sudo apt-get install rust-${RUST_CHANNEL} From 4d2c21629eb04f6217962229070efd8ad8de05df Mon Sep 17 00:00:00 2001 From: drrb Date: Sat, 30 May 2015 23:24:19 +1000 Subject: [PATCH 06/16] Fixed Rust warning --- src/main/rust/com/github/drrb/javarust/lib/greetings.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/rust/com/github/drrb/javarust/lib/greetings.rs b/src/main/rust/com/github/drrb/javarust/lib/greetings.rs index 879b1fc..568dae6 100644 --- a/src/main/rust/com/github/drrb/javarust/lib/greetings.rs +++ b/src/main/rust/com/github/drrb/javarust/lib/greetings.rs @@ -196,6 +196,6 @@ fn to_ptr(string: String) -> *const c_char { let ptr = cs.as_ptr(); // Tell Rust not to clean up the string while we still have a pointer to it. // Otherwise, we'll get a segfault. - unsafe { mem::forget(cs) }; + mem::forget(cs); ptr } From 81ede5d6be4f195865b8d39e51631a812a2c6c4b Mon Sep 17 00:00:00 2001 From: drrb Date: Mon, 22 May 2017 20:55:51 +1000 Subject: [PATCH 07/16] Fixed bad struct JNA mapping and some memory leaks Incorporated changes from #4. Also looked into the way that we free memory, to fix some memory leaks. --- README.md | 12 +-- generate-link-table | 89 +++++++++++++++++++ .../com/github/drrb/javarust/Greeting.java | 13 ++- .../com/github/drrb/javarust/GreetingSet.java | 12 ++- .../com/github/drrb/javarust/Greetings.java | 4 +- .../com/github/drrb/javarust/lib/greetings.rs | 62 ++++++------- .../github/drrb/javarust/GreetingsTest.java | 49 +++++----- 7 files changed, 170 insertions(+), 71 deletions(-) create mode 100755 generate-link-table diff --git a/README.md b/README.md index 25d5a1a..876a686 100644 --- a/README.md +++ b/README.md @@ -29,12 +29,12 @@ and the [Rust code](src/main/rust/com/github/drrb/javarust/lib/greetings.rs). Th implementation is heavily commented to explain it. So far, it contains examples of the following (click the links to see!): -- *[Arguments](src/test/java/com/github/drrb/javarust/GreetingsTest.java#L42)*: passing simple arguments from Java to Rust ([Java side](src/main/java/com/github/drrb/javarust/Greetings.java#L44) / [Rust side](src/main/rust/com/github/drrb/javarust/lib/greetings.rs#L73)) -- *[Return values](src/test/java/com/github/drrb/javarust/GreetingsTest.java#L47)*: returning simple values from Rust to Java ([Java side](src/main/java/com/github/drrb/javarust/Greetings.java#L49) / [Rust side](src/main/rust/com/github/drrb/javarust/lib/greetings.rs#L86)) -- *[Struct arguments](src/test/java/com/github/drrb/javarust/GreetingsTest.java#L54)*: passing structs to Rust from Java ([Java side](src/main/java/com/github/drrb/javarust/Greetings.java#L54) / [Rust side](src/main/rust/com/github/drrb/javarust/lib/greetings.rs#L97)) -- *[Returning structs (2 examples)](src/test/java/com/github/drrb/javarust/GreetingsTest.java#L63)*: returning structs from Rust by value and by reference ([Java side](src/main/java/com/github/drrb/javarust/Greetings.java#L62) / [Rust side](src/main/rust/com/github/drrb/javarust/lib/greetings.rs#L105)) -- *[Callbacks (3 examples)](src/test/java/com/github/drrb/javarust/GreetingsTest.java#L80)*: passing callbacks to Rust that get called from the Rust code ([Java side](src/main/java/com/github/drrb/javarust/Greetings.java#L84) / [Rust side](src/main/rust/com/github/drrb/javarust/lib/greetings.rs#L122)) -- *[Freeing memory](src/test/java/com/github/drrb/javarust/GreetingsTest.java#L76)*: freeing memory allocated in Rust ([Java side](src/main/java/com/github/drrb/javarust/Greetings.java#L144) / [Rust side](src/main/rust/com/github/drrb/javarust/lib/greetings.rs#L175)) +- *[Arguments](src/test/java/com/github/drrb/javarust/GreetingsTest.java#L46)*: passing simple arguments from Java to Rust ([Java side](src/main/java/com/github/drrb/javarust/Greetings.java#L45) / [Rust side](src/main/rust/com/github/drrb/javarust/lib/greetings.rs#L82)) +- *[Return values](src/test/java/com/github/drrb/javarust/GreetingsTest.java#L51)*: returning simple values from Rust to Java ([Java side](src/main/java/com/github/drrb/javarust/Greetings.java#L50) / [Rust side](src/main/rust/com/github/drrb/javarust/lib/greetings.rs#L92)) +- *[Struct arguments](src/test/java/com/github/drrb/javarust/GreetingsTest.java#L57)*: passing structs to Rust from Java ([Java side](src/main/java/com/github/drrb/javarust/Greetings.java#L55) / [Rust side](src/main/rust/com/github/drrb/javarust/lib/greetings.rs#L101)) +- *[Returning structs (2 examples)](src/test/java/com/github/drrb/javarust/GreetingsTest.java#L66)*: returning structs from Rust by value and by reference ([Java side](src/main/java/com/github/drrb/javarust/Greetings.java#L72) / [Rust side](src/main/rust/com/github/drrb/javarust/lib/greetings.rs#L110)) +- *[Callbacks (3 examples)](src/test/java/com/github/drrb/javarust/GreetingsTest.java#L81)*: passing callbacks to Rust that get called from the Rust code ([Java side](src/main/java/com/github/drrb/javarust/Greetings.java#L85) / [Rust side](src/main/rust/com/github/drrb/javarust/lib/greetings.rs#L130)) +- *[Freeing memory](src/test/java/com/github/drrb/javarust/GreetingsTest.java#L68)*: freeing memory allocated in Rust ([Java side](src/main/java/com/github/drrb/javarust/Greetings.java#L115) / [Rust side](src/main/rust/com/github/drrb/javarust/lib/greetings.rs#L172)) ## Building and Running the Tests diff --git a/generate-link-table b/generate-link-table new file mode 100755 index 0000000..c6e55c3 --- /dev/null +++ b/generate-link-table @@ -0,0 +1,89 @@ +#!/usr/bin/env ruby + +require "ostruct" + +class Concept < OpenStruct + def to_markdown + "*#{test_link}*: #{description} (#{java_link} / #{rust_link})" + end + + def test_link + link text: name, regex: test_regex, file: "src/test/java/com/github/drrb/javarust/GreetingsTest.java" + end + + def java_link + link text: "Java side", regex: java_regex, file: "src/main/java/com/github/drrb/javarust/Greetings.java" + end + + def rust_link + link text: "Rust side", regex: rust_regex, file: "src/main/rust/com/github/drrb/javarust/lib/greetings.rs" + end + + private + def link(text:, regex:, file:) + line = find_line(regex, file) + "[#{text}](#{file}#L#{line + 1})" + end + + def find_line(regex, file) + lines = File.read(file).lines.each_with_index + line, index = lines.find {|line, index| line =~ regex} + unless line + raise "Couldn't find #{regex.inspect} in #{file.inspect}" + end + index + 1 + end +end + +class Array + def to_markdown_points + map {|e| "- #{e}" }.join("\n") + end +end + +concepts = [ + Concept.new( + name: "Arguments", + description: "passing simple arguments from Java to Rust", + test_regex: /shouldAcceptStringParameterFromJavaToRust/, + java_regex: /void printGreeting/, + rust_regex: /pub extern fn printGreeting/ + ), + Concept.new( + name: "Return values", + description: "returning simple values from Rust to Java", + test_regex: /public void shouldAcceptStringFromJavaToRustAndReturnAnotherOne/, + java_regex: /String renderGreeting\(String name\)/, + rust_regex: /pub extern fn renderGreeting/ + ), + Concept.new( + name: "Struct arguments", + description: "passing structs to Rust from Java", + test_regex: /public void shouldAcceptAStructFromJavaToRust/, + java_regex: /String greet\(Person john\)/, + rust_regex: /pub extern fn greet\(person: &Person\)/ + ), + Concept.new( + name: "Returning structs (2 examples)", + description: "returning structs from Rust by value and by reference", + test_regex: /public void shouldGetAStructFromRustByValue/, + java_regex: /Greeting.ByValue getGreetingByValue/, + rust_regex: /pub extern fn getGreetingByValue/ + ), + Concept.new( + name: "Callbacks (3 examples)", + description: "passing callbacks to Rust that get called from the Rust code", + test_regex: /public void shouldGetAStringFromRustInACallback/, + java_regex: /void callMeBack\(GreetingCallback callback\)/, + rust_regex: /pub extern fn callMeBack/ + ), + Concept.new( + name: "Freeing memory", + description: "freeing memory allocated in Rust", + test_regex: /try \(Greeting greeting/, + java_regex: /void dropGreeting\(Greeting greeting\)/, + rust_regex: /pub extern fn dropGreeting/ + ) +] + +puts concepts.map(&:to_markdown).to_markdown_points diff --git a/src/main/java/com/github/drrb/javarust/Greeting.java b/src/main/java/com/github/drrb/javarust/Greeting.java index c860dd1..aa3ec61 100644 --- a/src/main/java/com/github/drrb/javarust/Greeting.java +++ b/src/main/java/com/github/drrb/javarust/Greeting.java @@ -17,15 +17,18 @@ package com.github.drrb.javarust; import com.sun.jna.Structure; -import static java.util.Arrays.asList; + +import java.io.Closeable; import java.util.List; +import static java.util.Arrays.asList; + /** * A struct that we return from Rust to Java. * * This is the Java representation of the Greeting struct in Rust. */ -public class Greeting extends Structure { +public class Greeting extends Structure implements Closeable { public static class ByReference extends Greeting implements Structure.ByReference { } @@ -43,4 +46,10 @@ public String getText() { protected List getFieldOrder() { return asList("text"); } + + @Override + public void close() { + // Send the struct back to rust for the memory to be freed + Greetings.INSTANCE.dropGreeting(this); + } } diff --git a/src/main/java/com/github/drrb/javarust/GreetingSet.java b/src/main/java/com/github/drrb/javarust/GreetingSet.java index a8cfda6..06ed511 100644 --- a/src/main/java/com/github/drrb/javarust/GreetingSet.java +++ b/src/main/java/com/github/drrb/javarust/GreetingSet.java @@ -17,6 +17,8 @@ package com.github.drrb.javarust; import com.sun.jna.Structure; + +import java.io.Closeable; import java.util.Arrays; import java.util.List; @@ -29,7 +31,13 @@ public class GreetingSet extends Structure { public static class ByReference extends GreetingSet implements Structure.ByReference { } - public static class ByValue extends GreetingSet implements Structure.ByValue { + public static class ByValue extends GreetingSet implements Structure.ByValue, Closeable { + + @Override + public void close() { + Greetings.INSTANCE.dropGreetingSet(this); + } + } /** @@ -38,7 +46,7 @@ public static class ByValue extends GreetingSet implements Structure.ByValue { * Actually, this is a pointer to a bunch of struct instances that are next * to each other in memory. We cast it to an array in {@link #getGreetings()}. * - * NB: We need to explicity specify that the field is a pointer (i.e. we need + * NB: We need to explicitly specify that the field is a pointer (i.e. we need * to use ByReference) because, by default, JNA assumes that struct fields * are not pointers (i.e. the if you just say "Greeting", JNA assumes * "Greeting.ByValue" here). diff --git a/src/main/java/com/github/drrb/javarust/Greetings.java b/src/main/java/com/github/drrb/javarust/Greetings.java index c395be5..aa65fc6 100644 --- a/src/main/java/com/github/drrb/javarust/Greetings.java +++ b/src/main/java/com/github/drrb/javarust/Greetings.java @@ -76,7 +76,7 @@ public interface Greetings extends Library { * This is the same as returning a {@link GreetingSet.ByReference}. JNA assumes * it's by reference when it's returned from a native function. */ - GreetingSet renderGreetings(); + GreetingSet.ByValue renderGreetings(); /** * Passing a callback that will be called from Rust with individual strings @@ -116,5 +116,5 @@ interface GreetingSetCallback extends Callback { /** * Free the memory used by a GreetingSet */ - void dropGreetingSet(GreetingSet greetingSet); + void dropGreetingSet(GreetingSet.ByValue greetingSet); } diff --git a/src/main/rust/com/github/drrb/javarust/lib/greetings.rs b/src/main/rust/com/github/drrb/javarust/lib/greetings.rs index 568dae6..ec00661 100644 --- a/src/main/rust/com/github/drrb/javarust/lib/greetings.rs +++ b/src/main/rust/com/github/drrb/javarust/lib/greetings.rs @@ -21,28 +21,23 @@ use std::ffi::{CStr,CString}; use std::str; use std::mem; - -// Normally we'd get these from libc, but that's unstable in 1.0 Beta -mod mylibc { - #[allow(non_camel_case_types)] - pub type c_int = i32; - #[allow(non_camel_case_types)] - pub type c_char = i8; -} -use mylibc::c_int; -use mylibc::c_char; +use std::os::raw::c_char; // GreetingSet corresponds to com.github.drrb.javarust.GreetingSet in Java. It is marked with // repr(c), as are all the structs passed back to Java. This makes sure the structs are represented // in memory in a way JNA can read them. #[repr(C)] pub struct GreetingSet { - // A pointer to an array of Greetings. This is converted to a Greeting.ByReference by JNA. - greetings: Box<[Greeting]>, - // The size of the array. We need to pass it back to Java so that we know how long the array - // is (JNA can't guess the size). We need to do this with all arrays created in Java and read - // in Rust (or vise-versa). This c_int is converted to a Java int by JNA. - number_of_greetings: c_int + // A struct that includes a pointer to an array of Greetings and a size. JNA will convert this + // to two fields: a Greeting.ByReference and an int. + greetings: Box<[Greeting]> +} + +impl Drop for GreetingSet { + fn drop(&mut self) { + // Print a message when we drop the object, so that we know we're not leaking memory + println!("Dropping GreetingSet"); + } } // Greeting corresponds to com.github.drrb.javarust.Greeting in Java. It is marked with @@ -63,6 +58,12 @@ impl Greeting { } } +impl Drop for Greeting { + fn drop(&mut self) { + println!("Dropping Greeting: {}", to_string(self.text)); + } +} + #[repr(C)] #[allow(missing_copy_implementations)] pub struct Person { @@ -144,30 +145,25 @@ pub extern fn callMeBack(callback: extern "stdcall" fn(*const c_char)) { /// In this example we send a pointer to a struct back to Java via the callback. #[no_mangle] #[allow(non_snake_case)] -pub extern fn sendGreetings(callback: extern "C" fn(Box)) { +pub extern fn sendGreetings(callback: extern "C" fn(&GreetingSet)) { let greetings = vec![ Greeting::new("Hello!"), Greeting::new("Hello again!") ]; - let num_greetings = greetings.len(); - let set = Box::new(GreetingSet { - // Get a pointer to the vector as an array, so that we can pass it back to Java - greetings: greetings.into_boxed_slice(), - // Also return the length of the array, so that we can create the array back in Java - number_of_greetings: num_greetings as c_int - }); - callback(set); + let set = GreetingSet { + // Get a pointer to the vector as an array, so that we can pass it back to Java + greetings: greetings.into_boxed_slice() + }; + callback(&set); // Let the callback "borrow" the set. Rust will destroy it after calling the callback } /// Example of returning a more complicated struct from Rust #[no_mangle] #[allow(non_snake_case)] -pub extern fn renderGreetings() -> Box { +pub extern fn renderGreetings() -> GreetingSet { let greetings = vec![ Greeting::new("Hello!"), Greeting::new("Hello again!") ]; - let num_greetings = greetings.len(); - Box::new(GreetingSet { - greetings: greetings.into_boxed_slice(), - number_of_greetings: num_greetings as c_int - }) + GreetingSet { + greetings: greetings.into_boxed_slice() + } } #[no_mangle] @@ -179,8 +175,8 @@ pub extern fn dropGreeting(_: Box) { #[no_mangle] #[allow(non_snake_case)] -pub extern fn dropGreetingSet(_: Box) { - // Do nothing here. Because we own the GreetingSet here (we're using a Box) and we're not +pub extern fn dropGreetingSet(_: GreetingSet) { + // Do nothing here. Because we own the GreetingSet here and we're not // returning it, Rust will assume we don't want it anymore and clean it up. } diff --git a/src/test/java/com/github/drrb/javarust/GreetingsTest.java b/src/test/java/com/github/drrb/javarust/GreetingsTest.java index 469331a..72edb3d 100644 --- a/src/test/java/com/github/drrb/javarust/GreetingsTest.java +++ b/src/test/java/com/github/drrb/javarust/GreetingsTest.java @@ -18,16 +18,18 @@ import com.github.drrb.javarust.Greetings.GreetingCallback; import com.github.drrb.javarust.Greetings.GreetingSetCallback; -import static com.github.drrb.javarust.test.Matchers.*; import com.github.drrb.javarust.test.MethodPrintingRule; -import static java.util.Arrays.asList; -import java.util.LinkedList; -import java.util.List; -import static org.junit.Assert.assertThat; import org.junit.Before; import org.junit.Rule; import org.junit.Test; +import java.util.LinkedList; +import java.util.List; + +import static com.github.drrb.javarust.test.Matchers.is; +import static org.hamcrest.Matchers.contains; +import static org.junit.Assert.assertThat; + public class GreetingsTest { @Rule @@ -61,19 +63,17 @@ public void shouldAcceptAStructFromJavaToRust() { @Test public void shouldGetAStructFromRustByValue() { - Greeting greeting = library.getGreetingByValue(); - assertThat(greeting.text, is("Hello from Rust!")); + // Using try-with-resources so that memory gets cleaned up. See Greeting.close() + try (Greeting greeting = library.getGreetingByValue()) { + assertThat(greeting.text, is("Hello from Rust!")); + } } @Test public void shouldGetAStructFromRustByReference() { - Greeting greeting = library.getGreetingByReference(); - - assertThat(greeting.text, is("Hello from Rust!")); - - // Free the memory after using it. We need to do this because JNA assumes - // that the memory is owned by Rust, so Rust must clean it up. - library.dropGreeting(greeting); + try (Greeting greeting = library.getGreetingByReference()) { + assertThat(greeting.text, is("Hello from Rust!")); + } } @Test @@ -84,7 +84,7 @@ public void apply(String greeting) { greetings.add(greeting); } }); - assertThat(greetings, is(asList("Hello there!"))); + assertThat(greetings, contains("Hello there!")); } @Test @@ -101,21 +101,18 @@ public void apply(GreetingSet.ByReference greetingSet) { greetingStrings.add(greeting.getText()); } - assertThat(greetingStrings, is(asList("Hello!", "Hello again!"))); - for (Greeting greeting: greetings) { - library.dropGreeting(greeting); - } + assertThat(greetingStrings, contains("Hello!", "Hello again!")); } @Test public void shouldGetAStructFromRustContainingAnArrayOfStructs() { - GreetingSet result = library.renderGreetings(); - List greetings = new LinkedList<>(); - for (Greeting greeting: result.getGreetings()) { - greetings.add(greeting.getText()); - } + try (GreetingSet.ByValue result = library.renderGreetings()) { + List greetings = new LinkedList<>(); + for (Greeting greeting : result.getGreetings()) { + greetings.add(greeting.getText()); + } - assertThat(greetings, is(asList("Hello!", "Hello again!"))); - library.dropGreetingSet(result); + assertThat(greetings, contains("Hello!", "Hello again!")); + } } } From 2ca5fc6ba2dfbe498df91ae4b0c2c6f1363329cc Mon Sep 17 00:00:00 2001 From: drrb Date: Mon, 22 May 2017 21:06:39 +1000 Subject: [PATCH 08/16] Modifying travis config --- .travis.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 2cd5d1a..98c255b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,7 +5,10 @@ env: - RUST_CHANNEL=stable - RUST_CHANNEL=nightly before_install: - - yes | sudo add-apt-repository ppa:hansjorg/rust + - which rustup + - which rustup.sh + - which rustc - sudo apt-get update + - apt search rust install: - sudo apt-get install rust-${RUST_CHANNEL} From 48570060a5e239e21ccd6a5edfade3eb42d390d4 Mon Sep 17 00:00:00 2001 From: drrb Date: Mon, 22 May 2017 21:09:09 +1000 Subject: [PATCH 09/16] Trying more travis config --- .travis.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 98c255b..c5e8c4c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,10 +5,10 @@ env: - RUST_CHANNEL=stable - RUST_CHANNEL=nightly before_install: - - which rustup - - which rustup.sh - - which rustc + - which rustup || true + - which rustup.sh || true + - which rustc || true - sudo apt-get update - - apt search rust + - apt search rust || true install: - sudo apt-get install rust-${RUST_CHANNEL} From febe65b394a4dc83fa93f0e96bc2f52bbb50f132 Mon Sep 17 00:00:00 2001 From: drrb Date: Mon, 22 May 2017 21:12:15 +1000 Subject: [PATCH 10/16] Trying more travis config --- .travis.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index c5e8c4c..cb26506 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,10 +5,10 @@ env: - RUST_CHANNEL=stable - RUST_CHANNEL=nightly before_install: - - which rustup || true - - which rustup.sh || true - - which rustc || true - - sudo apt-get update - - apt search rust || true + - "which rustup || true" + - "which rustup.sh || true" + - "which rustc || true" + - "sudo apt-get update" + - "apt search rust || true" install: - sudo apt-get install rust-${RUST_CHANNEL} From f6c74394f0fc3d93e5768b7130f07038c7c1e7d2 Mon Sep 17 00:00:00 2001 From: drrb Date: Mon, 22 May 2017 21:20:22 +1000 Subject: [PATCH 11/16] Try using rustup directly --- .travis.yml | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index cb26506..9a40926 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,10 +5,8 @@ env: - RUST_CHANNEL=stable - RUST_CHANNEL=nightly before_install: - - "which rustup || true" - - "which rustup.sh || true" - - "which rustc || true" - - "sudo apt-get update" - - "apt search rust || true" + - "curl https://sh.rustup.rs -sSf > rustup && chmod +x rustup" + - "./rustup -y" + - "rustc --version" install: - - sudo apt-get install rust-${RUST_CHANNEL} + - rustup toolchain install ${RUST_CHANNEL} From 8c27976336276951f9ad66e6305c3cc4d59b7093 Mon Sep 17 00:00:00 2001 From: drrb Date: Tue, 23 May 2017 07:50:48 +1000 Subject: [PATCH 12/16] Add rust to path on Travis --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 9a40926..be27188 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,6 +7,7 @@ env: before_install: - "curl https://sh.rustup.rs -sSf > rustup && chmod +x rustup" - "./rustup -y" + - "export PATH=$PATH:~/.cargo/bin" - "rustc --version" install: - rustup toolchain install ${RUST_CHANNEL} From 2d6dd312eaf4136c54442f2a7b909d9449fce17f Mon Sep 17 00:00:00 2001 From: drrb Date: Tue, 23 May 2017 08:03:19 +1000 Subject: [PATCH 13/16] Override rust version on Travis --- .travis.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index be27188..d2e66a3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,6 +8,8 @@ before_install: - "curl https://sh.rustup.rs -sSf > rustup && chmod +x rustup" - "./rustup -y" - "export PATH=$PATH:~/.cargo/bin" - - "rustc --version" + - rustc --version install: - - rustup toolchain install ${RUST_CHANNEL} + - rustup toolchain install ${RUST_CHANNEL} + - rustup override set ${RUST_CHANNEL} + - rustc --version From 052fd038f3ed071a902e2c4e0c2d46396488f8e9 Mon Sep 17 00:00:00 2001 From: drrb Date: Tue, 23 May 2017 08:09:32 +1000 Subject: [PATCH 14/16] Fixed link line numbers --- README.md | 12 ++++++------ generate-link-table | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 876a686..e6ee394 100644 --- a/README.md +++ b/README.md @@ -29,12 +29,12 @@ and the [Rust code](src/main/rust/com/github/drrb/javarust/lib/greetings.rs). Th implementation is heavily commented to explain it. So far, it contains examples of the following (click the links to see!): -- *[Arguments](src/test/java/com/github/drrb/javarust/GreetingsTest.java#L46)*: passing simple arguments from Java to Rust ([Java side](src/main/java/com/github/drrb/javarust/Greetings.java#L45) / [Rust side](src/main/rust/com/github/drrb/javarust/lib/greetings.rs#L82)) -- *[Return values](src/test/java/com/github/drrb/javarust/GreetingsTest.java#L51)*: returning simple values from Rust to Java ([Java side](src/main/java/com/github/drrb/javarust/Greetings.java#L50) / [Rust side](src/main/rust/com/github/drrb/javarust/lib/greetings.rs#L92)) -- *[Struct arguments](src/test/java/com/github/drrb/javarust/GreetingsTest.java#L57)*: passing structs to Rust from Java ([Java side](src/main/java/com/github/drrb/javarust/Greetings.java#L55) / [Rust side](src/main/rust/com/github/drrb/javarust/lib/greetings.rs#L101)) -- *[Returning structs (2 examples)](src/test/java/com/github/drrb/javarust/GreetingsTest.java#L66)*: returning structs from Rust by value and by reference ([Java side](src/main/java/com/github/drrb/javarust/Greetings.java#L72) / [Rust side](src/main/rust/com/github/drrb/javarust/lib/greetings.rs#L110)) -- *[Callbacks (3 examples)](src/test/java/com/github/drrb/javarust/GreetingsTest.java#L81)*: passing callbacks to Rust that get called from the Rust code ([Java side](src/main/java/com/github/drrb/javarust/Greetings.java#L85) / [Rust side](src/main/rust/com/github/drrb/javarust/lib/greetings.rs#L130)) -- *[Freeing memory](src/test/java/com/github/drrb/javarust/GreetingsTest.java#L68)*: freeing memory allocated in Rust ([Java side](src/main/java/com/github/drrb/javarust/Greetings.java#L115) / [Rust side](src/main/rust/com/github/drrb/javarust/lib/greetings.rs#L172)) +- *[Arguments](src/test/java/com/github/drrb/javarust/GreetingsTest.java#L45)*: passing simple arguments from Java to Rust ([Java side](src/main/java/com/github/drrb/javarust/Greetings.java#L44) / [Rust side](src/main/rust/com/github/drrb/javarust/lib/greetings.rs#L81)) +- *[Return values](src/test/java/com/github/drrb/javarust/GreetingsTest.java#L50)*: returning simple values from Rust to Java ([Java side](src/main/java/com/github/drrb/javarust/Greetings.java#L49) / [Rust side](src/main/rust/com/github/drrb/javarust/lib/greetings.rs#L91)) +- *[Struct arguments](src/test/java/com/github/drrb/javarust/GreetingsTest.java#L56)*: passing structs to Rust from Java ([Java side](src/main/java/com/github/drrb/javarust/Greetings.java#L54) / [Rust side](src/main/rust/com/github/drrb/javarust/lib/greetings.rs#L100)) +- *[Returning structs (2 examples)](src/test/java/com/github/drrb/javarust/GreetingsTest.java#L65)*: returning structs from Rust by value and by reference ([Java side](src/main/java/com/github/drrb/javarust/Greetings.java#L71) / [Rust side](src/main/rust/com/github/drrb/javarust/lib/greetings.rs#L109)) +- *[Callbacks (3 examples)](src/test/java/com/github/drrb/javarust/GreetingsTest.java#L80)*: passing callbacks to Rust that get called from the Rust code ([Java side](src/main/java/com/github/drrb/javarust/Greetings.java#L84) / [Rust side](src/main/rust/com/github/drrb/javarust/lib/greetings.rs#L129)) +- *[Freeing memory](src/test/java/com/github/drrb/javarust/GreetingsTest.java#L67)*: freeing memory allocated in Rust ([Java side](src/main/java/com/github/drrb/javarust/Greetings.java#L114) / [Rust side](src/main/rust/com/github/drrb/javarust/lib/greetings.rs#L171)) ## Building and Running the Tests diff --git a/generate-link-table b/generate-link-table index c6e55c3..f45499d 100755 --- a/generate-link-table +++ b/generate-link-table @@ -22,7 +22,7 @@ class Concept < OpenStruct private def link(text:, regex:, file:) line = find_line(regex, file) - "[#{text}](#{file}#L#{line + 1})" + "[#{text}](#{file}#L#{line})" end def find_line(regex, file) From 82fd3a22f646876c6185d1384e57a45111102cba Mon Sep 17 00:00:00 2001 From: drrb Date: Tue, 23 May 2017 08:15:22 +1000 Subject: [PATCH 15/16] Allow rust nightly to fail on Travis --- .travis.yml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index d2e66a3..18f1ab6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,9 +1,12 @@ language: java jdk: - - oraclejdk8 + - oraclejdk8 env: - - RUST_CHANNEL=stable - - RUST_CHANNEL=nightly + - RUST_CHANNEL=stable + - RUST_CHANNEL=nightly +matrix: + allow_failures: + - env: RUST_CHANNEL=nightly before_install: - "curl https://sh.rustup.rs -sSf > rustup && chmod +x rustup" - "./rustup -y" From aaf989677b1976aaf9c46117554c7da0b311d0ef Mon Sep 17 00:00:00 2001 From: drrb Date: Wed, 16 Aug 2017 18:51:33 +1000 Subject: [PATCH 16/16] Fixed dropping of GreetingSet We were incorrectly passing GreetingSet by value, which was causing problems when we tried to free it. --- .../com/github/drrb/javarust/Greeting.java | 5 ++++ .../com/github/drrb/javarust/GreetingSet.java | 28 +++++++++++++------ .../com/github/drrb/javarust/Greetings.java | 4 +-- .../com/github/drrb/javarust/lib/greetings.rs | 8 +++--- .../github/drrb/javarust/GreetingsTest.java | 4 +-- 5 files changed, 32 insertions(+), 17 deletions(-) diff --git a/src/main/java/com/github/drrb/javarust/Greeting.java b/src/main/java/com/github/drrb/javarust/Greeting.java index aa3ec61..020a80d 100644 --- a/src/main/java/com/github/drrb/javarust/Greeting.java +++ b/src/main/java/com/github/drrb/javarust/Greeting.java @@ -49,6 +49,11 @@ protected List getFieldOrder() { @Override public void close() { + // Turn off "auto-synch". If it is on, JNA will automatically read all fields + // from the struct's memory and update them on the Java object. This synchronization + // occurs after every native method call. If it occurs after we drop the struct, JNA + // will try to read from the freed memory and cause a segmentation fault. + setAutoSynch(false); // Send the struct back to rust for the memory to be freed Greetings.INSTANCE.dropGreeting(this); } diff --git a/src/main/java/com/github/drrb/javarust/GreetingSet.java b/src/main/java/com/github/drrb/javarust/GreetingSet.java index 06ed511..f793131 100644 --- a/src/main/java/com/github/drrb/javarust/GreetingSet.java +++ b/src/main/java/com/github/drrb/javarust/GreetingSet.java @@ -26,18 +26,12 @@ * A struct that contains an array of structs. This is the Java representation * of the GreetingSet struct in Rust (see the Rust code). */ -public class GreetingSet extends Structure { +public class GreetingSet extends Structure implements Closeable { public static class ByReference extends GreetingSet implements Structure.ByReference { } - public static class ByValue extends GreetingSet implements Structure.ByValue, Closeable { - - @Override - public void close() { - Greetings.INSTANCE.dropGreetingSet(this); - } - + public static class ByValue extends GreetingSet implements Structure.ByValue { } /** @@ -48,7 +42,7 @@ public void close() { * * NB: We need to explicitly specify that the field is a pointer (i.e. we need * to use ByReference) because, by default, JNA assumes that struct fields - * are not pointers (i.e. the if you just say "Greeting", JNA assumes + * are not pointers (i.e. if you just say "Greeting", JNA assumes * "Greeting.ByValue" here). */ public Greeting.ByReference greetings; @@ -88,4 +82,20 @@ public List getGreetings() { protected List getFieldOrder() { return Arrays.asList("greetings", "numberOfGreetings"); } + + /** + * Send the GreetingSet back to Rust to be dropped. + * + * We do this because JNA doesn't free the memory when the object is garbage collected. + */ + @Override + public void close() { + // Turn off "auto-synch". If it is on, JNA will automatically read all fields + // from the struct's memory and update them on the Java object. This synchronization + // occurs after every native method call. If it occurs after we drop the struct, JNA + // will try to read from the freed memory and cause a segmentation fault. + setAutoSynch(false); + // Send the struct back to rust for the memory to be freed + Greetings.INSTANCE.dropGreetingSet(this); + } } diff --git a/src/main/java/com/github/drrb/javarust/Greetings.java b/src/main/java/com/github/drrb/javarust/Greetings.java index aa65fc6..c395be5 100644 --- a/src/main/java/com/github/drrb/javarust/Greetings.java +++ b/src/main/java/com/github/drrb/javarust/Greetings.java @@ -76,7 +76,7 @@ public interface Greetings extends Library { * This is the same as returning a {@link GreetingSet.ByReference}. JNA assumes * it's by reference when it's returned from a native function. */ - GreetingSet.ByValue renderGreetings(); + GreetingSet renderGreetings(); /** * Passing a callback that will be called from Rust with individual strings @@ -116,5 +116,5 @@ interface GreetingSetCallback extends Callback { /** * Free the memory used by a GreetingSet */ - void dropGreetingSet(GreetingSet.ByValue greetingSet); + void dropGreetingSet(GreetingSet greetingSet); } diff --git a/src/main/rust/com/github/drrb/javarust/lib/greetings.rs b/src/main/rust/com/github/drrb/javarust/lib/greetings.rs index ec00661..213c7b1 100644 --- a/src/main/rust/com/github/drrb/javarust/lib/greetings.rs +++ b/src/main/rust/com/github/drrb/javarust/lib/greetings.rs @@ -158,12 +158,12 @@ pub extern fn sendGreetings(callback: extern "C" fn(&GreetingSet)) { /// Example of returning a more complicated struct from Rust #[no_mangle] #[allow(non_snake_case)] -pub extern fn renderGreetings() -> GreetingSet { +pub extern fn renderGreetings() -> Box { let greetings = vec![ Greeting::new("Hello!"), Greeting::new("Hello again!") ]; - GreetingSet { + Box::new(GreetingSet { greetings: greetings.into_boxed_slice() - } + }) } #[no_mangle] @@ -175,7 +175,7 @@ pub extern fn dropGreeting(_: Box) { #[no_mangle] #[allow(non_snake_case)] -pub extern fn dropGreetingSet(_: GreetingSet) { +pub extern fn dropGreetingSet(_: Box) { // Do nothing here. Because we own the GreetingSet here and we're not // returning it, Rust will assume we don't want it anymore and clean it up. } diff --git a/src/test/java/com/github/drrb/javarust/GreetingsTest.java b/src/test/java/com/github/drrb/javarust/GreetingsTest.java index 72edb3d..0cb09e8 100644 --- a/src/test/java/com/github/drrb/javarust/GreetingsTest.java +++ b/src/test/java/com/github/drrb/javarust/GreetingsTest.java @@ -97,7 +97,7 @@ public void apply(GreetingSet.ByReference greetingSet) { }); List greetingStrings = new LinkedList<>(); - for (Greeting greeting: greetings) { + for (Greeting greeting : greetings) { greetingStrings.add(greeting.getText()); } @@ -106,7 +106,7 @@ public void apply(GreetingSet.ByReference greetingSet) { @Test public void shouldGetAStructFromRustContainingAnArrayOfStructs() { - try (GreetingSet.ByValue result = library.renderGreetings()) { + try (GreetingSet result = library.renderGreetings()) { List greetings = new LinkedList<>(); for (Greeting greeting : result.getGreetings()) { greetings.add(greeting.getText());