«...Одним из примеров громоздкой и, по мнению авторов, бесполезной надстройки является интегрированная система WINDOWS фирмы Microsoft. Эта система занимает почти 1 Мбайт дисковой памяти и рассчитана на преимущественное использование совместно с устройством типа "мышь".» — вы точно знаете, откуда это
Приветствую всех! Буквально неделю назад прошло сорок лет с момента выхода первой релизной версии Windows. Именно в тот день в 1985 году началась история ныне повсеместно распространённой ОС.

И вот, узнав об этом, я подумал: а что, если попробовать запустить эту ОС и узнать, как предполагалось писать софт для неё? Именно этим мы сейчас и займёмся. Заодно и узнаем, насколько это было проще или сложнее, нежели сейчас.
❯ Суть такова
Уверен, если вы интересуетесь историей ОС, то хорошо знаете, что вообще такое Windows 1.0 и что она собой представляла. Хотя Windows 1.X были не полноценными ОС, а графическими оболочками под DOS, для них существовал SDK, позволяющий писать оконные приложения. Тем не менее, из-за высокой по меркам тех лет сложности разработки самих приложений было не так уж и много.
Особой популярности система тоже не заполучила, поскольку имела значительные системные требования и малое количество софта. Очень многие из тех, у кого в те годы был ПК, про этот продукт вообще ни разу не слышали. В общем, это самая подходящая платформа, под которую сейчас стоит попробовать что-то написать. Этим-то мы и займёмся.
❯ Обзор оборудования
Как известно, я пишу про железо, а не только про софт, поэтому запускать то, что получится, будем на настоящем ПК.

Намного более аутентичного для такой системы PC XT у меня нет, поэтому для запуска был вытащен вот такой промышленный одноплатник. Конечно, можно было бы взять и просто плату на 286 или 386, но этот девайс лежал у меня уже больше полугода и всё ждал, когда я сделаю с ним что-то интересное. Так что сейчас будем пробовать с ним.
Это ROCKY-328E-M4. На борту процессор 386SX-40 (точнее, SoC Ali M6117C, объединяющая процессорное ядро и чипсет Ali M1217), четыре мегабайта памяти, IDE, флоппи-контроллер, в данный момент ненужный Ethernet, панелька под DiskOnChip и стандартные для любого ПК интерфейсы. Когда-то давно он работал на одном неназванном предприятии и управлял какими-то устройствами при помощи плат дискретного ввода-вывода и платы последовательных портов. Впрочем, про эти модули поговорим как-нибудь в другой раз, а сейчас будем рассматривать его просто как обычный ПК.

Вообще, такие промышленные ПК — отличный вариант для того, кто хочет заполучить себе ретрокомпьютер, но у кого поставить дома обычную «тройку» или «четвёрку» возможности нет. Эта плата позволит заиметь полноценный 386 без всяких эмуляторов, а места такая машина будет занимать не больше, чем обычный бесперебойник.

Встроенного видео на плате нет, поэтому для запуска понадобится ещё и видеокарта.
Это довольно популярная в своё время плата на чипе Realtek RTG3105i. Особых причин выбрать именно её у меня нет: просто когда-то она досталась мне вместе с этим промПК.

Всё вместе втыкается в кросс-плату.
У меня она вот такая, от Advantech. Конкретно эта сделана под размер обычной материнки типа AT. Даже предусмотрен разъём DIN-5 для клавиатуры с отводом от него для подключения к процессорной карте.
❯ Что нужно, чтобы начать писать софт под Windows 1.X?
Вообще, по опыту работы со старым софтом, я ещё перед началом догадывался, что там наверняка будет куча каких-то косяков, которые не удастся сходу решить. На обычном компьютере всё это делать забавно, но только один раз. Поэтому сборкой самого приложения я занимался в эмуляторе.
Итак, определимся с тем, что нам вообще понадобится:
Компилятор. В моём случае Microsoft C 4.0.
Windows SDK. Тут я решил использовать версию 1.03.
Изначально я хотел использовать SDK 1.01 и Microsoft C 3.0, но...

...во всяком случае, я пытался.
То ли ему чего-то не хватает, то ли устанавливается он не так просто, как мне думалось, но ни одна программа им не собралась. Поэтому выбор был сделан в пользу того, на что имелась документация. Никаких PDF, никакой онлайн-справки в те годы не было, все мануалы были бумажными. Так уж вышло, что на Microsoft C 4.0 и Windows SDK 1.03 их сканы имелись в наличии.
Ну что, приступим?
❯ Эмулятор
Как я уже упомянул, собирать всё будем в эмуляторе. Им стал 86box (пришедший на смену почившему PCem). Как его поставить, описывается тут.

