Начало программирования в Виндовс 5

Совершите вы массу открытий, в основном, не желая того. Или ох уж этот линкер.

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

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

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

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

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

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

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

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

Учитывая разнообразие форматов объектных файлов, библиотек, степень уникальности, неповторимости, яркости или дебильности бесконечного количества конкретных компиляторов, а также закрытость документации, создание универсального линкера Всё-под-Всё, задача не только не тривиальная, но и невыполнимая. Поэтому упрекать Fasm, который, хотя бы из собственных исходников, может создать и исполняемый модуль, и библиотеку, и объектные файлы разных форматов, язык не поворачивается. Nasm и Yasm до библиотек и исполняемых файлов так и не доросли и без стороннего линкера им никак.

Винде уже 40 лет и может показаться, что нет больше места подвигу. Это не так. Ниже описано моё приключение с функцией scanf из msvcrt.dll Windows 10. Поэтому, я хочу подключить scanf из местной crt.lib, а printf из msvcrt.dll. Думаю, что ни с одним из существующих линкеров, мне это не удастся. Можно покоцать msvcrt.def и crt.lib, но зачем же подталкивать меня к извращениям? А линкер, который смог бы связать объектный файл со статической библиотекой и сохранить результат в новом объектном файле, не появится вообще никогда.

Рассмотрим конкретные заморочки конкретных линкеров. Конкретный, на языке оригинала, означает бетонный, не прошибаемый. И первый в списке, MSVC. Вот простейшая, абсолютно реальная программа, адаптированная из фарша MASM32. Все примеры выполняются из командной строки, а не из IDE.

// msgbox1.c user32 kernel32

// for MSVC

// (с)O.E.Tereshkov 23.07.2019

extern int _stdcall MessageBox();

extern int _stdcall ExitProcess();

#define NULL 0

#define MB_OK 0

char szMsgCaption[]="Message_Box";

char szMsgBoxText[]="Link.exe is not Great!";

void main(void){

MessageBox(NULL,szMsgBoxText,szMsgCaption,MB_OK);

ExitProcess(NULL);}

В MASM32 она вроде, как собирается, а в MSVC - нет. Пишет, не могу найти MessageBox. Открываем user32.dll и смотрим. Действительно, там нет MessageBox. А есть MessageBoxA/W. Исправляем.

// msgbox2.c user32

// for MSVC

// (C)O.E.Tereshkov 23.07.2019

extern int _stdcall MessageBoxA();

extern int _stdcall ExitProcess();

#define NULL 0

#define MB_OK 0

char szMsgCaption[]="Message_Box_A";

char szMsgBoxText[]="Link.exe is not Great!";

void main(void){

MessageBoxA(NULL,szMsgBoxText,szMsgCaption,MB_OK);

ExitProcess(NULL);}

Изменилось ровным счётом ничего. Пишет, не могу найти MessageBoxA. По задумке Microsoft, это должно сломить, остановить и заставить оставить бесплодные попытки возиться с командной строкой. Так и будет, если не заглянуть в user32.lib. Там, тоже, нет MessageBoxA, но есть _MessageBoxA@16. А в kernel32.lib нет ExitProcess, но только _ExitProcess@4. Теперь, открываем наш объектный файл и обнаруживаем в нём _MessageBoxA@0 и _ExitProcess@0. Налицо явное не соответствие. Не забавно, чё за фигня? Фекально, но именно так и действуют cl.exe и link.exe из MSVC по отношению к extern функциям stdcall. Чтобы выполнить все соответствия, программку следует переписать так:

// msgbox3.c user32

// for MSVC

// (C)O.E.Tereshkov 23.07.2019

extern int _stdcall MessageBoxA(int,int,int,int);

extern int _stdcall ExitProcess(int);

#define NULL 0

#define MB_OK 0

char szMsgCaption[]="Message_Box_A";

char szMsgBoxText[]="Link.exe is not Great!";

void main(void){

MessageBoxA(NULL,szMsgBoxText,szMsgCaption,MB_OK);

ExitProcess(NULL);}

