资讯专栏INFORMATION COLUMN

Head First JNA

YPHP / 3053人阅读

摘要:与动态链接库配套的,会有相应的头文件,来声明动态链接库中对外暴露的方法。结构体映射结构体映射类编写类,继承,表示这个一个结构体。声明字段与,并且设置访问属性为。计算机状态结构体结构体指针结构体具体的值至此,功能完成。

问题描述

虚拟化项目,需要用到Java调用原生代码的技术,我们使用的是开源库JNA(Java Native Access)。

Native(C/C++)代码,编译生成动态链接库Dynamic-link library

Windows下常见的.dll文件。这是我们项目中用到的动态链接库。

而在unix环境下,为.so文件。这是百度地图的动态链接库。

与动态链接库配套的,会有相应的头文件,来声明动态链接库中对外暴露的方法。

百度地图是直接封装好,给了.so,但是不给头文件,直接把写好的jar包给你,直接调用就行。

之前也是用过百度地图的SDK,现在自己手写代码调用动态链接库才明白,原来之前用的都是别人封装好的,如今自己参照头文件手写,感觉理解还是深刻了不少。

入门 待解决的问题

我们使用JNA,主要是去调用动态链接库中已经实现的方法,所以要解决的问题就是:如何在Java代码中调用动态链接库的方法?

打开头文件,这个方法要求传输的数据是指针,而Java是没有指针的,另一个问题:Java数据类型与C/C++的数据类型如何映射?

方法映射

打开JNA的官方README,点击Getting Started

直接看代码,里面的sample,入门足够了。

定义接口,继承Library

定义该接口的一个实例,加载动态链接库。

在接口中声明方法,该方法需要与原生代码方法声明一致。

然后该方法就映射到了原生的方法,我们只需调用该接口中的方法,即可调用到原生的对应方法。

// This is the standard, stable way of mapping, which supports extensive
// customization and mapping of Java to native types.

public interface CLibrary extends Library {
    CLibrary INSTANCE = (CLibrary)
        Native.load((Platform.isWindows() ? "msvcrt" : "c"),
                            CLibrary.class);

    void printf(String format, Object... args);
}
类型映射 默认类型映射

这是官方README中给的类型映射。

学习与实践的区别,看着这个表格感觉挺简单的,实际用起来真难。

结构体映射

结构体PSA_HOST

typedef struct {
  char   name[33];
  DWORD  context;
} PSA_HOST;

映射类HostStruct

编写类HostStruct,继承Structure,表示这个一个结构体。

声明字段namecontext并且设置访问属性为public

重写getFieldOrder方法,表示本类中各字段以何顺序映射原生结构体。

/**
 * @author zhangxishuo on 2019-02-16
 * 结构体 PSA_HOST
 */
public class HostStruct extends Structure {

    public byte[] name = new byte[33];

    public int context;

    @Override
    protected List getFieldOrder() {
        return Arrays.asList("name", "context");
    }
}

注意

const char *才能映射为String类型。

char *只能映射为byte数组,然后使用Native.toString()方法将byte数组转换为String

方法映射
typedef PSA_STATUS (*LPFN_PSA_ShutdownHost)( PSA_LOGON_HANDLE *handle, IN PSA_HOST *psa_host );

参数中需要PSA_HOST结构体的指针。

参考了好多篇文章,最常用的就是下面这种写法。写静态内部类,内部类实现ByReferenceByValue接口,分别表示映射指针,与映射值。

/**
 * @author zhangxishuo on 2019-02-16
 * 结构体 PSA_HOST
 */
public class HostStruct extends Structure {

    /**
     * 结构体指针
     */
    public static class ByReference extends HostStruct implements Structure.ByReference {
    }

    /**
     * 结构体具体的值
     */
    public static class ByValue extends HostStruct implements Structure.ByValue {
    }

    public byte[] name = new byte[33];

    public int context;

    @Override
    protected List getFieldOrder() {
        return Arrays.asList("name", "context");
    }
}

映射

/**
 * 关闭计算机
 * @param pointerByReference 认证pointer
 * @param host               主机指针
 * @return 参见枚举类PsaStatus
 */
NativeLong _PSA_ShutdownHost(PointerByReference pointerByReference,
                            HostStruct.ByReference host);
复杂类型

打起精神,重点来了!

开发过程中,有这样一种复杂的数据结构,嵌套关系比较复杂。

