Ма c доступ к устройству работает в C, но эквивалентный код в Java / JNA не - PullRequest
0 голосов
/ 09 апреля 2020

Мы работаем с последовательным устройством, подключенным к Ma c по USB, и нам необходимо настроить параметры линии DTR / RTS. Технически это предполагает использование open(3), ioctl(3).

Мы реализовали это в C, и это сработало. Ниже приведен очень упрощенный фрагмент, показывающий основную часть.

Затем мы перенесли код в Java / JNA и столкнулись с проблемой, что перенесенный код не работал , а не . хотя это в основном построчное преобразование кода C.

Вопрос в том, где мы go ошибаемся?

Симптом сбоя в Java errno=25 'Inappropriate ioctl for device' возвращается из вызова на ioctl(). Поскольку он работает в C, кажется, что мы делаем что-то не так в JNA.

Что мы сделали:

  • Проверены значения констант заголовка. Обратите внимание, что C -код генерирует Java -совместимые определения констант, которые используются в коде Java.
  • Проверены подписи ioctl(). Кажется, это правильно в соответствии с man-страницей и включаемыми файлами.
  • Предположил, что проблема в том, что код ioctl для TIOCMSET не передается должным образом, поскольку он отрицательный.

Мы используем 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 ]

2 голосов
/ 09 апреля 2020

Просмотр файла заголовка ioctl.h для команд, которые вы используете, показывает, что он ожидает int в качестве третьего аргумента:

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

Вы правильно определили 4-байтовый int в вашем коде C и передайте ссылку на него, которая работает:

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 );

Кажется, что "get" работает, потому что нативный код заполняет только 4 из 8 байтов и это младшие байты, но вы можете повредить стек из-за перераспределения.

Как указывает @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 без указателя " работает "но только в том смысле, что не выдает ошибку. Чтобы подтвердить, что «работает», вы должны снова прочитать байты, чтобы убедиться, что все, что вы установили, действительно застряло.

1 голос
/ 11 апреля 2020

Мы много раз проверяли третий аргумент. Должен ли указатель быть передан или нет? Документы, которые мы нашли - справочная страница man -s 4 tty на Ma c - действительно документы для передачи указателя. Так что, похоже, существуют различия между 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... оказались положительными.

...