Создал виртуалку с процессором 386SX и чипсетом как у моей платы (дабы, если что, заранее обнаружить, что что-то пошло не так, и это решить).

Далее добавляем винт, а в разделе контроллеров выбираем «PC/AT Floppy Drive Controller» и «[ISA16] PC/AT IDE Controller (Dual-channel)».

В BIOS указываем параметры жёсткого диска. Загружаем в дисковод образ DOS и перезагружаемся.

Далее выполняем стандартные действия для установки DOS: размечаем диск при помощи fdisk, форматируем при помощи format, делаем его загрузочным при помощи sys и копируем остальные файлы. На этом загрузочная дискета нам больше не понадобится. Компьютер теперь будет запускаться с винта.

Процесс установки Windows 1.0 особых сложностей тоже не вызывает, так что показывать его я тут не буду. При установке надо указать следующие параметры: мышь — Microsoft Mouse (Bus/Serial), видеокарта — EGA with Enhanced Color Display or Personal Computer Color Display, принтер — не используется.
Запускаем ОС командой win и убеждаемся, что картинка цветная, мышь шевелится, а стандартные приложения нормально открываются.
❯ Компилятор
Теперь очередь компилятора.
Установочной программы у него нет. Поэтому всё придётся копировать самому. На системном диске создаём папки BIN, INCLUDE, TEMP, LIB. В BIN копируем всё содержимое первой дискеты, ещё несколько экзешников со второй и link.exe с третьей, в INCLUDE — всё с расширением *.H и *.INC, в LIB — всё с расширением *.OBJ и *.LIB, TEMP оставляем пустой. В INCLUDE создаём папку SYS и копируем туда содержимое одноимённого каталога на третьем диске. Дискеты 6, 7 и 8 для первого запуска можно пока не трогать.

Казалось бы, на этом всё. Но на самом деле нет, ведь если теперь мы попробуем что-либо собрать, то компилятор выдаст вот такую ошибку.

Поэтому продолжим установку, для чего создадим в корне системного диска ещё два файла.
Первый, AUTOEXEC.BAT, следующего содержания:
PATH C:\WINDOWS;C:\BIN;C:\INCLUDE;C:\LIB
SET INCLUDE=C:\INCLUDE
SET LIB=C:\LIB
SET TMP=C:\TEMP
SET TEMP=C:\TEMP
Вообще, в этом батнике указаны команды, которые выполняются автоматически при запуске DOS. В данном случае мы здесь указываем глобальные переменные, чтобы не вводить их каждый раз вручную.
Второй, CONFIG.SYS, вот такой:
FILES=20
BUFFERS=40
Это файл конфигурирования системы. Здесь мы задали число максимально возможных открытых файлов и число максимально возможных дисковых буферов.
Если у вас не чистая установка DOS, то прописываем эти параметры и имена переменных в соответствующих файлах.

После этого тестовая программа (из комплекта компилятора) должна будет собраться и запуститься. Отлично.
❯ SDK
Теперь нужно установить Windows SDK. Поставляется он опять таки на нескольких дискетах.

Вставляем диск номер два и выполняем следующие команды:
C:
copy A:\INSTALL.BAT C:\INSTALL.BAT
CD \
INSTALL \BIN \WINDOWS \INCLUDE \LIB
После этого начнётся установка.

Тут всё просто, вставляем очередную дискету и ждём, пока скопируются файлы.
На этом установку SDK можно считать законченной.
❯ Ставим Windows
А пока что отвлечёмся от установки инструментария и произведём ещё одну установку Windows.

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

Собираем тестовый стенд.
В кросс-плату втыкаем одноплатник и видеокарту, подключаем клавиатуру, мышь, монитор, блок питания и дисковод. В дисковод втыкаем записанную нами дискету. Всё, девайс готов к запуску.
❯ Пишем первую программу
Ну что же, время попробовать что-нибудь собрать. В составе SDK есть и какие-то примеры кода. С них-то и начнём.

Находим папку HELLO и копируем её на жёсткий диск. Теперь заходим в неё и выполняем команду:
make hello
После этого через примерно пару минут приложение должно будет собраться. Если не собралось — проверяем, правильно ли установили компилятор и SDK.

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

