【问题标题】:How do you deallocate memory in java (using jna) from a C String returned from cgo?如何从 cgo 返回的 C 字符串中释放 java 中的内存(使用 jna)?
【发布时间】:2021-05-23 09:55:16
【问题描述】:

Golang


import "C"

//export MyFunction
func MyFunction(str *C.char) *C.char {
    goStr := C.GoString(str)
    msg := goStr + " world"
    return C.CString(msg)
}

func main() {}

Java


public interface MyLib extends Library {

    MyLib INSTANCE = Native.load("mylib.so", MyLib.class);

    String MyFunction(String str);

}

public class CGoExample {

    public static void main(String[] args) {
        String str = "Hello";
        String msg = MyLib.INSTANCE.MyFunction(str);
        System.out.println(msg);
    }

}

这段代码有效,Java 字符串被传递给 Go,Go 返回一个新字符串。我遇到的问题是 Go 返回的 CString 不会被 Java 或 Go 垃圾收集器释放。 CGO 文档指出“调用者有责任安排它被释放,例如通过调用 C.free”,当然在我的情况下调用 C.free 将不起作用,因为我正在返回 CString 并且如果我释放返回之前的 CString 我没有从 Go 函数得到正确的响应。我也尝试调用defer C.free(unsafe.Pointer(msg)),但我发现defer 语句将在return 语句之前被调用。

由于我需要返回 CString,我假设它的内存需要从 java 中释放,我该如何做呢?

编辑:

根据 DanielWiddis 的评论,我尝试了以下方法,但 java 进程存在,代码为 -1073741819。

// #include <stdlib.h>
import "C"

import "unsafe"

//export MyFunction
func MyFunction(str *C.char) *C.char {
    goStr := C.GoString(str)
    msg := goStr + " world"
    return C.CString(msg)
}

//export Free
func Free(str *C.char){
    C.free(unsafe.Pointer(str))
}

func main() {}
public interface MyLib extends Library {

    MyLib INSTANCE = Native.load("mylib.so", MyLib.class);

    String MyFunction(String str);

    void Free(String str);

}

public class CGoExample {

    public static void main(String[] args) {
        String str = "Hello";
        String msg = MyLib.INSTANCE.MyFunction(str);
        System.out.println(msg);
        MyLib.INSTANCE.Free(msg);
    }

}

【问题讨论】:

  • 在旧版本的 Java 中,我可能会为此使用 finalize() 方法。但是我认为finalize() 现在已弃用,因此您需要做一些额外的工作。或者至少为 finalize() 被完全删除的那一天做好准备,如果你还不想做这项工作的话。
  • 你能不能把C.free 映射为接口中的另一个函数/方法并从Java 调用它?几乎所有其他 API 都是这样做的。
  • 理论上,CString 确实会为libc 提供的malloc() 提供内存,cgo 安排与生成的可执行映像链接; C.free 是同一个libcfree() 的(几乎)一对一映射。因此,技术上您可以在该内存上调用free(),但这必须是相同free()——在某种意义上它必须是由同样libc。我不知道你是否可以用 JNA 保证这一点。如果没有,我同意 Daniel Widdis 的建议。
  • 另一种选择是注意到C.CString(s) 没什么特别的(调用malloc() 来获得一块长度为len(s)+1 的内存,复制s 的字节并写入NUL 最后)并向 C 端提供您自己的“分配”和“解除分配”函数,它们的工作方式与 malloc()free() 一样,并将由 JNA 部分和 Go 部分共享。然后,Go 部分可以对该分配器函数进行薄包装,该函数将执行 C.CString 所做的事情:调用分配器,复制内存,写入 NUL。

标签: java go jna cgo


【解决方案1】:

C.CString()的返回类型是一个指针:*C.char

通过将其直接映射到 Java 端的 String,您将失去对该本地指针的跟踪,从而无法在以后跟踪和释放它。

最直接的解决方案是将其映射到 Java 端的 Pointer。在界面中:

Pointer MyFunction(String str);

void Free(Pointer str);

然后使用它:

Pointer pMsg = MyLib.INSTANCE.MyFunction(str);
System.out.println(pMsg.getString(0));
MyLib.INSTANCE.Free(pMsg);

在这种情况下,byte[] (8 位)字符数组也可以工作。就个人而言,我可能会使用类型安全指针和一些面向对象的辅助方法,例如:

class CStringPtr extends PointerType {
    public CStringPtr(String str) {
        super(MyLib.INSTANCE.MyFunction(str));
    }

    public String getString() {
        return this.getPointer().getString(0);
    }

    public void free() {
        MyLib.INSTANCE.Free(this.getPointer());
    }
}

这将使您的代码:

CStringPtr pMsg = new CStringPtr(str);
System.out.println(pMsg.getString());
pMsg.free();

【讨论】:

  • 感谢您的帮助!我将把我的答案标记为正确答案,因为它是一个完整的代码解决方案,我觉得大多数人会发现它更有用,尽管你给出了一个很好的答案,这对我很有帮助,所以我会投票!
  • @D0M1N0。接受您自己更完整的答案是完全可以的,但是请参阅我的 cmets 以改进它。我认为您在释放内存方面有点激进。而且我认为面向对象的方法在向用户隐藏细节方面具有一定的价值。
【解决方案2】:

基于 DanielWiddis 的 cmets,我提出了以下解决方案,该解决方案成功地为所有相关对象释放内存。

package main

// #include <stdlib.h>
import "C"

import (
    "unsafe"
)

//export MyFunction
func MyFunction(cStr *C.char) *C.char {
    str := C.GoString(cStr)
    msg := str + " world"
    return C.CString(msg)
}

//export Free
func Free(str *C.char){
    C.free(unsafe.Pointer(str))
}

func main() {}
public interface MyLib extends Library {

    MyLib INSTANCE = Native.load("mylib.so", MyLib.class);

    Pointer MyFunction(Pointer ptr);

    void Free(Pointer ptr);

}
public class CGoExample {

    public static void main(String[] args) {

        // Create pointer with a String value
        String str = "Hello";
        Pointer ptr = new Memory(str.length() + 1);
        ptr.clear();
        ptr.setString(0, str);

        //Pass the pointer to Go function which returns the pointer of the message
        Pointer resultPtr = MyLib.INSTANCE.MyFunction(ptr);

        //Get the message String from the pointer
        String msg = resultPtr.getString(0);

        //Deallocate the memory for the message
        MyLib.INSTANCE.Free(resultPtr);

        System.out.println(msg);
    }

}

【讨论】:

  • 当您使用Memory 类在Java 端分配内存时使用Native.free() 不是一个好主意(当该Java 对象被GC 时释放内存)。我怀疑如果您运行程序足够长的时间以触发该Memory 对象上的GC,您将从双重释放中获得无效的内存访问或段错误等。此外,看起来您在使用 getString() 读取它之前释放了本机内存,这也很危险。
  • 感谢您的评论!我删除了Native.free(),但在释放它之前我已经从resultPtr 中读取了字符串值,因此没有对此进行任何更改。
  • 快到了!您需要的最后一个更改:实例化 new Memory() 不会清除该内存,因此不能保证最后一个字节为空,并且您的 MyFunction 可能导致读取未分配的内存和未定义的结果。在设置字符串之前,您应该ptr.clear();ptr.setByte(str.length(), (byte) 0);
  • 再次感谢,我已编辑答案添加ptr.clear(),现在标记为正确!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-01-10
  • 1970-01-01
  • 1970-01-01
  • 2015-07-07
  • 1970-01-01
相关资源
最近更新 更多