7 советов по портированию исходного кода, ориентированного на Linux, на Windows

Я студент-химик, но мой первый опыт программирования был, когда я решил заняться вычислительной химией. Сколько себя помню, я использовал свой старый ноутбук с Windows. Поэтому, естественно, я хотел проводить тестирование и подготовительную работу для своих проектов на своем ноутбуке.

И тут я столкнулся с целым рядом проблем. Большинство кодов в моей области — это коды с открытым исходным кодом, написанные в Linux. Эти коды почти всегда сложно запускать в Windows (либо программа не компилируется, либо отсутствуют инструкции по ее компиляции). Нет встроенной функции, необходимой программе, которая не может быть предоставлена ​​Windows. Просто, поскольку они были написаны и протестированы в Linux, функции или системные вызовы, предназначенные только для Linux, проникают в исходный код. Некоторые коды предоставляют предварительно скомпилированный двоичный файл для Windows (например, NAMD), но их очень мало.

Cygwin/WSL — вариант, но они медленные, особенно с дисковым вводом-выводом. После долгих попыток использовать Cygwin я сдался и погрузился в исходные коды. На данный момент мне удалось модифицировать и собрать две программы (GAMESS и MRCC) с использованием родных компиляторов (Visual C++ и Intel) для Windows.

Почему сложно строить на Windows?

Проблема компиляции научного программного обеспечения с открытым исходным кодом в Windows не нова. Проблема заключается частично в самой Microsoft и частично в разработчике, который пишет это программное обеспечение с открытым исходным кодом. Microsoft не упростила запуск среды компиляции в Windows. MS Visual Studio должна быть основной средой разработки, но она предоставляет только компилятор C/C++; а если вам нужен Фортран? Вам нужно либо попытаться установить Intel Visual Fortran, либо одну из альтернатив с открытым исходным кодом, таких как LLVM (flang) или GNU (gfortran), и все они сложны в настройке. Также трудно использовать командную строку (cmd.exe), потому что нет согласованности в анализе аргументов. В Linux оболочка анализирует аргументы на основе фиксированного набора правил, а затем передает разделенные аргументы вызываемой программе. В Windows командная строка просто берет всю команду и отправляет ее программе, а обработка аргументов зависит от отдельной программы. Это означает, что, например, если вы заключите текст с пробелами в двойные кавычки, одна программа может рассматривать его как один аргумент, а другая — как несколько аргументов. Все это означает, что кто-то должен потратить много времени на ознакомление с особенностями Windows, прежде чем писать для нее код. Windows также не имеет встроенной системы сборки по умолчанию, как Linux, где сборка программного обеспечения иногда так же проста, как запуск make install в командной строке.

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

Несмотря на все эти проблемы, я считаю, что многие программы с открытым исходным кодом можно скомпилировать в Windows с минимальными изменениями. Это связано с тем, что стандартный исходный код C/C++ и Fortran может быть скомпилирован как в Windows, так и в Linux. Как я упоминал ранее, в большинстве случаев низкоуровневая функциональность ОС (для Linux: unistd.h, для Windows: windows.h) не требуется.

Я не говорю о портировании программного обеспечения, предназначенного специально для Linux. Это намного сложнее, и я еще не настолько хороший программист, чтобы давать советы по этому поводу! Нет, я говорю только об открытом исходном коде общего назначения, который в основном является приложением командной строки и, вероятно, был разработан для Linux. Их можно портировать для работы в Windows.

Перенос исходного кода в Windows

1) Проверьте различия в компиляторах

Если я правильно понимаю, традиционные компиляторы для систем Linux предоставляются набором инструментов GNU. Итак, для C/C++ компиляторы gcc и g++, для Fortran — gfortran. Эти компиляторы почти всегда гарантированно доступны в Linux (или легко устанавливаются), поэтому большинство разработчиков пишут код, поддерживающий компиляторы GNU. Существуют также компиляторы LLVM и Intel, которые вы можете установить в Linux.

Для Windows родной компилятор C/C++ — это собственный компилятор Microsoft Visual C/C++. Его можно установить как часть Visual Studio. (Я слышал, что его можно установить отдельно, но я не уверен). Однако Microsoft не предоставляет компилятора Fortran. Intel предоставляет Visual Fortran (вместе с собственным компилятором C/C++), который может генерировать собственные исполняемые файлы для Windows. (Также доступны порты компиляторов GNU и LLVM.)