Отлично!
❯ Что же тут происходит?
Взглянем на исходник этого приложения.
HELLO.C
#include "windows.h"
#include "hello.h"
char szAppName[10];
char szAbout[10];
char szMessage[15];
int MessageLength;
static HANDLE hInst;
FARPROC lpprocAbout;
long FAR PASCAL HelloWndProc(HWND, unsigned, WORD, LONG);
BOOL FAR PASCAL About( hDlg, message, wParam, lParam )
HWND hDlg;
unsigned message;
WORD wParam;
LONG lParam;
{
if (message == WM_COMMAND) {
EndDialog( hDlg, TRUE );
return TRUE;
}
else if (message == WM_INITDIALOG)
return TRUE;
else return FALSE;
}
void HelloPaint( hDC )
HDC hDC;
{
TextOut( hDC,
(short)10,
(short)10,
(LPSTR)szMessage,
(short)MessageLength );
}
/* Procedure called when the application is loaded for the first time */
BOOL HelloInit( hInstance )
HANDLE hInstance;
{
PWNDCLASS pHelloClass;
/* Load strings from resource */
LoadString( hInstance, IDSNAME, (LPSTR)szAppName, 10 );
LoadString( hInstance, IDSABOUT, (LPSTR)szAbout, 10 );
MessageLength = LoadString( hInstance, IDSTITLE, (LPSTR)szMessage, 15 );
pHelloClass = (PWNDCLASS)LocalAlloc( LPTR, sizeof(WNDCLASS) );
pHelloClass->hCursor = LoadCursor( NULL, IDC_ARROW );
pHelloClass->hIcon = LoadIcon( hInstance, MAKEINTRESOURCE(HELLOICON) );
pHelloClass->lpszMenuName = (LPSTR)NULL;
pHelloClass->lpszClassName = (LPSTR)szAppName;
pHelloClass->hbrBackground = (HBRUSH)GetStockObject( WHITE_BRUSH );
pHelloClass->hInstance = hInstance;
pHelloClass->style = CS_HREDRAW | CS_VREDRAW;
pHelloClass->lpfnWndProc = HelloWndProc;
if (!RegisterClass( (LPWNDCLASS)pHelloClass ) )
/* Initialization failed.
* Windows will automatically deallocate all allocated memory.
*/
return FALSE;
LocalFree( (HANDLE)pHelloClass );
return TRUE; /* Initialization succeeded */
}
int PASCAL WinMain( hInstance, hPrevInstance, lpszCmdLine, cmdShow )
HANDLE hInstance, hPrevInstance;
LPSTR lpszCmdLine;
int cmdShow;
{
MSG msg;
HWND hWnd;
HMENU hMenu;
if (!hPrevInstance) {
/* Call initialization procedure if this is the first instance */
if (!HelloInit( hInstance ))
return FALSE;
}
else {
/* Copy data from previous instance */
GetInstanceData( hPrevInstance, (PSTR)szAppName, 10 );
GetInstanceData( hPrevInstance, (PSTR)szAbout, 10 );
GetInstanceData( hPrevInstance, (PSTR)szMessage, 15 );
GetInstanceData( hPrevInstance, (PSTR)&MessageLength, sizeof(int) );
}
hWnd = CreateWindow((LPSTR)szAppName,
(LPSTR)szMessage,
WS_TILEDWINDOW,
0, /* x - ignored for tiled windows */
0, /* y - ignored for tiled windows */
0, /* cx - ignored for tiled windows */
0, /* cy - ignored for tiled windows */
(HWND)NULL, /* no parent */
(HMENU)NULL, /* use class menu */
(HANDLE)hInstance, /* handle to window instance */
(LPSTR)NULL /* no params to pass on */
);
/* Save instance handle for DialogBox */
hInst = hInstance;
/* Bind callback function with module instance */
lpprocAbout = MakeProcInstance( (FARPROC)About, hInstance );
/* Insert "About..." into system menu */
hMenu = GetSystemMenu(hWnd, FALSE);
ChangeMenu(hMenu, 0, NULL, 999, MF_APPEND | MF_SEPARATOR);
ChangeMenu(hMenu, 0, (LPSTR)szAbout, IDSABOUT, MF_APPEND | MF_STRING);
/* Make window visible according to the way the app is activated */
ShowWindow( hWnd, cmdShow );
UpdateWindow( hWnd );
/* Polling messages from event queue */
while (GetMessage((LPMSG)&msg, NULL, 0, 0)) {
TranslateMessage((LPMSG)&msg);
DispatchMessage((LPMSG)&msg);
}
return (int)msg.wParam;
}
/* Procedures which make up the window class. */
long FAR PASCAL HelloWndProc( hWnd, message, wParam, lParam )
HWND hWnd;
unsigned message;
WORD wParam;
LONG lParam;
{
PAINTSTRUCT ps;
switch (message)
{
case WM_SYSCOMMAND:
switch (wParam)
{
case IDSABOUT:
DialogBox( hInst, MAKEINTRESOURCE(ABOUTBOX), hWnd, lpprocAbout );
break;
default:
return DefWindowProc( hWnd, message, wParam, lParam );
}
break;
case WM_DESTROY:
PostQuitMessage( 0 );
break;
case WM_PAINT:
BeginPaint( hWnd, (LPPAINTSTRUCT)&ps );
HelloPaint( ps.hdc );
EndPaint( hWnd, (LPPAINTSTRUCT)&ps );
break;
default:
return DefWindowProc( hWnd, message, wParam, lParam );
break;
}
return(0L);
}
Файл достаточно внушительной длины (больше полутора сотен строк). Тем не менее, там можно встретить много того, что есть и в куда более свежих программах для Windows на Си.
Вообще, первые версии Windows были просто оболочками, не имели никакой многозадачности, а целью их создания было не выпустить полноценную ОС, а облегчить работу с DOS. Несмотря на это, кое-что из появившегося в них либо претерпело значительное развитие и используется и до сих пор (например, GDI, много позже ставший GDI++ и использующийся и сейчас, появился с самых первых сборок Windows), либо ушло в историю, но оставило свой след (например, параметр hPrevInstance, использовавшийся в Win16 и всегда равный NULL в Win32).
Но всё же отличий Win16 от Win32 намного больше, чем может показаться неподготовленному пользователю. В Win32 очень многие операции стали значительно удобнее, а случайно уронить всю систему, запороть память другого приложения или заставить все остальные программы разом перестать работать теперь куда сложнее.
❯ Тесты на ПК
Теперь попробуем запустить тестовый стенд.

