Java调用Go原生库返回Map遇JNA类型不支持问题求助
解决JNA调用Go函数返回Map到Java的问题
首先得理清两个核心问题:
- Go端的代码错误:你当前的Go函数声明返回
string,但最后却返回了map[string]string,这在Go里根本编译不过,得先修正这个问题。 - JNA的类型限制:JNA是基于C语言调用机制的,它只能处理和C兼容的原生类型(比如字符串、整数、结构体),Java的
Map不是C原生类型,所以直接声明返回Map<String,String>肯定会报错——JNA完全不知道该怎么把Go返回的数据转换成Java的Map。
下面给你两种可行的解决方案,优先推荐第一种,因为实现起来更简单通用:
方案一:通过JSON序列化传递数据
这个思路是把Go的map转换成JSON字符串返回,Java端拿到字符串后再反序列成Map,这是跨语言传递复杂数据最常用的方式。
1. 修正Go代码
要把map序列化成JSON,并且正确返回C兼容的字符串(CGO要求导出函数返回*C.char类型):
package main import ( "C" "encoding/json" "fmt" "github.com/ip2location/ip2location-go" "unsafe" ) func main(){} //export GetGeoLoc func GetGeoLoc(ip *C.char) *C.char { // 把C字符串转成Go字符串 goIp := C.GoString(ip) fmt.Println("input:" , goIp) ip2location.Open("/data/md0/ip2loc/ip6/IPV6-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE-ZIPCODE-TIMEZONE-ISP-DOMAIN-NETSPEED-AREACODE-WEATHER-MOBILE-ELEVATION-USAGETYPE.BIN") defer ip2location.Close() // 用defer确保资源关闭 results := ip2location.Get_all(goIp) m := map[string]string{ "country_short": results.Country_short, "country_long": results.Country_long, "region": results.Region, "city": results.City, "isp": results.Isp, "latitude": fmt.Sprintf("%f", results.Latitude), "longitude": fmt.Sprintf("%f", results.Longitude), "domain": results.Domain, "zipcode": results.Zipcode, "timezone": results.Timezone, "netspeed": results.Netspeed, "iddcode": results.Iddcode, "areacode": results.Areacode, "weatherstationcode": results.Weatherstationcode, "weatherstationaname": results.Weatherstationname, "mcc": results.Mcc, "mnc": results.Mnc, "mobilebrand": results.Mobilebrand, "elevation": fmt.Sprintf("%f", results.Elevation), "usagetype": results.Usagetype, } fmt.Println("map:", m) // 把map序列化成JSON字节流 jsonBytes, err := json.Marshal(m) if err != nil { fmt.Println("json marshal error:", err) return C.CString("") } // 把JSON字节流转成C字符串返回 return C.CString(string(jsonBytes)) } //export FreeCString // 导出函数释放C字符串内存,避免内存泄漏 func FreeCString(str *C.char) { C.free(unsafe.Pointer(str)) }
关键修改说明:
- 函数参数和返回值改成C兼容的
*C.char类型 - 使用
C.GoString和C.CString完成Go字符串与C字符串的转换 - 添加
defer ip2location.Close()确保资源正确释放 - 新增
FreeCString函数,用来释放Go端分配的C字符串内存
编译Go库的命令:
go build -buildmode=c-shared -o iploc.so .
2. 修改Java代码
把JNA接口里的GetGeoLoc返回类型改成String,然后用JSON库(比如Jackson或Gson)把JSON字符串转成Map<String,String>:
以Jackson为例,先添加Maven依赖:
<dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.15.2</version> </dependency>
然后修改Java代码:
import com.fasterxml.jackson.databind.ObjectMapper; import com.sun.jna.Library; import com.sun.jna.Native; import java.util.Map; public class GetGeoLoc2Tests { static GoCaller GO_CQM_TRANSFORMER; private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); public interface GoCaller extends Library { String GetGeoLoc(String ip); void FreeCString(String str); int Add(int i); String SString(String s); } public static void getGeoLoc2(String ip) { if (ip == null) { return; } System.out.println("JAVA----->" + ip); String jsonStr = GO_CQM_TRANSFORMER.GetGeoLoc(ip); try { // 把JSON字符串转成Map Map<String, String> map = OBJECT_MAPPER.readValue(jsonStr, Map.class); System.out.println(map); } catch (Exception e) { e.printStackTrace(); } finally { // 释放Go端分配的内存 GO_CQM_TRANSFORMER.FreeCString(jsonStr); } } public static void main(String[] args) { if(args.length == 0) { System.out.println("Expecting an input<IpAddress>"); System.exit(0); } String pwd = System.getProperty("user.home"); String lib = pwd + "/go/src/ip2loc/iploc.so"; GO_CQM_TRANSFORMER = Native.loadLibrary(lib, GoCaller.class); getGeoLoc2(args[0]); } }
这里简化了GoString结构体,因为JNA可以直接把Java的String映射成C的char*,不需要手动处理长度,JNA会自动帮你做转换。
方案二:使用C兼容结构体传递数据
如果对性能要求很高,不想用JSON序列化的开销,可以定义C兼容的结构体,Go端把数据填充到结构体里返回,Java端对应定义Structure来映射。
1. Go代码定义结构体
package main import ( "C" "fmt" "github.com/ip2location/ip2location-go" "unsafe" ) // 定义C兼容的结构体,字段名要和Java端对应 type GeoLocResult struct { CountryShort *C.char CountryLong *C.char Region *C.char City *C.char Isp *C.char Latitude *C.char Longitude *C.char Domain *C.char Zipcode *C.char Timezone *C.char Netspeed *C.char Iddcode *C.char Areacode *C.char WeatherStationCode *C.char WeatherStationName *C.char Mcc *C.char Mnc *C.char MobileBrand *C.char Elevation *C.char UsageType *C.char } func main(){} //export GetGeoLoc func GetGeoLoc(ip *C.char) *GeoLocResult { goIp := C.GoString(ip) fmt.Println("input:" , goIp) ip2location.Open("/data/md0/ip2loc/ip6/IPV6-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE-ZIPCODE-TIMEZONE-ISP-DOMAIN-NETSPEED-AREACODE-WEATHER-MOBILE-ELEVATION-USAGETYPE.BIN") defer ip2location.Close() results := ip2location.Get_all(goIp) res := &GeoLocResult{ CountryShort: C.CString(results.Country_short), CountryLong: C.CString(results.Country_long), Region: C.CString(results.Region), City: C.CString(results.City), Isp: C.CString(results.Isp), Latitude: C.CString(fmt.Sprintf("%f", results.Latitude)), Longitude: C.CString(fmt.Sprintf("%f", results.Longitude)), Domain: C.CString(results.Domain), Zipcode: C.CString(results.Zipcode), Timezone: C.CString(results.Timezone), Netspeed: C.CString(results.Netspeed), Iddcode: C.CString(results.Iddcode), Areacode: C.CString(results.Areacode), WeatherStationCode: C.CString(results.Weatherstationcode), WeatherStationName: C.CString(results.Weatherstationname), Mcc: C.CString(results.Mcc), Mnc: C.CString(results.Mnc), MobileBrand: C.CString(results.Mobilebrand), Elevation: C.CString(fmt.Sprintf("%f", results.Elevation)), UsageType: C.CString(results.Usagetype), } return res } //export FreeGeoLocResult // 导出函数释放结构体及内部字符串的内存 func FreeGeoLocResult(res *GeoLocResult) { C.free(unsafe.Pointer(res.CountryShort)) C.free(unsafe.Pointer(res.CountryLong)) C.free(unsafe.Pointer(res.Region)) C.free(unsafe.Pointer(res.City)) C.free(unsafe.Pointer(res.Isp)) C.free(unsafe.Pointer(res.Latitude)) C.free(unsafe.Pointer(res.Longitude)) C.free(unsafe.Pointer(res.Domain)) C.free(unsafe.Pointer(res.Zipcode)) C.free(unsafe.Pointer(res.Timezone)) C.free(unsafe.Pointer(res.Netspeed)) C.free(unsafe.Pointer(res.Iddcode)) C.free(unsafe.Pointer(res.Areacode)) C.free(unsafe.Pointer(res.WeatherStationCode)) C.free(unsafe.Pointer(res.WeatherStationName)) C.free(unsafe.Pointer(res.Mcc)) C.free(unsafe.Pointer(res.Mnc)) C.free(unsafe.Pointer(res.MobileBrand)) C.free(unsafe.Pointer(res.Elevation)) C.free(unsafe.Pointer(res.UsageType)) C.free(unsafe.Pointer(res)) }
注意:必须导出FreeGeoLocResult函数,因为Go用C.CString分配的内存需要手动释放,否则会导致内存泄漏。
2. Java代码定义对应的Structure
import com.sun.jna.Library; import com.sun.jna.Native; import com.sun.jna.Pointer; import com.sun.jna.Structure; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; public class GetGeoLoc2Tests { static GoCaller GO_CQM_TRANSFORMER; public interface GoCaller extends Library { GeoLocResult GetGeoLoc(String ip); void FreeGeoLocResult(Pointer res); int Add(int i); String SString(String s); } public static class GeoLocResult extends Structure { public String countryShort; public String countryLong; public String region; public String city; public String isp; public String latitude; public String longitude; public String domain; public String zipcode; public String timezone; public String netspeed; public String iddcode; public String areacode; public String weatherStationCode; public String weatherStationName; public String mcc; public String mnc; public String mobileBrand; public String elevation; public String usageType; @Override protected List<String> getFieldOrder() { return Arrays.asList( "countryShort", "countryLong", "region", "city", "isp", "latitude", "longitude", "domain", "zipcode", "timezone", "netspeed", "iddcode", "areacode", "weatherStationCode", "weatherStationName", "mcc", "mnc", "mobileBrand", "elevation", "usageType" ); } } public static void getGeoLoc2(String ip) { if (ip == null) { return; } System.out.println("JAVA----->" + ip); GeoLocResult result = GO_CQM_TRANSFORMER.GetGeoLoc(ip); // 把结构体转成Map Map<String, String> map = new HashMap<>(); map.put("country_short", result.countryShort); map.put("country_long", result.countryLong); map.put("region", result.region); map.put("city", result.city); map.put("isp", result.isp); map.put("latitude", result.latitude); map.put("longitude", result.longitude); map.put("domain", result.domain); map.put("zipcode", result.zipcode); map.put("timezone", result.timezone); map.put("netspeed", result.netspeed); map.put("iddcode", result.iddcode); map.put("areacode", result.areacode); map.put("weatherstationcode", result.weatherStationCode); map.put("weatherstationaname", result.weatherStationName); map.put("mcc", result.mcc); map.put("mnc", result.mnc); map.put("mobilebrand", result.mobileBrand); map.put("elevation", result.elevation); map.put("usagetype", result.usageType); System.out.println(map); // 必须调用Free函数释放内存 GO_CQM_TRANSFORMER.FreeGeoLocResult(result.getPointer()); } public static void main(String[] args) { if(args.length == 0) { System.out.println("Expecting an input<IpAddress>"); System.exit(0); } String pwd = System.getProperty("user.home"); String lib = pwd + "/go/src/ip2loc/iploc.so"; GO_CQM_TRANSFORMER = Native.loadLibrary(lib, GoCaller.class); getGeoLoc2(args[0]); } }
这个方案需要手动处理内存释放,否则会有内存泄漏风险,适合对性能要求高的场景。
总结
- 核心问题是JNA不支持直接把Go的map转换成Java的Map,因为两者都不是C原生类型,必须通过C兼容的类型(字符串或结构体)传递。
- 优先推荐JSON方案,实现简单,不需要手动处理内存和结构体映射,适合大多数场景。
- 结构体方案性能更高,但需要维护两边的结构体定义,还要手动管理内存,适合性能敏感的场景。
内容的提问来源于stack exchange,提问作者srikanth
相关产品推荐
相关产品推荐