Каждый из этих компиляторов имеет свои особенности. Некоторые тенденции, которые я заметил:

  1. Все параметры командной строки компилятора разные. Формат аргумента также отличается. Компиляторы Linux будут запускаться в командной строке как compiler -argument1 option1 -argument2 option2 . Для компиляторов Visual C++ и Intel стиль по умолчанию — compiler /argument1:option1 /argument2:option2 . (Однако иногда допускается запись compiler -argument1:option1 -argument2:option2.)
  2. В Linux компиляторы Fortran запускают препроцессор по умолчанию, но для Windows его нужно явно запрашивать аргументом /fpp .
  3. В Linux большинство компиляторов Fortran экспортируют символы в нижнем регистре с символом подчеркивания в конце, следуя соглашению GNU. Таким образом, имя подпрограммы mysubrt будет экспортировано как mysubrt_. В Windows Intel Fortran экспортирует символы в верхнем регистре без подчеркивания, т. е. mysubrt станет MYSUBRT . Это проблема только в том случае, если вы пытаетесь связать C/C++ с Fortran. Многие коды, которые делают это, не компилируются в Windows по этой конкретной причине. (Однако порт gfortran для Windows следует соглашению GNU)

2) Обработка сигналов ограничена в Windows

Коды C и C++ имеют функцию перехвата «сигналов», которые представляют собой сообщения от ОС к программному обеспечению о том, что произошло что-то неожиданное (например, было нажато Ctrl+C или Ctrl+Break, или произошло деление на ноль, или программа пыталась получить доступ к ячейке памяти, которая ему недоступна и т. д.). Смысл обработки сигналов в том, чтобы программы имели некоторый контроль над тем, как они реагируют на исключения и ошибки.

Обработка сигналов осуществляется путем установки специальных функций в качестве обработчиков. Эти функции будут вызываться при получении сигнала программой. Например, если я устанавливаю обработчик для SIGINT, то программа будет вызывать функцию обработчика при нажатии Ctrl+C вместо завершения работы программы, что является действием по умолчанию.

Стандартная библиотека C определяет 6 сигналов — SIGABRT (abnormal termination), SIGFPE (floating point error), SIGILL (illegal instruction), SIGINT (ctrl+c signal), SIGSEGV (illegal storage access) and SIGTERM (termination request). Они поддерживаются как Linux, так и Windows. Windows также дополнительно поддерживает SIGBREAK (Ctrl+Break).

Однако в Linux существует масса других сигналов (подробнее здесь), которые можно перехватить кодом. Если открытые исходные коды включают обработку этих сигналов, то они не могут быть скомпилированы в Windows. Решение состоит в том, чтобы удалить обработку этих сигналов (поскольку они никогда не будут отправлены ОС Windows) и перехватывать другие сигналы, которые могут понадобиться в Windows (из списка выше). К счастью, в академических программах с открытым исходным кодом редко требуется что-либо, кроме стандартных сигналов.

Кроме того, в Linux обработчики сигналов устанавливаются struct sigaction. Функция, которая обрабатывает сигнал, задается sigaction.sa_handler. В Windows присутствует только базовая функция C signal().

Например, следующий код для Linux устанавливает handle_sig в качестве обработчика сигнала для SIGINT:

void handle_sig(int signal) {
...
}
struct sigaction act; //act is instance of the structure sigaction
act.sa_handler = &handle_sig; // handle_sig is the handler in act
sigaction(SIGINT, &act, NULL);
/* Here, structure act is installed as the sigaction structure for SIGINT, which means handle_sig becomes the signal handler*/

В Windows это будет:

void handle_sing(int signal) {
...
}
signal(SIGINT,handle_sig);

Вот и все для Windows: коротко и просто.

3) Остерегайтесь отсутствующих библиотек

В Linux большинство библиотек при установке помещают свои пути в LD_LIBRARY_PATH или какую-либо другую переменную среды. Когда исходный код, который зависит от этой библиотеки, скомпилирован, он ищет библиотеку в LD_LIBRARY_PATH или конкретной среде этой библиотеки. Переменная.

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

В таких случаях компилятор (точнее, компоновщик) не сможет найти эти библиотеки, и компиляция завершится ошибкой. Для компиляторов Visual C++ или Intel LNK2019 Unresolved external symbol часто вызывается этим.

Решение состоит в том, чтобы указать компилятору (или компоновщику) файл библиотеки (.lib). Большинство программ с открытым исходным кодом компилируются в командной строке. Для компиляторов Visual C++ или Intel путь к файлу библиотеки должен быть добавлен в переменную среды LIB или указан в аргументе /libpath: компоновщика. В процессе связывания также должны быть включены имена файлов библиотеки.

Это довольно сложно, и конкретная команда, которую вам нужно использовать, будет зависеть от необходимых вам библиотек и используемой вами системы сборки.

Например, если я компилирую программу на Фортране с именем calculate.f90 и использую подпрограмму mysub, предоставленную отдельной библиотекой mylibrary.lib, например так:

program calculate
integer :: i,j  !some variables
!some code here
call mysub(i,j) !external subroutine called
!some more code here
end program calculate

Затем я должен связать вот так на Intel Fortran:

ifort calculate.f90 /link C:\path\to\mylibrary.lib

Или еще проще:

ifort calculate.f90 C:\path\to\mylibrary.lib

В первом случае библиотека указывается компоновщику, во втором случае компилятор распознает файл как библиотеку и автоматически передает его компоновщику.

Без библиотеки компоновщик выдаст ошибку Unresolved external symbol.

