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

Анализ требований
Самым первым этапом разработки программного обеспечения по праву называется процедура проведения всестороннего анализа выдвинутых заказчиком требований к создаваемому ПО, чтобы определить ключевые цели и задачи конечного продукта. В рамках этой стадии происходит максимально эффективное взаимодействие нуждающегося в программном решении клиента и сотрудников компании-разработчика, в ходе обсуждения деталей проекта помогающих более четко сформулировать предъявляемые к ПО требования. Результатом проведенного анализа становится формирование основного регламента, на который будет опираться исполнитель в своей работе — технического задания на разработку программного обеспечения. ТЗ должно полностью описывать поставленные перед разработчиком задачи и охарактеризовать конечную цель проекта в понимании заказчика.

Проектирование
Следующий ключевой этап в разработке программного обеспечения — стадия проектирования, то есть моделирования теоретической основы будущего продукта. Самые современные средства программирования позволяют частично объединить этапы проектирования и кодирования, то есть технической реализации проекта, будучи основанными на объектно-ориентированном подходе, но полноценное планирование требует более тщательного и скрупулезного моделирования. Качественный анализ перспектив и возможностей создаваемого продукта станет основой для его полноценного функционирования и выполнения всего комплекса возлагаемых на ПО задач. Одной из составных частей этапа проектирования, к примеру, является выбор инструментальных средств и операционной системы, которых сегодня на рынке присутствует очень большое количество.

В рамках данного этапа стороны должны осуществить:

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

Кодирование
Следующим шагом становится непосредственная работа с кодом, опираясь на выбранный в процессе подготовки язык программирования. Описывать особенности и тонкости самого трудоемкого и сложного этапа вряд ли стоит, достаточно указать, что успех реализации любого проекта напрямую зависит от качества предварительного анализа и оценки конкурирующих решений, с которыми создаваемой программе предстоит «бороться» за право называться лучшей в своей нише. Если речь идет о написании кода для выполнения узкоспециализированных задач в рамках конкретного предприятия, то от грамотного подхода к этапу кодирования зависит эффективность работы компании, заказавшей разработку. Кодирование может происходить параллельно со следующим этапом разработки — тестированием программного обеспечения, что помогает вносить изменения непосредственно по ходу написания кода. Уровень и эффективность взаимодействия всех элементов, задействованных для выполнения сформулированных задач компанией-разработчиком, на текущем этапе является самым важным — от слаженности действий программистов, тестировщиков и проектировщиков зависит качество реализации проекта.

Тестирование и отладка
После достижения задуманного программистами в написанном коде следуют не менее важные этапы разработки программного обеспечения, зачастую объединяемые в одну фазу — тестирование продукта и последующая отладка, позволяющая ликвидировать огрехи программирования и добиться конечной цели — полнофункциональной работы разработанной программы. Процесс тестирования позволяет смоделировать ситуации, при которых программный продукт перестает функционировать. Отдел отладки затем локализует и исправляет обнаруженные ошибки кода, «вылизывая» его до практически идеального состояния. Эти два этапа занимают не меньше 30% затрачиваемого на весь проект времени, так как от их качественного исполнения зависит судьба созданного силами программистов программного обеспечения. Нередко функции тестировщика и отладчика исполняет один отдел, однако самым оптимальным будет распределить эти обязанности между разными исполнителями, что позволит увеличить эффективность поиска имеющихся в программном коде ошибок.

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

  • первоначальная загрузка данных;
  • постепенное накопление информации;
  • вывод созданного ПО на проектную мощность.

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

Заключение
Создание даже небольшого и технически простого ПО зависит от четкого выполнения каждой фазы, то есть деятельности всех отделов, задействованных в процессе разработки. Четкий план выполнения необходимых мероприятий с указанием конечных целей становится неотъемлемой частью работы разработчиков, планирующих оставаться широко востребованными на рынке труда специалистами. Только правильно составленное техническое задание позволит добиться нужного результата и осуществить разработку по-настоящему качественного и конкурентного ПО для любой платформы — серверной, стационарной или мобильной.

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

В колоссальном по объему и количеству многообразии различного программного обеспечения особое место занимают программы, предназначенные для людей с физическими недостатками. Область применения такого ПО достаточно специфична, что, тем не менее, не уменьшает востребованности в обеспечении адаптивным программным обеспечением людей с физическими недостатками, а говоря более простым языком — пользователей-инвалидов. Использовать свободу, предоставляемую доступом к Глобальной Паутине, вправе все категории потребителей, включая и имеющих ограниченные физические возможности.

Категории программ для инвалидов

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

  • лупы или системы экранного увеличения;
  • программы для чтения;
  • ПО для сканирования и распознавания текста;
  • программные манипуляторы и устройства ввода;
  • приложения для навигации;
  • средства коммуникации.

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

Русский язык — редкость

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

Работа над обработкой голосовых команд, подаваемых человеком машине, более активными темпами началась лишь недавно, что выразилось в появлении очень удобных и функциональных электронных помощников — Siri и Cortana. Эти голосовые интерфейсы способны понимать простейшие команды и преображать сказанный вслух простой текст в электронный вид. Именно такие инструменты, но имеющие более сложную механику и объемный словарный запас, крайне востребованы среди инвалидов.

Отечественная разработка

Единственным выходом из ситуации, когда заказ на разработку специализированного программного обеспечения для пользователей-инвалидов обходится в колоссальные суммы, так как работают в этом направлении преимущественно иностранные компании, становится обращение к отечественным разработчикам. Только они готовы и способны, используя имеющиеся библиотеки и уже созданные программные модули, разработать и предоставить лицам с ограниченными возможностями столь необходимые программы и приложения. Главным критерием в пользу российских разработчиков ПО становится умение работать с русскоязычным контентом, существенно различающимся по сложности с англоязычными версиями.

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

