Mac设备访问可在C中使用,但Java / JNA中的等效代码则不能

时间:2020-04-09 10:11:36

标签: java macos jna

我们使用连接到USB上Mac上的串行设备,需要配置DTR / RTS线路设置。从技术上讲,这涉及到open(3)ioctl(3)的使用。

我们用C语言实现了这一点,并且有效。下面是一个非常简化的代码段,显示了核心部分。

然后,我们将代码迁移到Java / JNA,并遇到了移植的代码起作用的问题,尽管它基本上是C代码的逐行转换。

问题是我们在哪里出错?

Java失败的症状是从对errno=25 'Inappropriate ioctl for device'的调用返回了ioctl()。由于它在C语言中有效,因此我们似乎在JNA中确实做错了。

我们做了什么:

  • 检查标头常量中的值。请注意,C代码会生成在Java代码中使用的与Java兼容的常量定义。
  • 检查了ioctl()的签名。根据手册页看似正确,并包含文件。
  • 猜测问题是TIOCMSET的ioctl代码未正确传递,因为它为负数。

我们使用JNA 5.5.0。

这是C代码。该代码段仅读取行的设置,然后将它们未经修改地写回以进行演示。这是代码(请注意硬编码的设备名称)。

int main(int argc, char **argv)
{
    // Print constant values.
    printf( "long TIOCMGET = 0x%x;\n", TIOCMGET );
    printf( "long TIOCMSET = 0x%x;\n", TIOCMSET );
    printf( "int O_RDWR = 0x%x;\n", O_RDWR );
    printf( "int O_NDELAY = 0x%x;\n", O_NDELAY );
    printf( "int O_NOCTTY = 0x%x;\n", O_NOCTTY );

    int value = O_RDWR|O_NDELAY|O_NOCTTY;
    printf( "value=%x\n", value );
    int portfd = open("/dev/tty.usbmodem735ae091", value);
    printf( "portfd=%d\n", portfd );

    int lineStatus;
    printf( "TIOCMGET %x\n", TIOCMGET );
    int rc = ioctl( portfd, TIOCMGET, &lineStatus );
    printf( "rc=%d, linestatus=%x\n", rc, lineStatus );

    rc = ioctl( portfd, TIOCMSET, &lineStatus );
    printf( "rc=%d, linestatus=%x\n", rc, lineStatus );

    if ( rc == -1 )
        printf( "Failure\n" );
    else
        printf( "Success\n" );

    if ( portfd != -1 )
        close( portfd );

    return 0;
}

上面的输出是:

long TIOCMGET = 0x4004746a;
long TIOCMSET = 0x8004746d;
int O_RDWR = 0x2;
int O_NDELAY = 0x4;
int O_NOCTTY = 0x20000;
value=20006
portfd=3
TIOCMGET 4004746a
rc=0, linestatus=6
rc=0, linestatus=6
Success

这是Java实现:

public class Cli
{
    /**
     * Java mapping for lib c
     */
    public interface MacCl extends Library {
        String NAME = "c";
        MacCl INSTANCE = Native.load(NAME, MacCl.class);

        int open(String pathname, int flags);
        int close(int fd);
        int ioctl(int fd, long param, LongByReference request);
        String strerror( int errno );
    }

    private static final MacCl C = MacCl.INSTANCE;

    private static PrintStream out = System.err;

    public static void main( String[] argv )
    {
        long TIOCMGET = 0x4004746a;
        long TIOCMSET = 0x8004746d;
        int O_RDWR = 0x2;
        int O_NDELAY = 0x4;
        int O_NOCTTY = 0x20000;

        int value = O_RDWR|O_NDELAY|O_NOCTTY;
        out.printf( "value=%x\n", value );
        int portfd = C.open(
                "/dev/tty.usbmodem735ae091",
                value );
        out.printf( "portfd=%d\n", portfd );

        LongByReference lineStatus = new LongByReference();

        int rc = C.ioctl( portfd, TIOCMGET, lineStatus );
        out.printf(
                "rc=%d, linestatus=%d\n", rc, lineStatus.getValue() );

        rc = C.ioctl( portfd, TIOCMSET, lineStatus );
        out.printf(
                "rc=%d errno='%s'\n",
                rc,
                C.strerror( Native.getLastError() ) );

        if ( rc == -1 )
            out.print( "Failure." );
        else
            out.print( "Success." );

        if ( portfd != -1 )
            C.close( portfd );
    }
}

Java输出为:

value=20006
portfd=23
rc=0, linestatus=6
rc=-1 errno='Inappropriate ioctl for device'
Failure.

2 个答案:

答案 0 :(得分:2)

ioctl.h头文件中正在使用的命令的检查表明,它期望将int作为第三个参数:

#define TIOCMSET    _IOW('t', 109, int) /* set all modem bits */
#define TIOCMGET    _IOR('t', 106, int) /* get all modem bits */

您在C代码中正确定义了一个4字节的int并传递了对它的引用,这有效:

int lineStatus;
int rc = ioctl( portfd, TIOCMGET, &lineStatus );
rc = ioctl( portfd, TIOCMSET, &lineStatus );

但是,在Java代码中,您定义了一个8字节的long引用来传递:

LongByReference lineStatus = new LongByReference();
int rc = C.ioctl( portfd, TIOCMGET, lineStatus );
rc = C.ioctl( portfd, TIOCMSET, lineStatus );

“获取”似乎有效,因为本机代码仅填充8个字节中的4个字节,而恰好是低位字节,但是您可能会由于过度分配而破坏堆栈。

正如@nyholku在评论中指出的那样,除了从long切换到int之外,您可能还需要将int(而不是指针)传递给{{1 }}版本的命令。有冲突的文档,但是我在野外看到的示例更喜欢您的指针实现。

因此您的代码应包括:

TIOCMSET

您确实说C版本没有指针“起作用”,但仅在它不会引发错误的意义上说。为了确认“有效”,您应该再次读取字节以确保您设置的内容实际上卡住了。

答案 1 :(得分:1)

我们多次检查了第三个参数。是否必须传递指针?我们发现的文档-在 Mac 上的手册页man -s 4 tty上,实际上是传递指针的文档。因此,Unix实现之间似乎存在差异。

最后,我们通过使用printf( "%xl", ... );打印传递的值找到了解决方案。并且结果值为0xffffffff8004746d。所以我们得到了意外的标志扩展名。

问题是线

        long TIOCMSET = 0x8004746d;

文字常量定义为int文字,它被隐式转换为带符号扩展名的长 。由于0xffffffff8004746d不等于0x8004746d',因此这说明了错误消息inappropriate ioctl for device。当我们将上面的行更改为

        long TIOCMSET = 0x8004746dL; // Note the 'L' at the end.

一切正常。在其他Unix上,我们没有问题,因为TIO...常量恰好是正数。

相关问题