Насаживаем перемычку между контактами PS_ON и землёй на кросс-плате, запуская тем самым блок питания. Через несколько секунд компьютер проходит POST и начинает загружаться. Можно набирать WIN и пробовать запускать софт.

Всё успешно работает!

Чуть более сложный пример — отрисовка фигуры при её выборе в выпадающем меню.

И ещё одно приложение — показ фигуры заданного мышкой размера.
❯ Утилиты
Помимо инструментария для сборки в комплекте с SDK идёт несколько графических приложений. Само собой, никаких интерактивных редакторов кода с ним не поставлялось: для написания программы надо было открыть текстовый редактор, набрать там код, закрыть редактор, попробовать собрать приложение, затем при необходимости снова открыть редактор и исправить ошибки. И так очень много раз. Но всё же несколько интересных утилит тут имеется.

Первая из них — это редактор шрифтов.

Следом идёт редактор иконок.
В Windows 1.0 нет ни рабочего стола, ни панели задач. Единственное место, где видны эти иконки, так это при сворачивании приложения. Снизу видны открытые HELLO.EXE, MS-DOS Executive и калькулятор.

Вот так выглядит процесс редактирования.

И, наконец, самое важное. Это редактор диалогов.

Весь интерфейс программы создаётся в нём и сохраняется в виде двух файлов — ресурсов и заголовков.
❯ Что же в итоге?
Несмотря на то, что программирование под Win16 по сути умерло, некоторые порой всё же пробуют что-то написать. Кому-то это надо из любви к ретрокомпьютерам, кому-то — ради того, чтобы оживить какой-то древний, но очень нужный и приносящий очень много денег софт.
Но всё же если вдруг вас так и тянет попробовать что-то написать под древнюю ОС, то рекомендую начать опыты с Windows 95 или 98. Под них куда больше документации и примеров кода, а инструментарий намного более удобен.
Такие дела.
❯ Ссылки
Новости, обзоры продуктов и конкурсы от команды Timeweb.Cloud — в нашем Telegram-канале ↩
Перед оплатой в разделе «Бонусы и промокоды» в панели управления активируйте промокод и получите кэшбэк на баланс.
AlexeyK77
Программирование GUI на Win32 с созданием хэндлов окна и ручным разбором и обработкой очереди оконных сообщений никуда не делось. Просто никто не понимает на сегодня этих рун и что находится под капотом даже не то. что электронов, а даже первой делфи. На сегодняшний день понимание и умение писать гуй на чистом вин32 приравнивается к колдовству..