Любая работа, совершаемая по требованиям заказчика и отданная на «откуп» исполнителю, должна начинаться подготовкой технического задания, разработка ПО, то есть программного обеспечения, не является исключением. Говоря иначе, начать разработку ПО можно только после подготовки грамотного и правильного ТЗ, охватывающего все технические аспекты будущего проекта и пожелания заказавшей работу организации или физического лица. Рассмотрим действия, необходимые для составления правильного документа, который станет основой при написании требуемых программных продуктов. Читать далее

В чем состоит принципиальная разница между гарантией и технической поддержкой программного обеспечения? Именно с этим вопросом очень часто сталкиваются заказывающие разработку программ организации, иногда не понимающие главных отличий двух очень взаимосвязанных элементов сопровождения ПО. Попробуем разобраться в этом вопросе, разобрав каждый компонент поддержки по отдельности. Читать далее

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

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

После принятия окончательного решения, приобретать ли уже разработанную СЭУД или создавать собственную, перед руководством предприятия встает проблема подготовки существующей инфраструктуры к внедрению Системы электронного управления документами. Начать следует с выбора вычислительного и серверного оборудования, напрямую ответственного за скорость обработки информации и емкостные пределы для создания архивов. Читать далее

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

Одним из важных преимуществ библиотеки JavaFX является использование стилей, принесенное из мира Web. Т.е. вместо того, чтобы быть зависимым от библиотек Look&Feel, пользователь сам определяет внешний вид, причем это действительно гибко и красиво, а также может изменяться динамически, «на лету», содержать анимацию и 3D графику. Используя концепцию стилей можно делать приложения с так называемыми «скинами», когда внешний вид полностью отделен от бизнес-логики, и даже разрабатывается отдельно, например, профессиональными дизайнерами.
Создадим простой пример диалога с кнопкой:

public class JavaFXDialog1 extends Application {
    @Override
    public void start(Stage stage) {
        final VBox vbox = new VBox();
        final Button button = new Button("test");
        vbox.getChildren().addAll(button);
        final Scene scene = new Scene(vbox, 150, 100);
        stage.setScene(scene);
        stage.show();
    }
  
    public static void main(String[] args) {
        launch(args);
    }

avafx and css styles 1
Применять стили можно несколькими способами:
1) Непосредственно в коде, например, изменить цвет фонта кнопки:

button.setStyle("-fx-text-fill: red");

avafx and css styles 2

2) Посредством CSS-файла, на который должен быть настроен класс Scene:
Для этого нужно создать файл с раширением .css и положить его в папку проекта, например — /css/styles.css.
Содержимое файла:

.button {
    -fx-text-fill: blue;
}

Да, тут главное не забыть настроить среду разработки, чтобы она копировала эти файлы в сборку, иначе можно долго разбираться, почему стиль не подключается
Например, в IntelliJ IDEA это делается так:
avafx and css styles 3

Теперь все готово, чтобы подключить к сцене стиль:

scene.getStylesheets().add((getClass().getResource("/css/styles.css")).toExternalForm());

Далее запустим проект и получим такой диалог:
avafx and css styles 4

Инструкция .button в CSS-файле говорит, что теперь все кнопки будут с голубым цветом:

final Button button1 = new Button("button1");
final Button button2 = new Button("button2");
vbox.getChildren().addAll(button1, button2);

avafx and css styles 5

А что если это не то, что нам надо? Что если нужно определить конкретную кнопку?
3) На помощь приходит определение пользовательского стиля кнопки:
В styles.css пишем:

.button1 {
    -fx-text-fill: green;
}

А в коде:

button1.getStyleClass().add("button1");

Получаемый диалог выглядит так:
avafx and css styles 6

Теперь все кнопки, присоединенные к этому классу стиля, будут с зеленым текстом, причем метод add() говорит, что подобных стилей можно добавлять несколько, тем самым расширяя, переопределяя или перекрывая разные свойства элемента.
4) Определить пользовательский стиль можно также через, так называемый, ID:
В styles.css пишем:

#button2 {
    -fx-text-fill: yellow;
}

А в коде:

button2.setId("button2");

Получаемый диалог:

avafx and css styles 7

Т.е. все элементы с одинаковым ID будут выглядеть одинаково.
Что же еще интересного можно сделать с помощью стилей?

Стили могут обрабатывать так называемые триггеры поведения, привнесенные из мира XAML.
На примере кнопки к ним относятся такие события интерфейса пользователя, как получение фокуса, выделение, нажатие, пронос курсора мыши над кнопкой, и пр. – все то, что так хочется вынести из кода диалога, чтобы разгрузить его, оставив лишь бизнес-логику. Тем более, если клиент хочет не то, и не так, то это не отразиться на коде диалога – все будет определять динамически подключаемый CSS-файл.
Например с помощью следующей CSS-инструкции можно изменить цвет кнопки, когда пользователь проносит над ней курсор мыши:

.button:hover {
    -fx-background-color: orange;
}

Получаемый диалог при наведении курсора мыши:
avafx and css styles 8

Причем, как уже указывалось ранее, это приведет к одинаковому поведению всех классов button, а такой код:

.button1:hover {
    -fx-background-color: orange;
}

будет применять триггер только для тех, кто ссылается на класс «button1».

Подобный подход можно применять для многих триггеров, например для кнопок кроме hover есть еще foused, selected, pressed.
Жаль, что подобное нельзя применить непосредственно в коде, например:

button.setStyle(":hover -fx-text-fill: red");

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

avafx and css styles 9