Увы, но не существует очевидных способов получения справочных библиотек кройки и шитья вида any.lib для link.exe. Microsoft намеренно 'эти способы замалчивает. Заполучить библиотеку MyNeeds.lib, для подключения сторонней MyNeeds.dll, в общем случае, начинающему не по силам. Нужны опыт, наблюдательность, проницательность, везение и знания с большой буквы Зэ. Но сейчас, это не тема для разговора. Запомним только, что родным форматом объектных файлов для link.exe является coff.

В прямое копирование, аналогично, действуют линкеры из пакетов PelleC и LCC32. А поскольку, plink входит в PureBasic, как неотъемлемая составная часть, то и в PureBasic, тоже, ожидаются все те же неприятности, что и в MSVC. И даже хуже. Если MSVC закрывает глаза на несоответствие типов аргументов в объявлениях внешних функций при их использовании, то в PureBasic соответствие должно быть абсолютным, всегда. А это тот ещё подарок. Функция printf, к примеру, сразу остаётся за бортом.

Рано или поздно, натыкаешься на alink. Диагноз у пациента гораздо более тяжёлый. В общем случае, тот же подход, но WinAPI урывочно свален в одну не расширяемую либу. В результате чего, ситуация "Приплыли" не за горами.

Я никогда не пользовался линкером GCC, равно, как и gcc.exe, за не надобностью. Есть гораздо более простые и элегантные способы насладиться gnu-компилятором. Но не смотрите на меня. Попробуйте. Вас ждет масса открытий. Хоть, в основном и нежеланных.

Особенно очаровательно выглядит не возможность отучить конкретный линкер/компилятор от использования функций, библиотек и объектных файлов по умолчанию. Этим страдает большинство доморощенных поделок аля Borland, Lcc32, Digital Mars, Pelle, сс386 и т.д. Лечится с трудом. И часто не имеет смысла. Ибо править вручную ассемблерные листинги, перекомпилировать библиотеки или создавать объекты заново то ещё развлечение. Лучше заняться другим, чем-нибудь более полезным.

Есть такой компилятор TCC. Наверное единственный, который и адекватный линкер, одновременно. Очень просто реализовано подключение библиотек. Проще, даже, чем в Fasm. Во многих случаях, способен заменить GCC и всё остальное. Отлично стыкуется с Fasm, Nasm, Yasm, а следовательно и со всеми компиляторами способными выдавать адекватные листинги под эти ассемблеры. Например, с cc386 стыкуется автоматически, без ручных правок. При использовании конвертера объектных файлов, без ручных правок, доступен даже MSVC. В пределах разумного, разумеется. Но понимает TCC только формат elf, со всеми вытекающими из этого скорбями и печалями по поводу совместимости и доступности статических библиотек.

Я намеренно не вдавался в технические подробности. А частный случай с MSVC, хорошая иллюстрация того в каком направлении должна двигаться мысль новичка, при полном отсутствии документации. В общем случае, всегда имеешь дело с произведением в котором дебильность компилятора умножается на дебильность линкера. И выбирая пакет, выбираешь не судьбу, как утверждают идиоты, но в первую очередь неприятности и подставы + тупые ограничения, о которые, потом, будешь постоянно спотыкаться всю жизнь. Уверяю, чтобы писать простые утилиты для дела и для развлечения, MSVC/GCC/INTEL/BORLAND, с их бесконечными гигабайтами и заморочками, совсем не нужны. Пока-пока.

(с) Терешков Олег Евгеньевич. Август 2019.

PS. О сколько, блин, открытий чудных! И когда же, нафиг, они, наконец, закончатся? Вот оригинальный скрин от Андрюшки Столярова.

Посмотри на него внимательно и честно скажи самому себе, какой именно литерал после знака % ты сейчас видишь? Честно, самому себе. А вот образчик от Jacob Navia.