Если вы используете компиляторы GNU, они не используют компоновщик Microsoft link.exe, поэтому вышеизложенное неприменимо.

4) Заголовочные файлы только для POSIX

Это последний кошмар, с которым вы можете иногда столкнуться при портировании кода C/C++. Фортран на самом деле не имеет таких заголовков, как C, так что это неприменимо. Это одна из причин, по которой портировать код Fortran проще.

Файлы заголовков для конкретной ОС в C/C++ раскрывают основные функции ОС и позволяют выполнять операции очень низкого уровня. К сожалению, это означает, что они на самом деле не переносимы, поскольку ядра Windows и Linux работают по-разному. К счастью, этот тип функциональности редко требуется, и часто между Linux и Windows есть почти идеальные эквиваленты.

Например, в Linux заголовочный файл unistd.h предоставляет функцию usleep(time), которая приостанавливает поток процесса, вызывающий функцию, на time микросекунд.

#include <stdio.h>
#include <unistd.h>
int main(){
// some code here
usleep(1000); // pauses the program for 1000 microseconds i.e. 1 ms
// after 1 ms, the code starts here again
}

Если вы попытаетесь скомпилировать это в Windows, компилятор выдаст ошибку, поскольку заголовок unistd.h не существует в Windows.

Решение* состояло бы в том, чтобы заменить его одной из функций, предоставляемых заголовками Windows C. Быстрый поиск в Google покажет вам, что в Windows функция Sleep() выполняет аналогичную работу и не требует никакого заголовка. Однако Sleep(time) приостанавливается на time миллисекунд. Итак, вы можете изменить его примерно так:

#include <stdio.h>
int main(){
// some code here
Sleep(1); // pauses for 1 ms
// resumes after 1 ms
}

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

5) Посмотрите на ошибки компилятора/системные ошибки сборки

В большинстве случаев ошибки компилятора очень информативны о том, что пошло не так. Поскольку вы не пишете новый код, а только портируете код, почти всегда причиной ошибки является отсутствующая библиотека/отсутствующий объектный файл (т.е. Unresolved externals ).

Однако в некоторых редких случаях возникают конфликты имен макросов. Макросы — это некоторые инструкции, которые считываются препроцессором C или Fortran и сообщают им, как изменить исходный файл перед фактической компиляцией. Например, если вы компилируете в Windows, почти все компиляторы определяют макрос _WIN32. Это позволяет программисту писать специфичный для Windows код, который компилируется только тогда, когда определено _WIN32.

#if defined _WIN32
// Some C++ code for Windows
#else
// C++ code for other systems
#endif

В любом случае разработчики программного обеспечения иногда используют свои собственные макросы, которые определяются через аргумент командной строки. Допустим, я разработал программное обеспечение с открытым исходным кодом для Linux и использовал макрос с именем _WIN32, который отключил бы компиляцию определенной части кода. (Это вымышленный пример, никто бы так не делал!) Теперь в Linux _WIN32 не определен, поэтому он рассматривается как определяемый пользователем макрос. Когда вы пытаетесь скомпилировать этот исходный код в Windows, всегда определяется _WIN32, поэтому эта часть кода никогда не будет скомпилирована.

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

Ошибки системы сборки также помогут понять, что происходит не так.

6) Следите за вызовами оболочки

Иногда программисты используют команды оболочки из исходного кода, в основном для выполнения операций с файловой системой. В C/C++ это обеспечивается функцией system(), а в Fortran — подпрограммой system() или подпрограммой execute_command_line.

Например, этот код на C скопирует каталог file1 в каталог dir в Linux:

#include <stdlib.h>
int main(){
    system("cp file1 dir/");
    return 0;
}
// works on Linux

Как правило, такие вызовы оболочки встречаются редко. Очевидно, что в Windows тот же код не будет работать, потому что system() вызывает командную строку Windows (cmd.exe), которая не распознает команду cp. Кроме того, разделителем файловой системы является обратная косая черта в Windows, которую необходимо экранировать в C.

#include <stdlib.h>
int main(){
    system("copy file1 dir\\"); 
    return 0;
}
// works on Windows

7) Всегда проверяйте

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

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

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

Стоит ли оно того?

Стоят ли все эти проблемы с переносом программного обеспечения? Многие люди рекомендовали бы перейти на Linux или установить двойную загрузку. Несмотря на несколько достижений, я все еще не верю, что Linux подходит для обычного конечного пользователя. Многие утилиты по-прежнему не работают в Linux, а работают только в Windows и Mac OS X. Мне просто удобнее использовать Windows, так как я использую ее для всего остального. (Возможно, вы не согласны, и это нормально, потому что вы должны выбрать ОС, которая наиболее удобна для вас.)

Итак, я считаю, что стоит потратить время, чтобы попытаться перенести коды с открытым исходным кодом в Windows. В большинстве случаев это будет меньше хлопот, чем установка Linux.

Спасибо за чтение! Не стесняйтесь оставлять комментарии или вопросы в ответ.