You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Java调用Go原生库返回Map遇JNA类型不支持问题求助

解决JNA调用Go函数返回Map到Java的问题

首先得理清两个核心问题:

  1. Go端的代码错误:你当前的Go函数声明返回string,但最后却返回了map[string]string,这在Go里根本编译不过,得先修正这个问题。
  2. 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-----&gt;" + 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-----&gt;" + 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.12 04:57:10