英文:
How to pass a Java string array []String to Go via cgo/JNA
问题
我想通过JNA将Java中的String[]传递给我的Go函数。
我的Go函数具有以下签名:
func PredicateEval(keys, values []string, expression string) *C.char
我已经使用链接模式“c-shared”编译了Go库。我在Java中定义了一个_GoString_,如下所示:
package predicates;
import com.ochafik.lang.jnaerator.runtime.NativeSize;
import com.sun.jna.Pointer;
import com.sun.jna.Structure;
import java.util.Arrays;
import java.util.List;
public class _GoString_ extends Structure {
public Pointer p;
public NativeSize n;
public _GoString_() {
super();
}
protected List<String> getFieldOrder() {
return Arrays.asList("p", "n");
}
public _GoString_(Pointer p, NativeSize n) {
super();
this.p = p;
this.n = n;
}
public static class ByReference extends _GoString_ implements Structure.ByReference {
};
public static class ByValue extends _GoString_ implements Structure.ByValue {
};
}
我还定义了一个Go切片,如下所示:
package predicates;
import com.sun.jna.Pointer;
import com.sun.jna.Structure;
import java.util.Arrays;
import java.util.List;
public class GoSlice extends Structure {
public Pointer data;
public long len;
public long cap;
public GoSlice() {
super();
}
protected List<String> getFieldOrder() {
return Arrays.asList("data", "len", "cap");
}
public GoSlice(Pointer data, long len, long cap) {
super();
this.data = data;
this.len = len;
this.cap = cap;
}
public static class ByReference extends GoSlice implements Structure.ByReference {
};
public static class ByValue extends GoSlice implements Structure.ByValue {
};
}
这是我将Java的String[]转换为Go的string[]的尝试:
static {
try {
Field field = sun.misc.Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
unsafe = (sun.misc.Unsafe) field.get(null);
Class<?> clazz = ByteBuffer.allocateDirect(0).getClass();
DIRECT_BYTE_BUFFER_ADDRESS_OFFSET = unsafe.objectFieldOffset(Buffer.class.getDeclaredField("address"));
DIRECT_BYTE_BUFFER_CLASS = clazz;
} catch (Exception e) {
throw new AssertionError(e);
}
}
private static long getAddress(ByteBuffer buffer) {
assert buffer.getClass() == DIRECT_BYTE_BUFFER_CLASS;
return unsafe.getLong(buffer, DIRECT_BYTE_BUFFER_ADDRESS_OFFSET);
}
public static _GoString_.ByValue JavaStringToGo(String jstr) {
try {
byte[] bytes = jstr.getBytes("utf-8");
ByteBuffer bb = ByteBuffer.allocateDirect(bytes.length);
bb.put(bytes);
Pointer p = new Pointer(getAddress(bb));
_GoString_.ByValue value = new _GoString_.ByValue();
value.n = new NativeSize(bytes.length);
value.p = p;
return value;
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
}
public static GoSlice.ByValue JavaStringArrayToGoStringSlice(String[] strings) {
_GoString_.ByValue[] goStrings = new _GoString_.ByValue[strings.length];
for (int i = 0; i < strings.length; i++) {
goStrings[i] = JavaStringToGo(strings[i]);
}
Memory arr = new Memory(strings.length * Native.getNativeSize(_GoString_.class));
for (int i = 0; i < strings.length; i++) {
byte[] bytes = goStrings[0].getPointer().getByteArray(0, Native.getNativeSize(_GoString_.class));
arr.write(i*Native.getNativeSize(_GoString_.class), bytes, 0, bytes.length);
}
GoSlice.ByValue slice = new GoSlice.ByValue();
slice.data = arr;
slice.len = strings.length;
slice.cap = strings.length;
return slice;
}
一切都编译通过,但是当我尝试在Go端访问切片元素时,我得到了一个段错误:
unexpected fault address 0xb01dfacedebac1e
fatal error: fault
[signal SIGSEGV: segmentation violation code=0x1 addr=0xb01dfacedebac1e pc=0x10d7d3d6f]
goroutine 17 [running, locked to thread]:
英文:
I would like to pass a String[] in Java to my Go function via JNA.
My go function has the following signature:
func PredicateEval(keys, values []string, expression string) *C.char
I have compiled the go library with link mode as "c-shared". I have a GoString in java defined as:
package predicates;
import com.ochafik.lang.jnaerator.runtime.NativeSize;
import com.sun.jna.Pointer;
import com.sun.jna.Structure;
import java.util.Arrays;
import java.util.List;
/**
* <i>native declaration : coverage_server/predicate_jvm_bridge/lib/libtest.h</i><br>
* This file was autogenerated by <a href="http://jnaerator.googlecode.com/">JNAerator</a>,<br>
* a tool written by <a href="http://ochafik.com/">Olivier Chafik</a> that <a href="http://code.google.com/p/jnaerator/wiki/CreditsAndLicense">uses a few opensource projects.</a>.<br>
* For help, please visit <a href="http://nativelibs4java.googlecode.com/">NativeLibs4Java</a> , <a href="http://rococoa.dev.java.net/">Rococoa</a>, or <a href="http://jna.dev.java.net/">JNA</a>.
*/
public class _GoString_ extends Structure {
/** C type : const char* */
public Pointer p;
public NativeSize n;
public _GoString_() {
super();
}
protected List<String> getFieldOrder() {
return Arrays.asList("p", "n");
}
/** @param p C type : const char* */
public _GoString_(Pointer p, NativeSize n) {
super();
this.p = p;
this.n = n;
}
public static class ByReference extends _GoString_ implements Structure.ByReference {
};
public static class ByValue extends _GoString_ implements Structure.ByValue {
};
}
I also have a Go slice defined as:
package predicates;
import com.sun.jna.Pointer;
import com.sun.jna.Structure;
import java.util.Arrays;
import java.util.List;
/**
* <i>native declaration : coverage_server/predicate_jvm_bridge/lib/libtest.h</i><br>
* This file was autogenerated by <a href="http://jnaerator.googlecode.com/">JNAerator</a>,<br>
* a tool written by <a href="http://ochafik.com/">Olivier Chafik</a> that <a href="http://code.google.com/p/jnaerator/wiki/CreditsAndLicense">uses a few opensource projects.</a>.<br>
* For help, please visit <a href="http://nativelibs4java.googlecode.com/">NativeLibs4Java</a> , <a href="http://rococoa.dev.java.net/">Rococoa</a>, or <a href="http://jna.dev.java.net/">JNA</a>.
*/
public class GoSlice extends Structure {
/** C type : void* */
public Pointer data;
/** C type : GoInt */
public long len;
/** C type : GoInt */
public long cap;
public GoSlice() {
super();
}
protected List<String> getFieldOrder() {
return Arrays.asList("data", "len", "cap");
}
/**
* @param data C type : void*<br>
* @param len C type : GoInt<br>
* @param cap C type : GoInt
*/
public GoSlice(Pointer data, long len, long cap) {
super();
this.data = data;
this.len = len;
this.cap = cap;
}
public static class ByReference extends GoSlice implements Structure.ByReference {
};
public static class ByValue extends GoSlice implements Structure.ByValue {
};
}
This is my attempt to convert a Java []String to a Go string[].
static {
try {
Field field = sun.misc.Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
unsafe = (sun.misc.Unsafe) field.get(null);
Class<?> clazz = ByteBuffer.allocateDirect(0).getClass();
DIRECT_BYTE_BUFFER_ADDRESS_OFFSET = unsafe.objectFieldOffset(Buffer.class.getDeclaredField("address"));
DIRECT_BYTE_BUFFER_CLASS = clazz;
} catch (Exception e) {
throw new AssertionError(e);
}
}
private static long getAddress(ByteBuffer buffer) {
assert buffer.getClass() == DIRECT_BYTE_BUFFER_CLASS;
return unsafe.getLong(buffer, DIRECT_BYTE_BUFFER_ADDRESS_OFFSET);
}
public static _GoString_.ByValue JavaStringToGo(String jstr) {
try {
byte[] bytes = jstr.getBytes("utf-8");
//ByteBuffer bb = ByteBuffer.wrap(bytes);
ByteBuffer bb = ByteBuffer.allocateDirect(bytes.length);
bb.put(bytes);
Pointer p = new Pointer(getAddress(bb));
_GoString_.ByValue value = new _GoString_.ByValue();
value.n = new NativeSize(bytes.length);
value.p = p;
return value;
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
}
public static GoSlice.ByValue JavaStringArrayToGoStringSlice(String[] strings) {
_GoString_.ByValue[] goStrings = new _GoString_.ByValue[strings.length];
for (int i = 0; i < strings.length; i++) {
goStrings[i] = JavaStringToGo(strings[i]);
}
Memory arr = new Memory(strings.length * Native.getNativeSize(_GoString_.class));
for (int i = 0; i < strings.length; i++) {
System.out.println(Native.getNativeSize(_GoString_.class));
byte[] bytes = goStrings[0].getPointer().getByteArray(0, Native.getNativeSize(_GoString_.class));
arr.write(i*Native.getNativeSize(_GoString_.class), bytes, 0, bytes.length);
}
GoSlice.ByValue slice = new GoSlice.ByValue();
slice.data = arr;
slice.len = strings.length;
slice.cap = strings.length;
return slice;
}
Everything compiles, but when i try to access the slice elements on the Go side, i get a seg fault:
unexpected fault address 0xb01dfacedebac1e
fatal error: fault
[signal SIGSEGV: segmentation violation code=0x1 addr=0xb01dfacedebac1e pc=0x10d7d3d6f]
goroutine 17 [running, locked to thread]:
答案1
得分: 0
您正在失去对实际字符串内存分配的追踪(和控制)。
您的 _GoString_
的映射只包括指针的分配(4 或 8 字节)和 NativeSize
(一个 4 或 8 字节的 size_t
)。这个映射假设 Pointer
保持有效:
public class _GoString_ extends Structure {
/** C type : const char* */
public Pointer p;
public NativeSize n;
// constructors, etc.
}
然而,当您将值分配给 p
时,您只跟踪指针的地址,而不是实际的内存分配(我在您的代码中添加了注释):
public static _GoString_.ByValue JavaStringToGo(String jstr) {
try {
byte[] bytes = jstr.getBytes("utf-8");
//
// 在这里,您为字节数组分配内存
//
ByteBuffer bb = ByteBuffer.allocateDirect(bytes.length);
bb.put(bytes);
//
// 在这里,您只跟踪字节数组的指针
//
Pointer p = new Pointer(getAddress(bb));
//
// 您再也没有引用 bb,它不再可达
// 系统可以回收它的分配
//
_GoString_.ByValue value = new _GoString_.ByValue();
value.n = new NativeSize(bytes.length);
value.p = p;
return value;
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
}
作为直接的字节缓冲区,内存位置和(解)分配机制与普通对象和 GC 不同,但基本原则仍然适用,即一旦您失去对 Java 对象(ByteBuffer
)的引用,就无法控制本机内存何时被释放。(当 bb
被 GC 时,它的内部字段包括将在处理时触发释放的引用。)
一种可能的解决方案是在您的 _GoString_
类中添加一个 private
字段,该字段保持对 ByteBuffer
的强引用,并防止系统回收其内存(可能需要添加一个 ByteBuffer
构造函数)。
另一种解决方案是对字符串使用 JNA 的 Memory
类,并直接将该 Memory
对象(它扩展了 Pointer
)存储到 p
字段中。我不确定为什么您选择了直接字节缓冲区用于此应用程序,所以这可能不适用于您的用例,但它肯定会简化您的代码。
英文:
You're losing track (and control) of the memory allocation of the actual Strings.
Your mapping of _GoString_
only includes allocation of a Pointer (4 or 8 bytes) and a NativeSize
(a 4- or 8-byte size_t
). This mapping assumes the Pointer
remains valid:
public class _GoString_ extends Structure {
/** C type : const char* */
public Pointer p;
public NativeSize n;
// constructors, etc.
}
However, when you assign the value to p
you only keep track of the pointer's address and not the actual memory allocation (I've added comments to your code):
public static _GoString_.ByValue JavaStringToGo(String jstr) {
try {
byte[] bytes = jstr.getBytes("utf-8");
//
// Here you allocate memory for the bytes
//
ByteBuffer bb = ByteBuffer.allocateDirect(bytes.length);
bb.put(bytes);
//
// Here you only keep track of the pointer to the bytes
//
Pointer p = new Pointer(getAddress(bb));
//
// You never reference bb again, it is no longer reachable
// and its allocation can be reclaimed by the system
//
_GoString_.ByValue value = new _GoString_.ByValue();
value.n = new NativeSize(bytes.length);
value.p = p;
return value;
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
}
As a direct bytebuffer, the memory location and (de)allocation mechanism are different than normal objects and GC, but the fundamental principle applies that once you've lost reference to the Java object (the ByteBuffer
) you have no control over when that native memory will be freed. (When bb
is GC'd, its internal fields include the reference that will trigger the deallocation when it is processed.)
One possible solution is to add a private
field to your _GoString_
class that maintains a strong reference to the ByteBuffer
and prevents the system from reclaiming its memory (possibly adding a ByteBuffer
constrcutor).
Another solution is to use JNA's Memory
class for the Strings and directly store that Memory
object (which extends Pointer
) to the p
field. I'm not sure why you have chosen direct byte buffers for this application, so this may not apply to your use case but it would certainly simplify your code.
专注分享java语言的经验与见解,让所有开发者获益!
评论