typedef struct {
  union {
    DWORD flags;
    struct {
      DWORD rev:23;
      DWORD copy_status:3;
      DWORD timeout:1;
      DWORD disconnect:1;
      DWORD rev1:1;
      DWORD os_logoned:1;
      DWORD logoned:1;
      DWORD online:1;
    };
  };
} PSA_HOST_STATUS_FLAGS;

知识不用就忘,谁还记得C语言里的联合是啥?

Too young
struct {
  DWORD rev:23;
  DWORD copy_status:3;
  DWORD timeout:1;
  DWORD disconnect:1;
  DWORD rev1:1;
  DWORD os_logoned:1;
  DWORD logoned:1;
  DWORD online:1;
};

DWORD就是int,先映射里面的结构体。

把这些属性一写,然后再重写getFieldOrder方法。

/**
 * @author zhangxishuo on 2019-02-26
 * 计算机状态结构体
 */
public class HostStatusStruct extends Structure {

    /**
     * 结构体指针
     */
    public static class ByReference extends HostStatusStruct implements Structure.ByReference {
    }

    /**
     * 结构体具体的值
     */
    public static class ByValue extends HostStatusStruct implements Structure.ByValue {
    }

    public int rev;

    public int copy_status;

    public int timeout;

    public int disconnect;

    public int rev1;

    public int os_logoned;

    public int logoned;

    public int online;

    @Override
    protected List getFieldOrder() {
        return Arrays.asList("rev", "copy_status", "timeout", "disconnect", "rev1", "os_logoned", "logoned", "online");
    }
}
union {
  DWORD flags;
  struct {
    DWORD rev:23;
    DWORD copy_status:3;
    DWORD timeout:1;
    DWORD disconnect:1;
    DWORD rev1:1;
    DWORD os_logoned:1;
    DWORD logoned:1;
    DWORD online:1;
  };
};

然后再映射联合,编写一个类继承Union,该类即映射到联合。

/**
 * 联合
 */
public static class UNION extends Union {
    public int flags;
    public HostStatusStruct hostStatusStruct;
}

最后映射最外层的PSA_HOST_STATUS_FLAGS

/**
 * @author zhangxishuo on 2019-02-26
 * 结构体 PSA_HOST_STATUS_FLAGS
 */
public class HostStatusFlagsStruct extends Structure {

    /**
     * 联合
     */
    public static class UNION extends Union {
        public int flags;
        public HostStatusStruct hostStatusStruct;
    }

    /**
     * 结构体指针
     */
    public static class ByReference extends HostStatusFlagsStruct implements Structure.ByReference {
    }

    /**
     * 结构体具体的值
     */
    public static class ByValue extends HostStatusFlagsStruct implements Structure.ByValue {
    }

    public UNION union;

    @Override
    protected List getFieldOrder() {
        return Collections.singletonList("union");
    }
}

看上去好像没什么毛病,一切都这么简单吗?当然不是。

-1073741824

一调用,就炸了。

API文档的声明,flags应该是没什么具体含义的,而结构体中应该是我们想要获取的信息。

那为什么flags有数,而结构体中却没有值呢?

联合
struct TEST_STRUCT {
    int a,
    int b
};

union TEST_UNION {
    int a,
    int b
};

声明映射类型

重写read方法,当读取数据时,设置联合的类型为结构体类型。

@Override
public void read() {
    super.read();
    union.setType(HostStatusStruct.class);
    union.read();
}
怪事

当时这个问题把我愁坏了,捯饬了一整天才学明白。

数字

一直是这个数字:-1073741824,这个数字是不是有什么问题?

-1073741824转换为32机的二进制表示:

1100 0000 0000 0000 0000 0000 0000 0000

1073741824230次方。

问题

问题还是出在这个上:

struct {
  DWORD rev:23;
  DWORD copy_status:3;
  DWORD timeout:1;
  DWORD disconnect:1;
  DWORD rev1:1;
  DWORD os_logoned:1;
  DWORD logoned:1;
  DWORD online:1;
};

虽然DWORD就映射为int,但这里不是简单的映射:

rev:23表示rev32位。

copy_status:3表示copy_status3位。

timeout:1表示timeout1位。

内存是倒着存的,所以数据应该是这样。

映射

原理知道了,那怎么映射呢?怎么映射一个一位的内容呢?

StackOverflow上一老哥给出了解决方案,先写个int把所有的都映射过来,然后我想要第几位再从里面扒。

/**
 * @author zhangxishuo on 2019-02-26
 * 计算机状态结构体
 */