Как заботливо, со сменой шрифта, отличаются буковки эл внутри кавычек и вне их?! Скажи, как можно учиться, если даже на уровне шрифтов ставят палки в колёса? Удобочитаемость кода! Про неё ты ещё услышишь, хотя идея рассматривать исходники в hex-редакторе, пока, не приходила в голову никому, кроме меня. А зря. :)

Но Яшка с Андрюшкой и комитет ANSI Си такой ерундой не заморачиваются.

Если засранец не отключил копи-пасту, лечится шрифтом Lucida Console и цветной подсветкой цифр в редакторе. Если отключил, не лечится ничем, кроме подключения серого вещества, если оно, конечно, есть. :) Найди 7 отличий между "l" и "1" -

Андрюшка Столяров. Офигенный чувак, с офигенным же апломбом. Одни ловят кайф на таблетках и порошке. Андрюшка же, как и всё МГУ, на Free BSD под командной строкой. Простим им это их невинное чудачество.

Во втором томе Андрюшкиного многотомника прорисовано дрочево для нахождения корней квадратного уравнения. Интересно, откуда он его спёр? Интерфейс пользователя.. Бесконечная тема.. Не буду отнимать у Андрюшки хлеб. Вот мой вариантик. С его помощью, уже, можно быстро порешать всё. :) Из учебника математики, разумеется. :)

//(c)TereshkovOE

//sqrt.c Є®а­Ё Єў ¤а в­®Ј® га ў­Ґ­Ёп

//sites.google.com/site/excelmidi

extern int printf(const char *f,...);

extern int scanf(const char *f,...);

extern double sqrt(double f);

void main(void){

float a, b, c;

double d;

int n;

L_a:

printf("\n");

printf("Џ®ЁбЄ Є®а­Ґ© Єў ¤а в­®Ј® га ў­Ґ­Ёп\n");

printf("ўЁ¤ ax^2+bx+c=0\n");

printf("‚ў®¤Ё Є®нддЁжЁҐ­вл a, b Ё c\n");

printf("зҐаҐ§ Їа®ЎҐ«: a b c\n");

n = scanf("%f %f %f", &a, &b, &c);

if(n!=3) {

printf("ЋиЁЎЄ : ­ҐЇа ўЁ«м­л© ўў®¤!\n");

goto L_a;}

if(a==0) {

printf("ЋиЁЎЄ : нв® ­Ґ Єў ¤а в­®Ґ га ў­Ґ­ЁҐ!\n");

goto L_a;}

d = (b*b - 4*a*c);

if (d<0) {

printf("ЌҐв Є®а­Ґ©..\n");

goto L_a;}

d = sqrt(d);

printf("%.5lf %.5lf\n", (-b-d)/(2*a), (-b+d)/(2*a));

goto L_a;

}

Как-то я уже предлагал называть глюки, надо же, как интересно получившимися, особенностями. Интересно получившаяся особенность функции scanf от Microsoft состоит в мгновенном уходе программы в бесконечный цикл при вводе буковки вместо циферки. По задумке, это должно развивать внимательность. Но требует перезапуска. Я не пробовал GCC, но уверен, там то же самое. И во всех остальных компиляторах, тоже. И имя им легион. Ибо весь официальный фарш в мире Си пишется копирастами по единой кальке ЦРУ и Пентагон.

Также, в этом листинге отображён секс со шрифтами в консольных приложениях на русском языке. Ну очень интересно получившаяся особенность каждой Microsoft Windows , вплоть до десятки.

Кстати, при компиляции из командной строки, в Pelle C с его родной crt.lib эта программка не сможет запуститься, а без crt.lib - слинковаться. Почему из командной строки? Потому, что poide.exe в 10-й винде не запускается. Хи-хи. Не легка ты доля программиста. :) Ой, запустился! Прошло 15 минут и Pelle C запустился! Обожаю Pelle C! Рассказ о том, как добиться такой удивительной "производительности" в Виндовс10, когда на запуск одной программы уходит 15 минут, в ближайших выпусках. Пока-пока. :)