Пользователь проносит курсор мыши над сеткой и видит выделение, в данном случае для таблиц 7х8 ячеек, при клике на компоненте это выделение должно передаваться в программу, чтобы вставилась таблица 7х8 ячеек.
Конечно, можно отслеживать выделение в коде и окрашивать ячейки определенным цветом, но что если одному клиенту нравится один цвет, другому – другой, либо цветовая схема определяется общим механизмом «скина», или еще как-нибудь – что делать тогда?

Тут нужен простой компонент для выделения ячеек, а внешний вид – не его забота.

Очевидно, напрашивается взять предыдущий пример триггером «hover» для кнопки, но он сработает для каждой ячейки при проносе над ней курсора мыши, те же ячейки, что теряют курсор мыши, перестают соответствовать триггеру и теряют цвет выделения, как же оставить выделенным весь диапазон?
Для решения задачи нужно сделать свой триггер, срабатывающий на какое-то свойство объекта, например – «нахожусь в диапазоне выделения», а короче – inRage, которое можно было бы использовать в CSS-файле подобным образом:

.MyCell:inRange {
    -fx-border-width: 0.5;
    -fx-border-color: #ffffff;
    -fx-background-color: lightskyblue
}

Забегая вперед скажу, что задача достаточно непростая, да еще и решение для версий JavaFX 1.7 и 1.8 – принципиально различные, и, например, для 1.7 придется использовать массу deprecated-методов.
Для начала рассмотрим сам компонент:

public class JavaFXDialog2  extends Application {
    @Override
    public void start(Stage stage) {
        final VBox vbox = new VBox();
        final GridPaneEx table = new GridPaneEx();
        table.init(10, 10);
        final Label label = new Label();
        label.setMaxWidth(Double.MAX_VALUE);
        label.setAlignment(Pos.CENTER);
        label.setTextAlignment(TextAlignment.CENTER);
        label.setStyle("-fx-padding: 3 0 5 0");
        label.textProperty().bind(table.text);
        vbox.getChildren().addAll(label, table);
        final Scene scene = new Scene(vbox, 350, 300);
        scene.getStylesheets().add((getClass().getResource("/css/styles.css")).toExternalForm());
        scene.setFill(null);
        stage.setScene(scene);
        stage.show();
    }
 
 
    public static void main(String[] args) {
        launch(args);
    }
 
 
    private void fireCreateTable(final int cols, final int rows){
        System.out.println("cols = " + cols + ", rows = " + rows);
    }
 
 
    protected class GridPaneEx extends GridPane {
  
        public final StringProperty text = new SimpleStringProperty("cancel");
        private int cols;
        private int rows;
  
        public GridPaneEx(){
            this.setOnMouseExited(new EventHandler() {
                @Override
                public void handle(MouseEvent mouseEvent) {
                    text.setValue("cancel");
                    deselectAll();
                }
            });
        }
  
        public void init(final int cols, final int rows){
            getChildren().clear();
            this.cols = cols;
            this.rows = rows;
            for (int col = 0; col < cols; col++){
                for (int row = 0; row < rows; row++){
                    final Button rect = new Button();
                    rect.setMinSize(30, 10);
                    add(rect, col, row);
                    final int selectedCol = col;
                    final int selectedRow = row;
                    rect.setOnMouseMoved(new EventHandler() {
                        @Override
                        public void handle(MouseEvent mouseEvent) {
                            selectRange(selectedCol, selectedRow);
                            text.setValue((selectedCol + 1) + " x " + (selectedRow + 1));
                        }
                    });
                    rect.setOnAction(new EventHandler() {
                        @Override
                        public void handle(ActionEvent actionEvent) {
                            fireCreateTable(selectedCol + 1, selectedRow + 1);
                            deselectAll();
                        }
                    });
                }
            }
            deselectAll();
        }
  
        private Node getNodeFromGridPane(int col, int row) {
            for (Node node : getChildren()) {
                if (GridPane.getColumnIndex(node) == col && GridPane.getRowIndex(node) == row) {
                    return node;
                }
            }
            return null;
        }
  
        private void selectCell(int col, int row, final boolean select){
            final Node node = getNodeFromGridPane(col, row);
            if (select){
                node.setStyle("-fx-border-width: 0.5; -fx-border-color: #ffffff; -fx-background-color: lightskyblue");
            } else {
                node.setStyle("-fx-border-width: 0.5; -fx-border-color: #000000; -fx-background-color: #ffffff");
            }
        }
  
        public void deselectAll(){
            for (int col = 0; col < cols; col++){
                for (int row = 0; row < rows; row++){
                    selectCell(col, row, false);
                }
            }
        }
        private void selectRange(int selectedCol, int selectedRow){
            deselectAll();
            for (int col = 0; col <= selectedCol; col++){
                for (int row = 0; row <= selectedRow; row++){
                    selectCell(col, row, true);
                }
            }
        }
    }
}

Интерес представляет метод selectCell, в котором окраска ячеек осуществляется непосредственно в коде:
для обычных ячеек:

node.setStyle("-fx-border-width: 0.5; -fx-border-color: #000000; -fx-background-color: #ffffff");

для ячеек в выбранном диапазоне:

node.setStyle("-fx-border-width: 0.5; -fx-border-color: #ffffff; -fx-background-color: lightskyblue");

Т.к. в описании задачи указано, что чётко прописать цвета никак нельзя, то попробуем задать их с помощью собственного стиля, пометим в styles.css такой код:

#MyCellNormal {
    -fx-border-width: 0.5;
    -fx-border-color: #000000;
    -fx-background-color: #ffffff;
}
 
 
#MyCellInRange {
    -fx-border-width: 0.5;
    -fx-border-color: #ffffff;
    -fx-background-color: lightskyblue
}