public class HostStatusStruct extends Structure {

    /**
     * 结构体指针
     */
    public static class ByReference extends HostStatusStruct implements Structure.ByReference {
    }

    /**
     * 结构体具体的值
     */
    public static class ByValue extends HostStatusStruct implements Structure.ByValue {
    }

    public int value;

    public int getRev() {
        return value & 0x3FFFFF;
    }

    public int getCopyStatus() {
        return (value >> 23) & 0x3;
    }

    public int getTimeout() {
        return (value >> 26) & 0x1;
    }

    public int getDisconnect() {
        return (value >> 27) & 0x1;
    }

    public int getRev1() {
        return (value >> 28) & 0x1;
    }

    public int getOsLogoned() {
        return (value >> 29) & 0x1;
    }

    public int getLogoned() {
        return (value >> 30) & 0x1;
    }

    public int getOnline() {
        return (value >> 31) & 0x1;
    }

    @Override
    protected List getFieldOrder() {
        return Collections.singletonList("value");
    }
}

至此,功能完成。

总结

又过去了忙碌的一周,很高兴我们的新项目已经完成大半。

感谢潘佳琦与李宜衡在本项目中的支持,第一次用Angular,好多地方我也不懂,我先学着,然后设计一套架构付诸实践,潘佳琦与李宜衡也都能遵从我制定的规范。

起初,我也提出了许多错误的规范,但当我用着用着发现原来那套不行的时候,及时改正,修改架构再重新设计,潘佳琦与李宜衡也在前台经历了大约三次的代码重构。

前台架构变更多次,感觉最后的设计还让人满意,也能让他人快速理解这种设计理念。

争取下一个项目,不使用ng-alain,自己从头到尾搭建一个项目骨架。

最后表扬一下潘佳琦,上周基本我有一半的时间都在上课,我能做的就是前一天晚上把任务建好,然后写一些基础代码或示例代码,然后给潘佳琦讲,再让他去写。

潘佳琦效率还是很高的,我记得周一的时候建了一堆任务,我想怎么着也得写两天吧,当我上课回来,发现“当当当”,潘佳琦都给写完了,代码也十分的规范。

对小组员的开发效率在心中也有了一个重新的定位。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/73494.html

相关文章

  • Elastic Search搜索引擎在SpringBoot中的实践

    摘要:注本文首发于公众号,可长按或扫描下面的小心心来订阅实验环境版本版本首先当然需要安装好环境,最好再安装上可视化插件来便于我们直观地查看数据。 showImg(https://segmentfault.com/img/remote/1460000015723674); 注: 本文首发于 My 公众号 CodeSheep ,可 长按 或 扫描 下面的 小心心 来订阅 ↓ ↓ ↓ showI...

    Me_Kun 评论0 收藏0
  • Java 外部函数接口:JNI, JNA, JNR

    摘要:我们知道,发起函数调用,需要构造一个栈帧。构造栈帧的具体实现细节的选择,被称为调用惯例。要想完成这个函数调用逻辑,就要运行时构造栈帧,生成参数压栈和清理堆栈的工作。目前,几乎支持全部常见的架构。 原文:http://nullwy.me/2018/01/java...如果觉得我的文章对你有用,请随意赞赏 遇到的问题 前段时间开发的时候,遇到一个问题,就是如何用 Java 实现 chdir...

    pubdreamcc 评论0 收藏0
  • 一个简单的JNA使用例子

    摘要:提供了这个技术来实现调用和程序,但实现起来比较麻烦,所以后来公司在的基础上实现了一个框架使用这个框架可以减轻程序员的负担,使得调用和容易很多。 使用JAVA语言开发程序比较高效,但有时对于一些性能要求高的系统,核心功能可能是用C或者C++语言编写的,这时需要用到JAVA的跨语言调用功能。JAVA提供了JNI这个技术来实现调用C和C++程序,但JNI实现起来比较麻烦,所以后来SUN公司在...

    winterdawn 评论0 收藏0
  • Java调用dll文件

    摘要:目录创建创建项目与工具项目与工具步骤与代码步骤与代码使用调用使用调用项目与工具项目与工具步骤与代码步骤与代码实际效果实际效果参考链接参考链接创建项目与工具步骤与代码使用创建动态链接库项目设置项目名与项目 目录 1 C++创建dll 1.1 项目与工具 1.2 步骤与代码 2 Java使用JN...

    Jeff 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<