Назад в будущее, или Hello World сегодня и тридцать лет назад
Так как сегодня исполняется 30 лет ожиданию знаменательного будущего, обещанного нам в эпохальной киноэпопее «Назад в будущее», мы решили тоже немножко вспомнить, как раньше выглядели наши продукты и сравнить с тем, что мы имеем сегодня.
Наш отдел работает с разработчиками и один из наших ключевых продуктов на сегодня – это Windows 10, точнее развитие экосистемы вокруг нашей новой операционной системы и, в частности, создание универсальных приложений.
Поэтому мы решили немножко перенестись в прошлое и посмотреть, как изменились инструменты разработки, примеры кода и SDK за 30 лет. А что может быть более показательным, чем сравнить создание оконного приложения “Hello World”?
От Windows 1.0 к Windows 10
Летом к выходу Windows 10 наши друзья из компании OnTheHub подготовили замечательную инфографику, из которой можно проследить прогресс самой операционной системы.
Уверен, что большинство хабрачитателей знакомы со значительной частью этой истории на собственном опыте, поэтому давайте смотреть, что же там было с кодом?
Windows 1.0 SDK
SDK для первой версии Windows распространялась на семи (7) пятидюймовых флоппи-дисках.
Как Википедия цитирует Чарльза Петцольда, «оригинальная версия программы Hello World в Windows 1.0 SDK носила слегка скандальный характер. Файл HELLO.C был около 150 строк длинной, и еще примерно 20 строк занимал ресурсный скрипт HELLO.RC… Программисты-ветераны часто морщатся в ужасе или наоборот смеются, сталкиваясь с той hello-world программой для Windows.» Код программы приведен ниже.
Десять лет назад Чарльз вспоминал соответственно 20-летний юбилей выхода SDK и написал очень «вкусную» для любителей истории статью о своих впечатлениях и попытках написать новую программу двадцать лет спустя.
В то время IBM, Digital Research и Microsoft выпустили SDK для своих соответствующих систем. Они стоили примерно по $500, все были достаточно сложны, но, честно говоря, больше всех среди них выделялась Windows. Она не была идеальной, но создавалось впечатление, что Microsoft действительно хотела, чтобы независимые разработчики ПО, как мы тогда, инвестировали в написание приложений для Windows.
На семи дискетах размещались:
- Утилиты – 2 дискеты с установочными и исполняемыми файлами
- EXEHDR, IMPLIB, LIB, LINK4, MAKE, MAPSYM, RC, RCPP, SYMDEB, WINSTUB
- DIALOG, FONTEDIT, HEAPWALK, ICONEDIT, SHAKER, SLAPJR
- Библиотеки и include-файлы, включая знаменитый Windows.h на 80Kb! – тоже 2 дискеты
- Символьные файлы и библиотеки для отладки – 1 дискета
- Примеры кода – 2 дискеты, включавшие 13 приложений в исходниках:
- HELLO – создание окон,
- TYPE – ввод с клавиатуры и отображения текста,
- SHAPES – работа с меню и рисование фигур в окне,
- TRACK – работа с мышью (вы же помните, что мышь только начинала входить в обиход и массовому пользователю была не знакома?),
- FONTTEST – создание диалоговых окон и обработка пользовательского ввода, работа со шрифтами,
- TEMPLATE – пустое приложение как основа для все остальных
- MAPMODES – разные режимы отображения данных с изменением положения и размера,
- SAMPLE – тоже самое, что TEMPLATE, но с дополнительными пунктами меню (File, Edit) и стандартными диалоговыми окнами,
- CLOCL – аналог приложения с часами в самой системе, показывающий, как работать с битмапами,
- CARDFILE – аналог приложения с картотекой из Windows, работа в полноэкранном режиме,
- MUZZLE – приложение на паскеле, демонстрирующее создание работу с окном.
Все примеры, кроме последнего, написаны на Cи, иногда со вставками на ассемблере.
Утилиты «включали специальную версию Microsoft C Compiler 3.0, а также два толстых мануала на примерно 1000 страниц в сумме, описывающих все, что вам нужно было знать для написания Windows-приложений».
Говоря о стоимости ($500), Чарльз пишет, что самую большую ценность представлял заголовочный файл на 80K, что было феноменально на то время. Фактически, он определял программный интерфейс (API) для работы с Windows, включая кучу новых типов данных, констант, структур и описание всех функций, которые были доступны вашим приложениям (около 400 всего). И все было отлично документировано в руководствах.
Windows 1.0: HELLO
«Проект», как бы мы сказали сегодня, а тогда просто набор файлов с Make-файлом для сборки включает три файла кода:
Заголовочный файл HELLO.h
/* String table constants */
#define IDSNAME 100
#define IDSABOUT 200
#define IDSTITLE 300
/* 'About' dialog box resource id */
#define ABOUTBOX 1
/* icon name */
#define HELLOICON 1
Файл программы HELLO.c
/* Hello.c
Hello Application
Windows Toolkit Version 1.03
Copyright (c) Microsoft 1985,1986 */
#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);
}
Смотря на него, вы как раз можете понять, почему Петцольд высказался соответствующим образом.
Ресурсный файл HELLO.rc
#include "style.h"
#include "hello.h"
HELLOICON ICON hello.ico
STRINGTABLE
BEGIN
IDSNAME, "Hello"
IDSABOUT, "About..."
IDSTITLE, "Hello Windows!"
END
ABOUTBOX DIALOG 22, 17, 154, 75
STYLE WS_POPUP | WS_DLGFRAME
BEGIN
CTEXT "Microsoft Windows" -1, 0, 5, 154, 8
ICON "hello" -1, 9, 23, 0, 0
CTEXT "Hello application" -1, 0, 14, 154, 8
CTEXT "Version 1.03" -1, 30, 34, 94, 8
CTEXT "Copyright 1985,1986, Microsoft Corp." -1, 0, 47,154, 9
DEFPUSHBUTTON "Ok" IDOK, 61, 59, 32, 14, WS_GROUP
END
Как вы можете видеть по исходникам, на самом деле это не совсем тривиальное приложение (на 170+ строчек), выводящее в заголовке или внутри приложения простую фразу. В действительности оно было несколько сложнее: в нем демонстрируется также работа со строками из ресурсного файла и также добавляется About-пункт в системное меню, запускающий диалоговое окно при выборе элемента, причем уже с использованием callback-функции для обработки результата.
В прошлом году Чарльз написал еще одну статью на тему того самого “Hello World”, в которой описывает свои впечатления от изучения программы в 1985 и мысли на тему того, как можно было бы ее переделать, чтобы сделать более понятной простым смертным. Спустя три года, в 1988 выйдет его первая книга “Programming Windows”.
Windows 10 SDK
За тридцать лет программные системы прошли огромный путь. Драматически выросли сложность и количество внутреннего кода. Инсталлятор современной операционной системы может занимать целый DVD-диск с гигабайтами информации. Сопоставимо весят инструменты разработки и SDK со всеми необходимыми библиотеками.
Например, в состав SDK для Windows 10 входят:
• Заголовочные файлы, библиотеки и метаданные (с учетом версий!)
• Windows-контракты (метаданные для Windows Runtime) с соответствующими платформенными файлами
• Расширения Windows-платформы — SDK для Windows Desktop, Windows Mobile, Windows IoT и Windows PPI.
• WindowsApp.lib – общая библиотека для универсальных приложений
• Объединенная CRT
• Windows App Certification Kit
• Обновления DirectX
• Windows Performance Toolkit
• .NET Framework 4.6 SDK
• Windows Accessibility Tools
• Ссылки на примеры кода и документацию
За многие годы относительно простые утилиты для компиляции и сборки приложений превратились в удобную среду Visual Studio для эффективного написания приложений, работы с кодом, отладки, обеспечения командной работы – еще одна огромная новинка по сравнению с тем, как программы создавали тридцать лет назад.
Дискеты во многом заменила доставка через интернет. Например, скачивая SDK отдельно от Visual Studio, вы сначала загрузите небольшой загрузчик, который весит как половина тех семи дискет, а потом вам предложат скачать еще около 700Мб установочных файлов. Эмулятор для Windows 10 Mobile – это еще 1.5Гб сверху.
По мере развития API вскорости стало очевидно, что всю документацию невозможно уместить в разумное количество бумажных томов. Было время, когда документация для Windows поставлялась на отдельном CD/DVD, однако, сейчас она доступна через интернет и, соответственно, сайт MSDN. Причем многие разделы документации переведены на широко распространенные языки, включая русский.
Примеры кода можно найти в MSDN Code Gallery или, как это принято в сообществе разработчиков сегодня, на GitHub. Интересный момент, который сегодня можно наблюдать во многих IT-сообществах, — это участие сообщества в формировании актуальных примеров и внесении правок и рекомендаций в документацию.
Интересно, мог ли кто-то предсказать подобные изменения тридцать лет назад?
Windows 10: Hello World
Я решил попробовать написать своего рода аналог “Hello World”-приложения под Windows 10, используя новую платформу UWP и язык JavaScript (как наиболее близкий мне). Вообще это такой интересный момент, что если 30 лет назад основным языком был чистый Си и почти все примеры были написаны на нем, то сегодня это не только С/С++, но также и такие «новые» языки, как C#, VB и JavaScript (и даже на ObjC можно писать для UWP!).
Первое, что бросается в глаза – это переход от совокупности файлов к проекту, содержащему значительно большее количество иконок, и, помимо ресурсных файлов и файлов с кодом приложения, также отдельно большое описание самого приложения и его настроек (манифест).
Стоит также отметить, что процедура сборки «описывается» в настройках самого проекта (своего рода замена make-файлам). И, в отличие от времен тридцатилетней давности, она стала существенно более комплексной. Если бы я писал код на С++ мне бы пришлось вместо безальтернативных 16bit выбирать сборку под x86, x64 или ARM, не говоря уже о множестве других параметров, которые обычно заботливо прячутся за банальным нажатием F5 или Shift+F5.
Ресурсний файл resources.resjson
В случае проекта на JavaScript ресурсные файлы (с автоматической поддержкой локализации!) представляют собой обычные JSON-файлы:
{
"idsname" : "Hello",
"idsabout" : "About...",
"idstitle" : "Hello Windows!",
"aboutbox.text" : "Microsoft Windows\nHello application\nVersion 10.03\nCopyright @2015, Microsoft Corp.",
"aboutbox.button" : "Ok!"
}
Тридцать лет назад не было ни JavaScript, ни XML, ни JSON.
Файл разметки страницы default.html
Хотя приложение можно «собрать» из кода, значительную часть интерфейса теперь можно описать более простым и наглядным декларативным способом. Причем тут же можно подключить связывание данных, в том числе с ресурсами:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>HelloWorld</title>
<!-- WinJS references -->
<link href="WinJS/css/ui-dark.css" rel="stylesheet" />
<script src="WinJS/js/base.js"></script>
<script src="WinJS/js/ui.js"></script>
<!-- HelloWorld references -->
<link href="/css/default.css" rel="stylesheet" />
<script src="/js/default.js"></script>
</head>
<body class="win-type-body">
<nav>
<a href="#" id="aboutMenu" data-win-res="{textContent: 'idsabout'}"></a>
</nav>
<p data-win-res="{textContent: 'idsname'}"></p>
</body>
</html>
(Автоматическое) связывание данных – огромная плюшка современной разработки.
Файл с логикой приложения default.js
Основная логика простого приложения по-прежнему может быть сосредоточена в одном файле (в более сложных примерах, как и тридцать лет назад вы, скорее всего, начнете декомпозировать на разного рода модули).
(function () {
"use strict";
var app = WinJS.Application;
var activation = Windows.ApplicationModel.Activation;
var appView = Windows.UI.ViewManagement.ApplicationView.getForCurrentView();
// Called on app activation
app.onactivated = function (args) {
// Initiate resources and UI -- all joined into a chain of promises
var promise = new WinJS.Promise.join([
WinJS.Resources.processAll(),
WinJS.UI.processAll()
// Add basic logic
]).then(function () {
// Update window title
appView.title = WinJS.Resources.getString('idstitle').value;
// Add click listener for menu "about" item
document.getElementById("aboutMenu").addEventListener("click", (e) => showAboutDialog());
});
args.setPromise(promise);
};
// Show"About" MessageDialog
function showAboutDialog() {
var messageDialog = new Windows.UI.Popups.MessageDialog(
WinJS.Resources.getString('aboutbox.text').value,
WinJS.Resources.getString('idsabout').value);
// Dummy event handler for commands in dialog box
var aboutHandler = (command) => { return true; };
messageDialog.commands.append(
new Windows.UI.Popups.UICommand(WinJS.Resources.getString('aboutbox.button').value, aboutHandler));
messageDialog.showAsync();
}
app.start();
})();
Код приложения стал раза в четыре короче (я тоже добавил немного комментариев и пустых строк), но самое главное преимущество развития технологий – это сильно возросшая выразительность API. Фактически, многие вещи, которые раньше надо было делать «ручками» сегодня прячутся за удобные вызовы системных или библиотечных функций.
Синтаксический сахар вроде стрелочных функций и возможности использовать promise сильно облегчают написание кода и его читаемость. К слову, существенное изменение – это переход к асинхронной модели кода, отсюда промимы и async.
Конечно, мое «обновление 30 лет спустя» не является дословным, но, думаю, общая динамика изменений в подходах создания приложений вполне прослеживаема. Сегодня это выглядит как совершенно новый API, хотя то и дело, все-таки проскакивают уши старого лампового Win32 Windows.h.