А в метод selectCell:

private void selectCell(int col, int row, final boolean select){
            final Node node = getNodeFromGridPane(col, row);
            if (select){
                                node.setId("MyCellNormal");
            } else {
                                node.setId("MyCellInRange");
            }
        }

Уже лучше, не правда ли? Т.е. если клиента не устраивают цвета, то он меняет их прямо в styles.css, компонент же остается неизменным.
Но оказывается, что есть еще более элегантное решение – поместим в styles.css два стиля:

.MyCell {
    -fx-border-width: 0.5;
    -fx-border-color: #000000;
    -fx-background-color: #ffffff;
}
 
 
.MyCell:inRange {
    -fx-border-width: 0.5;
    -fx-border-color: #ffffff;
    -fx-background-color: lightskyblue
}

Это значит, что ячейки будут ориентироваться на стиль «MyCell», а когда у них будет срабатывать триггер «inRange», по аналогии «hover» или «pressed» на кнопке, то цвет должен измениться соответствующим образом.
Как «научить» ячейки запускать триггер?
Т.к. в качестве ячеек в нашем компоненте используется Button , то необходимо переопределить его поведение в нашем «наследнике», а точнее переопределить поведение так называемого «псевдо-класса», для JavaFX 1.7 это делается так:

protected static class RangeButton extends Button {
        public RangeButton(){
            getStyleClass().add("MyCell");
        }
 
 
        private BooleanProperty inRange = new BooleanPropertyBase() {
 
 
            @Override
            protected void invalidated() {
                impl_pseudoClassStateChanged("inRange");
            }
 
 
            @Override
            public Object getBean() {
                return RangeButton.this;
            }
 
 
            @Override
            public String getName() {
                return "inRange";
            }
        };
 
 
        public boolean isInRange() {
            return inRange.get();
        }
 
 
        public void setInRange(boolean value) {
            inRange.set(value);
        }
 
 
        private static final long IN_RANGE_PSEUDOCLASS_STATE = StyleManager.getInstance().getPseudoclassMask("inRange");
 
 
        @Override
        public long impl_getPseudoClassState() {
            long mask = super.impl_getPseudoClassState();
            if (isInRange()) mask |= IN_RANGE_PSEUDOCLASS_STATE;
            return mask;
        }
    }

Как видно, все методы «…PseudoClass…» — deprecated.
Теперь вместо Button используем наш RangeButton и смотрите, как теперь преобразится метод selectCell:

private void selectCell(int col, int row, final boolean select){
            final Node node = getNodeFromGridPane(col, row);
            ((RangeButton)node).setInRange(select);
        }

Т.е. изменение состояния поля «InRange» приводит к срабатыванию триггера из стиля и цвет выделенных в диапазоне ячеек изменится соответствующим образом.
Это именно то, что нам нужно!
Для JavaFX 1.7 это работает, но в JavaFX 1.8 – запрещено, поэтому такой код перестанет компилироваться, как только будет подключена JVM 1.8.
Что же предлагает нам новая версия? Как и ожидалось deprecated-методы были убраны и упрощена архитектура в целом. Теперь достаточно сделать так:

protected static class RangeButton extends Button {        protected final PseudoClass pcInRange = PseudoClass.getPseudoClass("inRange");
 
 
        public RangeButton(){
            getStyleClass().add("MyCell");
        }
 
 
        protected final BooleanProperty inRange = new BooleanPropertyBase() {
 
 
            @Override
            protected void invalidated() {
                pseudoClassStateChanged(pcInRange, getValue());
            }
 
 
            @Override
            public Object getBean() {
                return RangeButton.this;
            }
 
 
            @Override
            public String getName() {
                return "inRange";
            }
        };
 
 
        public boolean isInRange() {
            return inRange.get();
        }
 
 
        public void setInRange(boolean value) {
            inRange.set(value);
        }
    }

Все работает, как и прежде.
Можно еще упростить код:

protected static class RangeButton extends Button {
        protected final BooleanProperty inRange;
        public RangeButton(){
            getStyleClass().add("MyCell");
            final PseudoClass pcInRange = PseudoClass.getPseudoClass("inRange");
            inRange = new SimpleBooleanProperty();
            inRange.addListener(new ChangeListener() {
                @Override
                public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
                    pseudoClassStateChanged(pcInRange, newValue);
                }
            });
        }

И изменить метод соответствующим образом:

private void selectCell(int col, int row, final boolean select){
            final Node node = getNodeFromGridPane(col, row);
            ((RangeButton)node).inRange.setValue(select);
        }

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

Применение JNA (Java Native Access) в Java-проектах для доступа к функциональности и объектам, так называемых «нативных» библиотек — COM-DLL Microsoft Windows, представляет собой большой интерес.
Основным преимуществом является сокращение времени разработки проекта, если вся необходимая функциональность уже содержится в какой-то стандартной библиотеке Microsoft Windows, либо есть сторонняя COM-DLL с необходимым набором решений, либо это уже применяемая клиентом COM-DLL бизнес-логики. Также невозможно переоценить возможность использования COM-DLL, работающей с объектами Microsoft .NET Framework, написанная, например, на C#.
Вторым, но не меньшим по значимости преимуществом является то, что в отличие от предыдущей технологии JNI (Java Native Interface), здесь не придется писать библиотеку-оболочку на C, а это и в правду сомнительное удовольствие.
Но обо всем по порядку. Рассмотрим задачу, которая и подвигла на изучение технологии JNA.
Для нужд проекта потребовалось получить список принтеров в системе, но также название их драйверов, т.к. имя принтера можно задать вручную какое угодно, хоть «MySuperPuperPrinter», а проекту требовалось что-то более надежное и стабильное. Результат решения, которое предлагает Java, оказался неправильным…
Воспользуемся библиотекой javax.print:

import javax.print.PrintService;
import javax.print.PrintServiceLookup;
import javax.print.attribute.standard.PrinterMakeAndModel;
 
public class Tester {
    public static void main(String[] args) {
        final PrintService[] printServices = PrintServiceLookup.lookupPrintServices(null, null);
        for (PrintService printService : printServices) {
            System.out.println("PrinterName = " + printService.getName() +
                    ", PrinterMakeAndModel = " + printService.getAttribute(PrinterMakeAndModel.class));
        }
    }
}

Моя система показывает следующий результат:
PrinterName = Samsung SCX-4×28 Series PCL6, PrinterMakeAndModel = null
PrinterName = Samsung CLX-3180 Series, PrinterMakeAndModel = null
PrinterName = PDF Creator, PrinterMakeAndModel = null
PrinterName = FinePrint, PrinterMakeAndModel = null
PrinterName = Fax, PrinterMakeAndModel = null

Где информация о драйвере или модели принтера? Ее нет.
Посмотрим, что говорит об этом Oracle:

For attributes like
javax.print.attribute.standard.PrinterMakeAndModel
javax.print.attribute.standard.PrinterLocation
javax.print.attribute.standard.PrinterInfo
javax.print.PrintService.getAttribute should return meaningful information obtained from the Windows OS, instead of empty strings.
This bug can be reproduced always.
EVALUATION 2003-02-18
In Windows, this information can be retrieved from PRINTER_INFO_X structure.

ОК, проверим последнюю версию Java 1.7.0_17 – результат тот же, т.е. проблема 2003 не исправлена.

Ну да ладно, в принципе Java не предназначена знать все тонкости операционной системы, на которой она работает. Поэтому, как там Microsoft Windows разбирается со своими принтерами, должен был бы знать какой-нибудь Microsoft Windows сервис. Он есть – это библиотека c:WindowsSystem32Winspool.drv и ее функция EnumPrinters. Функция перечисляет принтеры системы и складывает информацию как раз в структуру PRINTER_INFO_2.

Чтобы получить доступ к библиотеке Winspool.drv можно использовать JNI – для этого нам придется писать оболочку на С, h-файл со стороны JNI и т.д. Для неспециалиста в этой технологии достаточно трудоемкий и время затратный процесс, а уж о трудностях отладки я вообще молчу.

Посмотрим, что нам может предложить JNA.

Начнем с самого оригинального примера «Hello World», что мне приходилось видеть, от разработчика JNA.

import com.sun.jna.Library;
import com.sun.jna.Native;
public class Tester {
    public interface Kernel32 extends Library {
        boolean Beep(int frequency, int duration);
    }
    private static Kernel32 kernel32 = (Kernel32) Native.loadLibrary("kernel32", Kernel32.class);
    private static void toMorseCode(String letter) throws Exception {
        for (byte b : letter.getBytes()) {
            kernel32.Beep(1200, ((b == '.') ? 50 : 150));
            Thread.sleep(50);
        }
    }
    public static void main(String[] args) throws Exception {
        String helloWorld[][] = {
                {"....", ".", ".-..", ".-..", "---"}, // HELLO
                {".--", "---", ".-.", ".-..", "-.."}  // WORLD
        };
        for (String word[] : helloWorld) {
            for (String letter : word) {
                toMorseCode(letter);
                Thread.sleep(150);
            }
            Thread.sleep(350);
        }
    }
}

Т.е. достаточно объявить интерфейс, расширяющий com.sun.jna.Library, объявить в нем необходимый метод с именем и сигнатурой, соответствующими функции из библиотеки (в данном случае это библиотека c:WindowsSystem32kernel32.dll и функция Beep), и загрузить библиотеку, используя этот интерфейс с помощью com.sun.jna.Native. Первым параметром выступает имя библиотеки, которое может содержать полный путь к ней в файловой системе, а вторым – класс интерфейса, который используется для связывания интерфейса и библиотеки посредством reflection.

Впечатляет, насколько просто можно услышать приветствие компьютера «Hello World», которое он пробибикает азбукой Морзе.

Также интересные примеры можно найти тут.

Теперь вернемся к задаче проекта – получить имена принтеров и их драйверов в Microsoft Windows. Как было сказано выше, нужно обратиться к Winspool.drv и посредством ее функции EnumPrinters получить набор структуры PRINTER_INFO_2 для каждого принтера в системе. К счастью JNA позаботился об обертке структур «нативных» библиотек и предоставила класс com.sun.jna.Structure.

Тестовый код выглядит так:

import com.sun.jna.Memory;
import com.sun.jna.Native;
import com.sun.jna.Pointer;
import com.sun.jna.Structure;
import com.sun.jna.platform.win32.Kernel32;
import com.sun.jna.platform.win32.Win32Exception;
import com.sun.jna.platform.win32.WinDef;
import com.sun.jna.ptr.IntByReference;
import com.sun.jna.win32.StdCallLibrary;
import com.sun.jna.win32.W32APIOptions;
import java.util.Arrays;
import java.util.List;
 
public class WinSpoolTest {
     
    public interface Winspool extends StdCallLibrary {
         
        boolean EnumPrinters(int flags, String name, int level, Pointer pPrinterEnum, int cbBuf, IntByReference pcbNeeded, IntByReference pcReturned);
        public static final int PRINTER_ENUM_LOCAL = 0x00000002;
        public static final int LEVEL2 = 2;
         
