Изменение файла интерфейса SWIG для поддержки C void * и типов возвращаемых структур - PullRequest
0 голосов
/ 06 декабря 2011

Я использую SWIG для создания своего уровня JNI для большого набора API C, и мне было интересно, каковы лучшие практики для следующих ситуаций. Ниже приведены не только SWIG, но и JNI в целом.

Когда функции C возвращают указатели на Structures, должен ли интенсивно использоваться файл интерфейса SWIG (логика JNI) или должны быть созданы функции-оболочки C для возврата данных по частям (т. Е. Массива char, который содержит различные элементы данных)? Когда функции C возвращают void *, следует ли изменять API C, чтобы они возвращали фактический тип данных, будь то примитивный или структурный типы? Я не уверен, хочу ли я добавить большое количество логики и создать средний уровень (файл интерфейса SWIG / логика JNI). Мысли

1 Ответ

2 голосов
/ 07 декабря 2011

Мой подход к этому в прошлом состоял в том, чтобы написать как можно меньше кода, чтобы он работал. Когда мне нужно написать код, чтобы он заработал, я пишу его в следующем порядке:

  1. Пишите как C или C ++ в исходной библиотеке - каждый может использовать этот код, вам не нужно писать что-либо специфичное для Java или SWIG (например, добавьте больше перегрузок в C ++, добавьте больше версии функций в C, используйте типы возврата, о которых SWIG знает в них)

  2. Напишите больше целевого языка - поставьте «клей», чтобы соединить некоторые кусочки библиотеки. В этом случае это будет Java.

    На самом деле не имеет значения, является ли это "чистой" Java вне SWIG или как часть файла интерфейса SWIG с моей точки зрения. Пользователи интерфейса Java не должны различать их. Вы можете использовать SWIG, чтобы избежать повторения в ряде случаев.

  3. Напишите несколько JNI через карты типов SWIG . Это уродливо, подвержено ошибкам, если вы не знакомы с написанием, сложнее в обслуживании (возможно) и полезно только для SWIG + Java. Использование типографских карт SWIG по крайней мере означает, что вы пишете их только один раз для каждого переносимого вами типа.

    Количество раз, когда я предпочел бы это более 2., является одним или несколькими из:

    1. Когда это часто возникает (экономит повторяющееся кодирование)
    2. Я вообще не знаю целевой язык, и в этом случае использование языка C API, вероятно, проще, чем писать что-то на этом языке
    3. Пользователи ожидают этого
    4. Или просто невозможно использовать предыдущие стили.

В основном, эти рекомендации, которые я предложил, пытаются предоставить функциональность как можно большему количеству пользователей библиотеки, одновременно сводя к минимуму количество дополнительного кода, предназначенного для конкретного языка, который вы должны написать, и уменьшая сложность его, когда вам приходится его писать. .


Для конкретного случая sockaddr_in*:

Подход 1

Первое, что я попытаюсь сделать, это избегать чего-либо большего, чем указатель на него. Это то, что swig делает по умолчанию с SWIGTYPE_p_sockaddr_in. Вы можете с радостью использовать этот «неизвестный» тип в Java, если все, что вы делаете, это передаете его от одного к другому, сохраняете в контейнерах / в качестве члена и т. Д., Например,

public static void main(String[] argv) {
  Module.takes_a_sockaddr(Module.returns_a_sockaddr());
}

Если это не сработает, вы могли бы сделать что-то вроде написания другой функции на C:

const char * sockaddr2host(struct sockaddr_in *in); // Some code to get the host as a string
unsigned short sockaddr2port(struct sockaddr_in *in); // Some code to get the port

В этом случае это не очень хорошо - у вас есть некоторая сложность, чтобы справиться с семействами адресов, которые, я думаю, вы бы предпочли избежать (именно поэтому вы используете sockaddr_in в первую очередь) , но это не специфично для Java, это не неясный синтаксис, и все это происходит автоматически для вас, кроме этого.

Подход 2

Если это все еще не достаточно хорошо, тогда я бы начал думать о написании небольшого количества Java - вы могли бы предложить более приятный интерфейс, скрыв тип SWIGTYPE_p_sockaddr_in как частный член вашего собственного типа Java, и обернуть вызов в функцию, которая возвращает его в какой-то Java, которая создает ваш тип для вас, например,

public class MyExtension {
  private MyExtension() { }
  private SWIGTYPE_p_sockaddr_in detail;
  public static MyExtension native_call() {
    MyExtension e = new MyExtension();
    e.detail = Module.real_native_call();
    return e;
  }

  public void some_call_that_takes_a_sockaddr() {
    Module.real_call(detail);
  }
}

Никаких дополнительных SWIG для записи, JNI для записи. Вы можете сделать это через SWIG, используя %pragma(modulecode), чтобы сделать все перегрузки на фактическом модуле, генерируемом SWIG - это кажется более естественным для пользователей Java, вероятно (это не выглядит как особый случай) и на самом деле не является более сложным , SWIG все еще выполняет тяжелую работу, она просто обеспечивает некоторую полировку, которая позволяет избежать повторяющегося кодирования на стороне Java.

Подход 3

Это будет вторая часть моего предыдущего ответа . Это приятно, потому что выглядит и чувствует себя нативным для пользователей Java, и библиотеку C также не нужно изменять. По сути, карта типов обеспечивает синтаксис «чистого» для инкапсуляции вызовов JNI для преобразования того, что пользователи Java ожидают, в то, с чем работает C, и ни одна из сторон не знает о взглядах другой стороны.

Недостатком является то, что его сложнее поддерживать и действительно сложно отлаживать.Мой опыт показывает, что у SWIG есть крутая кривая обучения для подобных вещей, но как только вы достигнете точки, когда не нужно слишком много усилий для написания таких карт типов, которые они дают вам через повторное использование и инкапсуляцию Ctype-> Java type mapping очень полезен и мощен.

Если вы являетесь частью команды, но единственным человеком, который действительно понимает интерфейс SWIG, тогда это ставит большое «что, если вас ударитавтобус?»фактор на проекте в целом.(Вероятно, это очень хорошо для того, чтобы сделать тебя нежелательным!)

...