«...Одним из примеров громоздкой и, по мнению авторов, бесполезной надстройки является интегрированная система 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?

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

Изначально я хотел использовать 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-канале

Перед оплатой в разделе «Бонусы и промокоды» в панели управления активируйте промокод и получите кэшбэк на баланс.

Комментарии (1)


  1. AlexeyK77
    28.11.2025 08:35

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