        public static class PRINTER_INFO_2 extends Structure {
            public String pServerName;
            public String pPrinterName;
            public String pShareName;
            public String pPortName;
            public String pDriverName;
            public String pComment;
            public String pLocation;
            public WinDef.INT_PTR pDevMode;
            public String pSepFile;
            public String pPrintProcessor;
            public String pDatatype;
            public String pParameters;
            public WinDef.INT_PTR pSecurityDescriptor;
            public int Attributes;
            public int Priority;
            public int DefaultPriority;
            public int StartTime;
            public int UntilTime;
            public int Status;
            public int cJobs;
            public int AveragePPM;
             
            @Override
            protected List<String> getFieldOrder() {
                return Arrays.asList(
                        "pServerName",
                        "pPrinterName",
                        "pShareName",
                        "pPortName",
                        "pDriverName",
                        "pComment",
                        "pLocation",
                        "pDevMode",
                        "pSepFile",
                        "pPrintProcessor",
                        "pDatatype",
                        "pParameters",
                        "pSecurityDescriptor",
                        "Attributes",
                        "Priority",
                        "DefaultPriority",
                        "StartTime",
                        "UntilTime",
                        "Status",
                        "cJobs",
                        "AveragePPM");
            }
            public PRINTER_INFO_2() {
            }
             
            public PRINTER_INFO_2(int size) {
                super(new Memory(size));
            }
        }
    }
     
    final static Winspool winspool = (Winspool) Native.loadLibrary("Winspool.drv", Winspool.class, W32APIOptions.UNICODE_OPTIONS);
     
    public static void main(String[] args) throws Exception {
        final IntByReference pcbNeeded = new IntByReference();
        final IntByReference pcReturned = new IntByReference();
        winspool.EnumPrinters(Winspool.PRINTER_ENUM_LOCAL, null, Winspool.LEVEL2, null, 0, pcbNeeded, pcReturned);
        if (pcbNeeded.getValue() <= 0) {
            return;
        }
        final Winspool.PRINTER_INFO_2 pPrinterEnum = new Winspool.PRINTER_INFO_2(pcbNeeded.getValue());
        if (!winspool.EnumPrinters(Winspool.PRINTER_ENUM_LOCAL, null, Winspool.LEVEL2, pPrinterEnum.getPointer(),
                pcbNeeded.getValue(), pcbNeeded, pcReturned)) {
            throw new Win32Exception(Kernel32.INSTANCE.GetLastError());
        }
        pPrinterEnum.read();
        final Winspool.PRINTER_INFO_2[] result = (Winspool.PRINTER_INFO_2[]) pPrinterEnum.toArray(pcReturned.getValue());
        for (Winspool.PRINTER_INFO_2 pi : result) {
            System.out.printf("PrinterName = %1$2s, DriverName = %2$2sn", pi.pPrinterName, pi.pDriverName);
        }
    }
}

Что мы видим тут? Интерфейс Winspool теперь расширяет StdCallLibrary, а также содержит структуру PRINTER_INFO_2, наследницу Structure. Это необходимость, т.к. вызов Native.loadLibrary посредством reflection строит карту интерфейса и его структур и, если вынести эту структуру из интерфейса, то карта будет неправильной. Ладно, если бы была ошибка, так структура просто будет ошибочно заполнена методом EnumPrinters – например, вместо имени принтера в поле pPrinterName будет только первая буква имени.

У структуры PRINTER_INFO_2 есть еще пара особенностей: она должна содержать конструктор без параметров и переопределять метод protected List getFieldOrder() – строгая последовательность и именование полей.

Вот и все. Запустив тест, получим в консоли список принтеров и их драйверов:

PrinterName = Samsung SCX-4×28 Series PCL6, DriverName = Samsung SCX-4×28 Series PCL6
PrinterName = Samsung CLX-3180 Series, DriverName = Samsung CLX-3180 Series
PrinterName = PDF Creator, DriverName = CUSTPDF Writer
PrinterName = FinePrint, DriverName = FinePrint 7
PrinterName = Fax, DriverName = Microsoft Shared Fax Driver

Это именно то, что требовалось.
Как же можно еще упростить эту задачу? Выглядит все это все равно чересчур громоздко.
Например, нам не нужны все 21 поля структуры PRINTER_INFO_2, а только два – PrinterName и DriverName.
Посмотрим, как справляется с этим .NET – ведь это неотъемлемая часть Microsoft Windows.
Код для консольного приложения .NET будет выглядеть так:

public class Tester
{
    public static void Main()
    {
        System.Printing.LocalPrintServer localPrintServer = new
        System.Printing.LocalPrintServer(System.Printing.PrintSystemDesiredAccess.AdministrateServer);
        foreach (var printer in localPrintServer.GetPrintQueues())
        {
            System.Console.WriteLine("PrinterName = {0}, DriverName = {1}", printer.FullName, printer.QueueDriver.Name);
        }
        System.Console.Read();
    }
}

Результат выводится на консоль:

PrinterName = Samsung SCX-4×28 Series PCL6, DriverName = Samsung SCX-4×28 Series PCL6
PrinterName = Samsung CLX-3180 Series, DriverName = Samsung CLX-3180 Series
PrinterName = PDF Creator, DriverName = CUSTPDF Writer
PrinterName = FinePrint, DriverName = FinePrint 7
PrinterName = Fax, DriverName = Microsoft Shared Fax Driver

Хм… То же самое. Значит мы на правильном пути.

Как теперь обернуть этот код в «нативную» библиотеку, получить к ней доступ через JNA и забрать результат в удобном для нашего проекта виде?
Начнем с того, что .NET не «нативная» среда — у нее свои CLR (Common Language Runtime), CIL (Common Intermediate Language) и CLI (Common Language Infrastructure-Standard), и .NET-DLL будет отлично линковаться другой библиотекой или программой .NET, но никак не как «нативная» библиотека к С- и Java-программам.

