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

标签 java macos jna

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

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

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

问题是我们哪里出了问题?

Java 中失败的症状是从对 ioctl() 的调用返回errno=25 'In合适的 ioctl for device'。由于它在 C 中工作,看来我们在 JNA 中做错了一些事情。

我们做了什么:

  • 检查 header 常量中的值。请注意,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.

最佳答案

查看您正在使用的命令的 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 字节长的引用来传递:

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

“get”似乎可以工作,因为 native 代码仅填充 8 个字节中的 4 个,并且它恰好是低位字节,但您可能会因过度分配而破坏堆栈。

正如 @nyholku 在评论中指出的那样,除了从 long 切换到 int 之外,您可能还需要传递 int (而不是而非指针)到命令的 TIOCMSET 版本。存在相互冲突的文档,但我在野外看到的示例有利于您的指针实现。

所以你的代码应该包括:

IntByReference lineStatus = new IntByReference();
int rc = C.ioctl( portfd, TIOCMGET, lineStatus );
// Possible, per the page @nyholku linked:
rc = C.ioctl( portfd, TIOCMSET, lineStatus.getValue() );
// Probable, per the man pages and other examples:
rc = C.ioctl( portfd, TIOCMSET, lineStatus );

您确实说没有指针的 C 版本“有效”,但只是在它不会引发错误的意义上。要确认什么“有效”,您应该再次读取字节以确保您设置的任何内容实际上被卡住了。

关于java - Mac 设备访问可以在 C 中使用,但在 Java/JNA 中的等效代码则不行,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/61118784/

相关文章:

java - 如何在Java中将文件存储在磁盘上并保证它们被删除

macos - 如何在 super View 中移动 subview

macos - Apple LLVM 和 LLVM 之间的差异

java - 使用管道获取运行时?

java - 错误 E/BitmapFactory:无法解码流:java.io.FileNotFoundException::open failed:ENOENT(没有这样的文件或目录)

java - 让 JNA 在 Java 1.4 下工作

java - jna加载库

java - Java/JNA中有类似MAKELPARAM的东西吗?

java - 如何手动启用 LibGDX 网络?

在 Mac OS X 10.6.8 上构建 android 2.1 期间出现 java.lang.OutOfMemoryError