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

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

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


  1. AlexeyK77
    28.11.2025 08:35

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


    1. ohrenet
      28.11.2025 08:35

      На сегодняшний день понимание и умение писать гуй на чистом вин32 приравнивается к колдовству..

      А толку? Шеккелей в карманах это не прибавляет.


    1. snuk182
      28.11.2025 08:35

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


      1. LAutour
        28.11.2025 08:35

        С предыдущей работы, когда там лишнее выкидывали, мне достались книги по WinApi для Win3.1. Там все было расписано гораздо подробнее, чем в более новых книгах по тому же (суть не изменилась) WinApi для 32-битных Windows.


    1. Nikollor48
      28.11.2025 08:35

      Ну да, Win32 как был диким лесом, так и остался. Там реально без заклинаний в голову не укладывается, почему всё ещё так работает


    1. LAutour
      28.11.2025 08:35

      На сегодняшний день понимание и умение писать гуй на чистом вин32 приравнивается к колдовству.

      Для генерации кусков кода можно вполне использовать ИИ.


  1. dlinyj
    28.11.2025 08:35

    Спасибо за ностальгичную статью! Никогда не имел дел с первой виндой, и благодаря вашей статье всё же решил попробовать. Это реально круто и интересно!

    На фотографиях вижу знакомые платы ;). Рад, что пошли в дело :).


    1. MaFrance351 Автор
      28.11.2025 08:35

      Да, тоже был рад запустить. Спасибо за такой подгон.


  1. rabitagorgor
    28.11.2025 08:35

    Ради интереса загуглил, сколько стОит на авито эта самая ROCKY-328E-M4. Мда уж ;)


    1. MaFrance351 Автор
      28.11.2025 08:35

      Хех. На самом деле его заметно дешевле можно найти. Но надо будет специально мониторить объявления.

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

      В целом такие платы (если специально искать в течение пары месяцев) можно купить за пять-шесть тысяч.


    1. dlinyj
      28.11.2025 08:35

      Не надо пытаться найти конкретную модель, лучше поискать промышленные платы, промышленный компьютер, одноплатный компьютер и т.п. В целом ценник у них 3-6 тысяч. И они, не сказать, что особо кому-то нужны. Не супер редкость.


      1. Siemargl
        28.11.2025 08:35

        на х86 архитектуре довольно таки редкость. почти все на арм перешли


        1. MaFrance351 Автор
          28.11.2025 08:35

          почти все на арм перешли

          Потому-то на X86 сейчас и можно купить. Старые замещаются девайсами на ARM и постепенно списываются.


  1. NeoNN
    28.11.2025 08:35

    Windows 95/98 это уже не древняя ОС, а по сравнению с DOS+WIN1.0 вполне современная и уже работала на процессорах с Protected Mode, а не Real Mode, как все, что было под дос, с присущими этому болями с памятью, двухсекционными указателями (near/far) итд.


  1. anonymous
    28.11.2025 08:35


  1. Nikollor48
    28.11.2025 08:35

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


  1. AWE64
    28.11.2025 08:35

    А че там на электроне вайбкодить нельзя было???


  1. goga_77
    28.11.2025 08:35

    Блин. Я прочитал: Программирование Windows 10 в 2025 году. Не ожидал такого подвоха! Зачем сейчас писать программы под Windows1.0? Просто время своей жизни в пустую тратить.


    1. Siemargl
      28.11.2025 08:35

      Например, чтобы понимать, как писать программы, чтобы влезали в 256Кб памяти.

      Или сравнимый объем на сегодняшний день.

      А то уже совсем разучились, вон, даже проводник в 11 тормозит.


    1. nolirpaf
      28.11.2025 08:35

      зачем уметь вбивать гвоздь в стену, ведь есть специальные люди?


  1. ImagineTables
    28.11.2025 08:35

    Прикол! Я думал, API по пути от 1.0 к Win95 изменился до неузнаваемости, а оказалось, что в реальности больше изменился язык (паскалевское объявление стековых переменных вместо более позднего объявления по месту режет глаз).


    1. Siemargl
      28.11.2025 08:35

      Это типы параметров функции.

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


      1. ImagineTables
        28.11.2025 08:35

        А-а-а! Точно, типы параметров. Не по глазам пришлось.


    1. ohrenet
      28.11.2025 08:35

      Прикол! Я думал, API по пути от 1.0 к Win95 изменился до неузнаваемости, а оказалось,

      Это называется "обратная совместимость". О, да, для современных айтишников это "прикол".


      1. ImagineTables
        28.11.2025 08:35

        О, да, для современных айтишников это "прикол"

        И к чему это было сказано? Глядя на скриншоты Windows 1.0, трудно предположить, что под капотом привычная обработка сообщений и вывод графики через контексты устройств.


        1. AWE64
          28.11.2025 08:35

          насколько я понимаю, там есть свои заморочки, когда винда работает в real mode