Как же быть?

На помощь приходит Unmanaged Exports (DllExport for .Net).

Ее можно подключить как nuget-плагин через Package Manager Console с помощью команды:

PM> Install-Package UnmanagedExports

Либо просто взять у производителя архив с плагином UnmanagedExportLibrary.zip и скопировать его в каталог шаблонов проектирования Microsoft Visual Studio.

Обычно это My DocumentsVisual Studio 20**TemplatesProjectTemplates.

Потом создать новый проект C# на базе шаблона UnmanagedExportLibrary и наша «нативная» библиотека Winspool.dll почти готова.
Добавим в нее статический класс Export и статическую функцию GetPrinterInfo:

using System;
using System.Text;
using RGiesecke.DllExport;
 
public static class Export
{
    [DllExport("GetPrinterInfo")]
    public static string GetPrinterInfo()
    {
        try
        {
            System.Text.StringBuilder sb = new System.Text.StringBuilder();
            System.Printing.LocalPrintServer localPrintServer = new System.Printing.LocalPrintServer(System.Printing.PrintSystemDesiredAccess.AdministrateServer);
            foreach (var printer in localPrintServer.GetPrintQueues())
            {
                sb.Append(printer.FullName).Append(";").Append(printer.QueueDriver.Name).Append(";");
            }
            return sb.ToString();
        }
        catch (Exception ex)
        {
            return ex.Message;
        }
    }
}

Статический класс и статическая функция – это необходимое условие для применения атрибута DllExport из библиотеки RGiesecke.DllExport. Таким образом, функция станет «нативной» в библиотеке и к ней можно будет непосредственно обращаться через JNA.

Функция GetPrinterInfo собирает информацию о принтерах и их драйверах в строку с разделителем «;», т.е. если потом преобразовать эту строку по разделителю в массив строк, то по нечетным индексам будет имя принтера, а по четным – имя драйвера. Очень полезным для нас тут будет то, что DllExport может передавать простые типы в качестве параметров и возвращаемых значений функций, и к счастью для нас, что в этот список входит string.

Скомпилируем библиотеку Winspool.dll для платформы х86.

Код на Java выглядит так:

import com.sun.jna.Native;
import java.io.File;
 
public class WinSpoolTest {
 
    public interface Winspool extends com.sun.jna.Library {
        public String GetPrinterInfo();
    };
 
    public static void main(String[] args){
        final String path = new File("").getAbsolutePath() + "\lib\Winspool.dll";
        final Winspool lib = (Winspool) Native.loadLibrary(path, Winspool.class);
 
        final String res = lib.GetPrinterInfo();
        final String[] arr = res.split(";");
 
        for (int i = 0; i < arr.length; i += 2){
            System.out.printf("PrinterName = %1$2s, DriverName = %2$2sn", arr[i], arr[i+1]);
        }
    }
}

Выглядит значительно проще, не правда ли?

Но что-то все равно бросается в глаза своим несовершенством… Да, это расположение библиотеки Winspool.dll в папке lib, рядом с классом, и ее поиск по пути в файловой системе:

final String path = new File(«»).getAbsolutePath() + «\lib\Winspool.dll»;

В данном случае это не критично, но что делать, если проект как-то хитро компилируется, использует специальные папки для ресурсов, или динамически ориентирован на Java определенной разрядности – 64 бита или 32? Как сделать так, чтобы нам упростить поиск месторасположения библиотеки? И как подготовить «нативную» библиотеку для разной разрядности, да еще отследить в Java проекте, когда и какую использовать?

Вот тут выступает еще одно достоинство JNA – автоматическое сканирование и нахождение «нативных» библиотек.

Если сделать так:
1 – где-нибудь создать две папки — win32-x86 и win32-x86-64
2 — скомпилировать «нативную» библиотеку с атрибутом х86, и положить ее в папку win32-x86
3 — скомпилировать с атрибутом х64, и положить в win32-x86-64
4 — создать из этих двух папок zip-архив
5 — переименовать его в jar
6 — подключить его к Java-проекту
то JNA автоматически подключает версию «нативной» библиотеки соответственно разрядности Java.

Код станет еще проще, и нам не придется устраивать поиск библиотеки Winspool.dll, контроль соответствия разрядности Java и библиотеки Winspool.dll.

import com.sun.jna.Native;
 
public class WinSpoolTest2 {
 
    public interface Winspool extends com.sun.jna.Library {
        public String GetPrinterInfo();
    };
 
    public static void main(String[] args){
        final Winspool lib = (Winspool) Native.loadLibrary("Winspool", Winspool.class);
 
        final String res = lib.GetPrinterInfo();
        final String[] arr = res.split(";");
 
        for (int i = 0; i < arr.length; i += 2){
            System.out.printf("PrinterName = %1$2s, DriverName = %2$2sn", arr[i], arr[i+1]);
        }
    }
}

Ну и напоследок, можно сделать еще красивее. Благодаря тому, что «нативная» библиотека .NET может возвращать строку неограниченной длинны, то использование результата с разделителем выглядит не очень дружественно.

Что более всего подходит для передачи данных в виде строки? Правильно – XML. Попробуем передать в качестве результата «нативной» библиотеки XML-сериализованный объект, а на стороне Java десериализуем в свой точно такой же объект.

На C# есть несколько возможностей XML-сериализации объектов, но сразу сделаю замечание, что распространенная сериализация из библиотеки System.Xml.Serialization не работает, если использовать ее в «нативной» библиотеке.

Будет ошибка System.IO «Illegal characters in path», хотя никакого IO при этом не используется – это какой-то баг .NET, который пока не исправлен и в .NET 4.5.

