我们使用连接到USB上Mac上的串行设备,需要配置DTR / RTS线路设置。从技术上讲,这涉及到open(3)
,ioctl(3)
的使用。
我们用C语言实现了这一点,并且有效。下面是一个非常简化的代码段,显示了核心部分。
然后,我们将代码迁移到Java / JNA,并遇到了移植的代码不起作用的问题,尽管它基本上是C代码的逐行转换。>
问题是我们在哪里出错?
Java失败的症状是从对errno=25 'Inappropriate ioctl for device'
的调用返回了ioctl()
。由于它在C语言中有效,因此我们似乎在JNA中确实做错了。
我们做了什么:
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.
答案 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...
常量恰好是正数。