From 81ede5d6be4f195865b8d39e51631a812a2c6c4b Mon Sep 17 00:00:00 2001 From: drrb Date: Mon, 22 May 2017 20:55:51 +1000 Subject: [PATCH 01/10] 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 02/10] 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 03/10] 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 04/10] 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 05/10] 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 06/10] 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 07/10] 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 08/10] 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 09/10] 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 10/10] 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());