К счастью есть другой механизм XML-сериализации в библиотеке System.Runtime.Serialization.

Добавим к .NET-проекту класс PrintInfoList:

using System;
using System.Runtime.Serialization;
using System.Text;
using System.IO;
using System.Collections.Generic;
 
[DataContract(Name = "PrintInfoList")]
public class PrintInfoList
{
    [DataContract(Name = "PrintInfo")]
    public class PrintInfo
    {
        [DataMember(Name = "PrinterName")]
        public string PrinterName
        { get; set; }
 
        [DataMember(Name = "DriverName")]
        public string DriverName
        { get; set; }
    }
 
    private List<PrintInfo> list = new List<PrintInfo>();
 
    [DataMember(Name = "List")]
    public List<PrintInfo> List
    { get { return list; } }
 
    public String Serialize()
    {
        DataContractSerializer serializer = new DataContractSerializer(typeof(PrintInfoList));
        using (MemoryStream memoryStream = new MemoryStream())
        {
            serializer.WriteObject(memoryStream, this);
            memoryStream.Flush();
            string result = Encoding.UTF8.GetString(memoryStream.ToArray());
            result = System.Text.RegularExpressions.Regex.Replace(result, "<PrintInfoList.*?>", "<PrintInfoList>");
            return result;
        }
    }
}

Здесь можно видеть корневой класс PrintInfoList, который является списком объектов PrintInfo. Стоит обратить внимание на использование атрибутов библиотеки System.Runtime.SerializationDataContract и DataMember. Их применение должно быть совершенно очевидным – DataContract описывает объект, а DataMember – данные объекта, в данном случае это открытые поля. Кроме того, в конце метода десериализации в полученной XML-строке применена замена секции <PrintInfoList… со всеми пространствами имен Namespaces на — просто секцию <PrintInfoList>. Это нужно для упрощения примера, иначе придется обеспечивать обработку всех Namespaces на стороне Java.

Далее в класс Export добавим новую функцию GetPrinterInfo2, возвращающую заполненный и сериализованный объект PrintInfoList в виде XML-строки:

[DllExport("GetPrinterInfo2")]
public static string GetPrinterInfo2()
{
    try
    {
        PrintInfoList pil = new PrintInfoList();
        System.Printing.LocalPrintServer localPrintServer = new System.Printing.LocalPrintServer(System.Printing.PrintSystemDesiredAccess.AdministrateServer);
        foreach (var printer in localPrintServer.GetPrintQueues())
        {
            pil.List.Add(new PrintInfoList.PrintInfo
            {
                PrinterName = printer.FullName,
                DriverName = printer.QueueDriver.Name
            });
        }
        return pil.Serialize();
    }
    catch (Exception ex)
    {
        return ex.Message;
    }
}

Теперь вернемся к Java. Чтобы десериализовать XML-строку — результат метода GetPrinterInfo2 библиотеки Winspool.dll, создадим полный аналог объектов на Java:

import com.sun.jna.Native;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Unmarshaller;
import javax.xml.bind.annotation.*;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.util.List;
 
public class WinSpoolTest3 {
 
    @XmlAccessorType(XmlAccessType.NONE)
    @XmlRootElement(name = "PrintInfo")
    public static class PrintInfo {
         
        @XmlElement(name = "PrinterName")
        public String printerName;
 
        @XmlElement(name = "DriverName")
        public String driverName;
    }
 
    @XmlAccessorType(XmlAccessType.NONE)
    @XmlRootElement(name = "PrintInfoList")
    public static class PrintInfoList {
 
        @XmlElementWrapper(name = "List")
        @XmlElements({@XmlElement(name = "PrintInfo", type = PrintInfo.class)})
        public List<PrintInfo> list;
 
        public static PrintInfoList deserialize(String xml) {
            try {
                final InputStream inputStream = new ByteArrayInputStream(xml.getBytes("UTF-8"));
                try {
                    final JAXBContext context = JAXBContext.newInstance(PrintInfoList.class);
                    final Unmarshaller unmarshaller = context.createUnmarshaller();
                    return (PrintInfoList)unmarshaller.unmarshal(inputStream);
                } finally {
                    inputStream.close();
                }
            } catch (Exception ex) {
                ex.printStackTrace();
            }
            return null;
        }
    }
 
    public interface Winspool extends com.sun.jna.Library {
        public String GetPrinterInfo2();
    };
 
    public static void main(String[] args){
        final Winspool lib = (Winspool) Native.loadLibrary("Winspool", Winspool.class);
        final String res = lib.GetPrinterInfo2();
 
        final PrintInfoList pil = PrintInfoList.deserialize(res);
 
        for(final PrintInfo pi : pil.list){
            System.out.printf("PrinterName = %1$2s, DriverName = %2$2sn", pi.printerName, pi.driverName);
        }
    }
}

Т.е. XML-строка десериализуется в экземпляр PrintInfoList, который представляет собой список объектов PrintInfo, и который теперь гораздо удобнее и нагляднее использовать в любых частях проекта.

На базе полученного шаблона «нативная» DLL -> JNA -> Java можно создавать много разных и интересных межплатформенных проектов. На C# можно писать библиотеки-оболочки, упрощающие работу с «внутренностями» Microsoft Windows, которые трудно реализуемы или вообще недоступны для Java, а качестве обмена между .NET и Java процессами использовать XML-сериализованные объекты. Ведь этот принцип можно использовать и в обратном направлении, т.е. от Java к .NET, и таким способом передавая параметры и прочие данные для обработки в «нативной» DLL через XML-сериализованные объекты.

Исходные коды и прочие ресурсы:

Все исходные коды можно найти здесь:
src-jna-for-access-to-native-com-dll-library.zip