В нулевых среди геймеров было распространено поверье: мол чем больше памяти в видеокарте, тем она круче.
Конечно в нулевых объем VRAM уже особо не решал. Видяшки устаревали буквально за год-два, даже топовая GeForce 4 Ti уже в 2004 году почти полностью потеряла свою актуальность из за того, что новые игры активно использовали SM 2.0, а GF4 поддерживал только SM1.1.
Объем VRAM удваивался почти каждый год. В 2001 нормой было ~32МБ, в 2002-2003 чаще всего можно было встретить 64-128МБ, начиная с 2004'ого пошли видеокарты с 256МБ VRAM, а к 2010'ому 2ГБ DDR3 памяти получали уже бюджетки типа GT430. Примерно в 2013-2014 стремительный рост производительности и функционала десктопных GPU прекратился, благодаря чему старые видеокарты живут уже не год-два, а чуть ли не по 5 лет.
Однако откуда пошло это поверье? Из 80-х и 90-х! Дело в том, что первые видеокарты были исключительно 2D. Состояли они из знакогенератора, небольшого объема VRAM и RAMDAC. Знакогенератор занимался задачами вывода консоли в так называемом текстовом режиме (в нем работает DOS, Volkov Commander и т.п.), оперируя символами и атрибутами, в то время как RAMDAC выводил "выхлоп" знакогенератора на экран. При переключении в графический режим, RAMDAC переставал выводить "выхлоп" знакогенератора и отображал на мониторе так называемый буфер экрана (фреймбуфер).
От размера распаянной VRAM и зависело насколько большой и "цветастый" видеорежим может выдать RAMDAC (речь про VGA, EGA/CGA мы опустим) - бюджетные карточки с 512КБ VRAM могли рассчитывать на 8-битное (256 цветов) изображение в разрешении ~800x600 (но при этом 16-битное в 320x240), в то время как более дорогие модели с 2МБ могли выводить чуть ли не HD в 16 битах! Именно поэтому считалось что чем больше видеопамяти в видяхе, тем она круче.
Хотя конечно крутость 2D карточек определялась не только видеопамятью. Помимо VRAM, роль играла производительность "GPU" в видео режимах с большим разрешением и высокой битностью цвета (некоторые лагали, из за чего тормозили и игрушки - хотя процессор вполне вывозил), а в 90-х - поддержка DirectDraw.
Как мы с вами помним, первые видеокарты ничего не ускоряли (ну, кроме знакогенератора) и тупо выводили картинку на экран. Ее формированием занимался процессор. Но в 90-х появилось понятие видеоускориеля - видеокарты, которая может разгружать процессор путем выполнения базовых операций - рисование аппаратного курсора, быстрая отрисовка изображений на дисплей (блиттинг), рисование примитивов типа линий/прямоугольников и другие полезные операции. DirectDraw позволял использовать эти возможности без особого геморроя, оперируя простой концепцией "поверхностей", благодаря чему использовался в сотнях игр под Win9x. HoMM, Fallout, GTA, Warcraft - все эти игры юзали DD в качестве графического API.
После DDraw появились первые массовые 3D-видеокарты, а затем 2D стало частным случаем 3D. Именно поэтому большинство современных интерфейсов и 2D игр работает с использованием именно 3D-ускорителя.
Доля Windows на рынке десктопных ОС впервые просела ниже 60 % по всему миру. Об этом пишут со ссылкой на свежую статистику сервиса StatCounter. Раньше система Microsoft уверенно держала намного большую часть рынка, а теперь позиции заметно сдают. На фоне этого конкуренты вроде Mac OS и Linux понемногу отъедают свою долю.
С развитием ИИ отпал смысл копить специфические знания. Яркий пример - мусорные знания о Windows (многих это удерживало на этой ОС). ИИ настолько упростил освоение операционных систем, что люди теперь без проблем переходят на нормальные ОС.
Накопительное обновление KB5095093 для Windows 11 24H2 и 25H2. Оно необязательное — само не прилетит, надо руками тыкнуть «Проверить наличие обновлений» в Центре обновления Windows.
Что реально чинит:
Проводник перестаёт тупить. Раньше вкладка «Главная» разом дёргала данные из закреплённых папок, облака и недавних файлов — и от этого зависала. Теперь схему перестроили, окно открывается сразу, без фонового прелоада, который жрал память.
OneDrive больше не бесит. Ярлык в Проводнике в режиме администратора наконец реагирует на клик, а не делает вид, что тебя не существует.
Откат системы одной кнопкой. Если после кривого обновления всё легло — можно вернуться к последней автоматической точке восстановления, не теряя файлы и настройки. Раньше это был квест с ручной диагностикой.
Виджеты перестали выпрыгивать от твоего дыхания. Панель больше не раскрывается сама при наведении курсора, уведомления свёрнуты в счётчик, а не сыпятся плашками.
Bluetooth починили. Меньше проблем с переключением микрофона на гарнитурах, AirPods находятся быстрее, Beats Studio Pro больше не тупит с микрофоном.
Пауза обновлений через календарь — можно отложить нежеланное обновление на срок до 35 дней, а не воевать с планировщиком заданий.
Как поставить, по шагам:
Пуск → Параметры → Центр обновления Windows.
Нажми «Проверить наличие обновлений».
Если видишь KB5095093 — жми «Загрузить и установить» (само не поставится, это preview-патч).
Перезагрузи компьютер.
Если не появилось — не паникуй, накатывается не всем сразу, но по расписанию попадёт в обязательное июльское обновление.
Один нюанс: это preview-версия, то есть часть фишек может доехать не сразу и не у всех одинаково. Но Проводник и откат системы уже подтверждены пользователями — работает.
Автор, как и наверное большинство разработчиков, считал Golang всего лишь новомодной корпоративной игрушкой, призванной подсадить широкие программисткие массы на очередную технологию «корпорации добра» — создавался этот язык внутри Гугла и для задач Гугла, которые разумеется сильно отличаются от обывательских.
Поэтому когда мне показали работу Golang с WinAPI «из коробки» я был сильно удивлен — в более серьезных языках вроде C/C++ работа c внутренностями Windows всегда выглядела куда более монструозной. Так и родилась эта замечательная статья.
Что мы будем в этот раз творить:
Desktop-приложение с настоящим интерфейсом, с учетом реалий Windows, которое запустит встроенный вебсервер, с методом REST API на ассемблере.
Еще будет загрузка графического файла и установка его в качестве обоев — через WinAPI. Плюс небольшой обход файрвола, чтобы не показывался вот этот раздражающий экран с предупреждением:
Он всегда меня бесил, а то что меня бесит — я отключаю.
Надеюсь описанное в статье удивит даже опытных разработчиков на Golang.
Собственно так выглядит наш сегодняшний герой в действии:
Обратите внимание на отключенные кнопки «закрыть» и «развернуть» — даже это оказалось не так просто сделать на чистом WinAPI
Я использовал последнюю на момент написания версию 1.22.5, но язык столь бурно развивается, что не удивлюсь если выйдет более новая версия еще до завершения статьи.
Открытый проект в Visual Studio Code с установленным плагином для Golang
Теперь самое интересное:
для сборки проекта использовались не обычные Makefile и не шелл-скрипты — так характерные для проектов на «гошечке», а целая отдельная внешняя система сборки — Magefile.
Ставится она множеством разных способов, я использовал вот такой:
Если сборка прошла успешно, в текущем каталоге будет файл ungoogled-go.exe, который можно свободно перемещать и запускать на пользовательских компьютерах — он полностью статичный и не зависит от установленного Golang.
Опционально можно запустить:
mage generate
Этой командой запустится генерация файлов add.s и stub.go — для метода на ассемблере. Стоит также отметить, что конечная и отладочная сборка немного отличаются, разделение происходит путем проброса параметра:
-X main.DebugMode=false
Которым изменится значение глобальной переменной — флагом отладочного режима, который в свою очередь немного влияет на поведение программы.
Теперь начинаем разбираться, как же оно все работает.
Невероятный факт № 5668 : не каждый Windows-программист знает как скомпилировать программу из консоли.
Приложение Windows
Если попробовать собрать и запустить в Windows классический «Hello world» на C:
#include <stdio.h> int main() { printf("Hello, World!"); return 0; }
Вместо ожидаемого пустого графического окна запустится страшная черная консоль как на снимке выше. Это происходит потому что в Windows для графических программ используется другая точка запуска (entry point):
Every Windows program includes an entry-point function named either WinMain or wWinMain.
И если уж жизнь вас заставила разрабатывать на Go под Windows, еще и с графическим интерфейсом, то стоит «гошечке» об этом сообщить, добавив флаг в параметры ldflags.:
-H windowsgui
Целиком это выглядит так:
go build -ldflags "-H windowsgui"
Помимо этого, я указываю режим сборки exe:
-buildmode=exe Build the listed main packages and everything they import into executables. Packages not named main are ignored.
Для того чтобы получить в итоге сборки один большой и переносимый запускаемый exe файл.
Так выглядит «официальный Hello World» на C++ и WinAPI
Golang и WinAPI
Стоит пояснить читателям, в чем вообще заключается сложность работы с WinAPI. Для примера возьмем официальный «Hello world» на C++ под Windows:
А все потому что 90% кода даже в столь простом приложении не имеют никакого отношения к C++, а являются структурами, макросами или функциями самого WinAPI.
От C++ тут только примитивные типы (int) и управляющие конструкции (case, while).
Поэтому задача как-то серьезно взаимодействовать с WinAPI (дальше чем разовый вызов какой-то функции) — всегда была, есть и будет сложной. А разработка под Windows является отдельной специальной дисциплиной, чемпионы которой запросто могут забыть обычный C/C++ вообще и всю разработку (даже серверную) вести на инструментах WinAPI.
Но вернемся к нашей «гошечке».
Go далеко не C++ и является экзотикой в мире Windows-разработки, по крайней мере за пределами кампусов Google.
Но внезапно оказалось, что поддержка WinAPI в нем очень даже неплоха.
Взгляните как выглядит вызов WinAPI функции для установки обоев на Golang:
И оно даже работало. Но только объем кода очень быстро вырос до былинных размеров и никак не влезал в масштаб статьи.
Поэтому от такого подхода пришлось отказаться, оставив лишь работу с системным треем.
Все остальное я отдал на откуп готовым библиотекам. В частности построение окон и обработку событий были реализованы через библиотеку Windigo. — хотя это по-сути лишь набор готовых биндингов для функций WinAPI.
Вот так выглядит в работе демо-приложение на Windigo:
Собственно тут показаны все основные радости Windigo, доступные без долгих часов камлания над документацией
func main()
Запуск приложения Go согласно спецификации начинается с функции func main() в пакете main:
A complete program is created by linking a single, unimported package called the main package with all the packages it imports, transitively. The main package must have package name main and declare a function main that takes no arguments and returns no value.
Первая же строка внутри main() нашего проекта нуждается в пояснении:
runtime.LockOSThread()
Этот вызов из пакета runtime нужен для того чтобы все goroutines (легковесные потоки Go) выполнялись в отдельных системных потоках каждый.
В нашем случае это необходимо для взаимодействия с системным потоком, отвечающим за графический интерфейс:
A goroutine should call LockOSThread before calling OS services or non-Go library functions that depend on per-thread state.
Следующим шагом происходит вызов функции, отвечающей за построение графического интерфейса:
mainWindow = newMyWindow()
Разберем как формируются и связываются графические элементы, в нашем проекте за это отвечает функция:
С помощью константы co.CS_NOCLOSE отключается кнопка закрытия окна:
CS_NOCLOSE 0x0200 Disables Close on the window menu.
Ну и дальше задается заголовок и размеры создаваемого окна — тут все просто. Зато сложно чуть ниже:
if DebugMode == "false" { // ID of icon resource, see resources folder // does not work in debug mode opts = opts.IconId(101) }
Тут указывается иконка окна в виде числового ID ресурса, файл с ресурсами minimal.syso был взят из демо-проекта Windigo:
A syso file, ready to use, that contains the icon and the manifest. Just place it at the root folder of your project. You can load the icon using the resource ID 101.
Следующим шагом происходит вызов сложной цепочки инициализации окна:
// create main window wnd := ui.NewWindowMain(opts)
В конце которой вызывается известная функция WinAPI CreateWindowEx, используемая для создания нового графического окна.
Если пользователь нажал кнопку «Yes» (т.е подтвердил операцию), происходит завершение работы HTTP-сервера:
if httpSrv != nil { if err := httpSrv.Close(); err != nil { fmt.Printf("HTTP close error: %v", err) } }
Закрытие главного окна приложения:
me.wnd.Hwnd().DestroyWindow()
И завершение работы:
os.Exit(0)
Следущим шагом из функции main мы загружаем иконку, используемую в трее:
var trayIcon win.HICON
// Load icon // in debug mode, there are no resources available, so we need to load // icons from FS if DebugMode == "false" { trayIcon = win.HICON( win.GetModuleHandle(win.StrOptNone()).LoadImage( win.ResIdInt(101), co.IMAGE_ICON, 16, 16, co.LR_DEFAULTCOLOR, )) } else { trayIcon = win.HICON( win.GetModuleHandle(win.StrOptNone()).LoadImage( win.ResIdStr("gopher.ico"), co.IMAGE_ICON, 16, 16, co.LR_DEFAULTCOLOR|co.LR_LOADFROMFILE, )) }
Используется разная логика для режима отладки и запуска финального бинарника, потому что в готовом приложении иконка будет находиться в ресурсах — специальном файле, упакованном вместе с приложением.
А во время отладки либо запуска вроде:
go run main.go
ресурсов не будет, поэтому придется загружать иконку непосредственно с файловой системы.
Дальше мы настраиваем дополнительные обработчики, в первую очередь добавляем обработку на закрытие главного окна приложения:
// close systray on main window destroy mainWindow.wnd.On().WmDestroy(func() { if tray != nil { tray.Dispose() } })
При закрытии главного окна, произойдет и автоматическое закрытие трея — не будет эффекта потерянной инонки, когда приложение уже закрылось, а его иконка до сих пор отображается в трее.
Дальше мы вешаем обработчик на активацию главного окна, для того чтобы поймать момент полной готовности и отображения и запустить сервер:
var configured = false // check for action that runs only once
mainWindow.wnd.On().WmActivate(func(p wm.Activate) { // we need to run our handler logic only once at start if configured { return } configured = true go startServer() })
Столь отложенный старт необходим для большей интерактивности:
метод startServer () пишет сообщения в «графический лог», если он не будет полностью инциализирован — сообщения пропадут.
Проблема заключается в том что этот обрабочик будет запускаться и на повторную активацию (например после сворачивания окна) — чтобы логика не отрабатывала повторно стоит проверка на переменную configured, которая работает в качестве флага «инициализация завершена».
Ну и сам запуск HTTP-сервера происходит через отдельный поток, чтобы не блокировать работу обработчика:
go startServer()
Последним мы добавляем инициализацию трея по событию создания главного окна:
// action on windows create // runs once mainWindow.wnd.On().WmNcCreate(func(p wm.Create) bool { // create systray tray := systray.CreateSysTray() // set handler on icon click - just focus on main window systray.SetTrayClickHandler(func() { systray.ShowWindow(uintptr(mainWindow.wnd.Hwnd()), systray.SW_SHOWNORMAL) })
tray.SetIcon(uintptr(trayIcon)) tray.SetTooltip("Tiny Server: click me to show main window.")
return true })
Нужно это по той простой причине что только на этой стадии появляется настоящий window handle:
mainWindow.wnd.Hwnd()
С помощью которого возможно взаимодействовать с окном:
systray.ShowWindow(uintptr(mainWindow.wnd.Hwnd())
До этого момента (т.е. до вызова обработчика) HWND нашего окна будет пустым. Наконец финальный шаг в функции main() это запуск блокирующего цикла обработки cобытий:
mainWindow.wnd.RunAsMain()
После вызова этого метода, приложение начнет реагировать на события вроде нажатия клавиш или кликов мышкой.
Теперь разберем работу с системным треем — как пример работы с чистым WinAPI.
Вот так это выглядит в Windows 11
Работа с системным треем
Разумеется есть способ проще:
взять одну из готовых библиотек, тем более что есть универсальные — сразу для Windows, MacOS и Linux и всей кучи разных сред окружения.
Но фана ради и пользы обучения для, был избран более сложный путь — закат солнца вручную взаимодействие с системным треем только через WinAPI.
Все функции, относящиеся к этой задаче находятся в пакете systray:
Благодаря этому флагу можно создать невидимое окно и заставить его обработчик принимать сообщения системного трея:
// this is main window function // see https://learn.microsoft.com/en-us/windows/win32/api/winuser/... func wndProc(hWnd uintptr, msg uint32, wParam, lParam uintptr) uintptr { switch msg { case TrayIconMsg: nmsg := LOWORD(uint32(lParam)) // if user clicked on tray icon if nmsg == WM_LBUTTONDOWN { // if callback function exist if trayClickCallback != nil { trayClickCallback() } } case WM_DESTROY: PostQuitMessage(0) default: r, _ := DefWindowProc(hWnd, msg, wParam, lParam) return r } return 0 }
Да, это все тот же старый добрый WndProc , описанный выше в статье и хорошо знакомый любым Windows-разработчикам.
Блок внутри:
.. case TrayIconMsg: nmsg := LOWORD(uint32(lParam)) // if user clicked on tray icon if nmsg == WM_LBUTTONDOWN { // if callback function exist if trayClickCallback != nil { trayClickCallback() } } ..
Отвечает за обработку сообщений системного трея.
Функция, которая отрабатывает по событию клика левой кнопки мыши (WM_LBUTTONDOWN) выглядит вот так:
Словом, уровень интеграции с WinAPI и легкости его применения поражает воображение.
Лог с интерфейсом
Лог
Он же «журнал работы» — отображает события в приложении в центральной части рабочей области. Логика записи выглядит следующим образом:
// appends to UI log func appendToLog(message string) { // could be no window yet if mainWindow == nil || mainWindow.txtName == nil { fmt.Println(message) return } // window could be not visible yet // and attempt to add message will raise an exception if !mainWindow.txtName.Hwnd().IsWindowVisible() { fmt.Println(message) return } // get current text txt := mainWindow.txtName.Text() // to avoid overflow if len(txt) > 512 { txt = "" } b := strings.Builder{} b.WriteString(txt) // append existing text b.WriteString(message) // append new message b.WriteString("\r\n") // this is Windows, so \r\n, not \n ! // and finally set updated text (yep, there is no append, sorry) mainWindow.txtName.SetText(b.String()) }
Кроме достаточно очевидного пропуска записи в случае неполной инициализации, тут есть еще вот такая логика:
if !mainWindow.txtName.Hwnd().IsWindowVisible() { fmt.Println(message) return }
Нужно это потому, что CEdit не даст изменить текст внутри если сам компонент еще не отображается, а попытка вызова метода API изменения текста вызовет ошибку.
Также внезапно (хотя для кого как) оказалось что стандартный компонент Windows для ввода не поддерживает логику добавления (append) — только полную замену всего текстового блока:
// get current text txt := mainWindow.txtName.Text() // to avoid overflow if len(txt) > 512 { txt = "" } b := strings.Builder{} b.WriteString(txt) // append existing text b.WriteString(message) // append new message b.WriteString("\r\n") // this is Windows, so \r\n, not \n ! // and finally set updated text (yep, there is no append, sorry) mainWindow.txtName.SetText(b.String())
Поэтому с точки зрения современной разработки это выглядит как колхоз, но увы — таковы реалии WinAPI.
Встроенный HTTP-сервер
В составе Golang идет готовый встраиваемый HTTP-сервер (пакет «net/http»), с примитивами обработчиков для типовых действий.
С его помощью удалось минимальными силами реализовать весь тестовый функционал, метод инициализации и запуска встроенного HTTP-сервера выглядит вот так:
// firewall bypass does not work correctly in debug mode if DebugMode == "false" { server.AddAppFirewallRule() appendToLog("Added firewall rule..") } // create request multiplexer, see https://pkg.go.dev/net/http#ServeMux mux := http.NewServeMux() // test assembler method mux.HandleFunc("/asmtest", server.TestAsmMethod) // upload & set wallpaper image mux.HandleFunc("/upload", server.UploadHandler) // default handler mux.HandleFunc("/", server.IndexHandler)
// if this is production mode - bind to all interfaces if DebugMode == "false" { httpSrv = &http.Server{ Addr: ":8090", Handler: mux, } } else { // otherwise - bind to localhost (firewall bypass // does not work in debug mode) httpSrv = &http.Server{ Addr: "localhost:8090", Handler: mux, } }
appendToLog(fmt.Sprintf("Server started at %s", httpSrv.Addr)) // set logging handler server.SetMessageLogHandler(appendToLog) httpSrv.ListenAndServe() // here will be lock }
Разберем что тут происходит, первым шагом идет запись в лог:
// firewall bypass does not work correctly in debug mode if DebugMode == "false" { server.AddAppFirewallRule() appendToLog("Added firewall rule..") }
Как это работает подробно разобрано ниже, пока замечу что этот обход не работает в режиме отладки, поэтому тут и стоит такая странная на первый взгляд проверка.
Следующим шагом происходит инстанциация мультиплексора запросов:
// test assembler method mux.HandleFunc("/asmtest", server.TestAsmMethod) // upload & set wallpaper image mux.HandleFunc("/upload", server.UploadHandler) // default handler mux.HandleFunc("/", server.IndexHandler)
Т.е. по какой ссылке будет отвечать каждый обработчик.
Логика всех обработчиков разобрана чуть ниже, а пока пройдем дальше по логике инициализации HTTP-сервера:
// if this is production mode - bind to all interfaces if DebugMode == "false" { httpSrv = &http.Server{ Addr: ":8090", Handler: mux, } } else { // otherwise - bind to localhost (firewall bypass // does not work in debug mode) httpSrv = &http.Server{ Addr: "localhost:8090", Handler: mux, } }
Вся эта простыня нужна по той простой причине что в Golang нет тернаров, т.е нельзя сделать логику одной строкой вроде:
Addr = DebugMode? "localhost:8090" : ":8090"
Как это было бы в Java или Typescript.
Поэтому надо было либо делать отдельную функцию, отдающую адрес, внутри которой вставлять проверку на DebugMode, либо сделать как на примере выше — два повторяющихся блока.
Дальше по логике происходит установка обработчика логирования:
// set logging handler server.SetMessageLogHandler(appendToLog)
Со стороны пакета сервера он вызвается вот так:
// logs message with callback on UI func logMessage(message string) { if messageLogCallback != nil { messageLogCallback(message) } else { fmt.Println(message) } }
А вот так выглядит пример конечного использования:
logMessage(fmt.Sprintf("Background changed to %s", dst.Name()))
Наконец последним шагом происходит запуск самого HTTP-сервера:
httpSrv.ListenAndServe() // here will be lock
Обратите внимание что httpSrv объявлен как глобальная переменная:
var ( tray *systray.TrayIcon httpSrv *http.Server mainWindow *MyWindow //You can only set string variables with -X linker flag. From the docs: DebugMode = "true" )
Это нужно чтобы иметь возможность остановить HTTP-сервер при завершении работы (graceful shutdown):
if httpSrv != nil { if err := httpSrv.Close(); err != nil { fmt.Printf("HTTP close error: %v", err) } }
Обход файрвола
И не надо так подозрительно смотреть — речь про вполне себе документированное API, позволяющее пропустить вот такое откровенно дурацкое подтверждение:
Был добавлен еще в Windows 7 с официальной целью: выбешивать пользователей.
Как и в случае с интерфейсом, было принято волевое решение использовать готовый пакет с биндингами:
This is a package for controlling the Windows Filtering Platform (WFP), also known as the Windows firewall.
С его помощью вся логика свелась к вот такой простой функции, взятой из issue в Github проекта и немного переделанной:
Не буду детально расписывать эту довольно сложно воспринимаемую логику — слишком уж тут много специфики WFP, читать устанете.
Если есть желание погрузиться в тему — вам в помощь вот такая замечательная статья от авторов пакета, где расписано в деталях внутренее устройство WFP и работа с его API.
Но если в кратце — тут происходит создание и применение нового правила фильтрации, которое разрешает входящие соединения для приложения, из которого выполняется вызов API.
Golang и ассемблер
Чтобы сразу закрыть все вопросы по поводу адекватности использования ассемблера из Go, вот вам небольшая цитата:
This example is taken from the AES package of the standard Go library. It makes use of Go Assembly to leverage Intel’s hardware support for AES, calling the AES-NI CPU instructions that can perform a “round” of encryption or decryption of the AES algorithm.
Да, как только начинается большая криптография и множественные вычисления на слабом железе — сразу с дальней полки достается пыльная книга по ассемблеру.
Разумеется не было открытием что язык, с самого своего начала имевший компилятор в нативный код умеет вызывать вставки на ассемблере. Открытием была легкость и простота с которой это делается.
Не задумывались почему маскоты Go и Plan 9 так похожи?
Еще одним открытием оказались торчащие уши Plan 9:
The assembler is based on the input style of the Plan 9 assemblers, which is documented in detail elsewhere. If you plan to write assembly language, you should read that document although much of it is Plan 9-specific
А разгадка проста — один из авторов Go когда‑то работал над Plan 9:
func main() { TEXT("Add", NOSPLIT, "func(x, y uint64) uint64") Doc("Add adds x and y.") x := Load(Param("x"), GP64()) y := Load(Param("y"), GP64()) ADDQ(x, y) Store(y, ReturnIndex(0)) RET() Generate() }
Это отдельная программа на Go, которая при запуске генерирует ассемблерный код в файле asm/add.s:
// Code generated by command: go run asm.go -out asmtest/add.s -stubs asmtest/stub.go. DONOT EDIT.
#include "textflag.h"
// func Add(x uint64, y uint64) uint64 TEXT ·Add(SB), NOSPLIT, $0-24 MOVQ x+0(FP), AX MOVQ y+8(FP), CX ADDQ AX, CX MOVQ CX, ret+16(FP) RET
А также заголовочный файл на Go в файле stub.go:
// Code generated by command: go run asm.go -out asmtest/add.s -stubs asmtest/stub.go. DO NOT EDIT. package ungoogled // Add adds x and y. func Add(x uint64, y uint64) uint64
Затем данные файлы линкуются с основным приложением, а вызов метода с ассемблерной вставкой выглядит вот так:
// a test API method to call function with Assembler inside func TestAsmMethod(w http.ResponseWriter, req *http.Request) {
Просто для демонстрации возможностей, к достаточно стандартному функционалу по загрузке файлов был приделан вызов WinAPI функции для смены обоев на рабочем столе.
Сама форма загрузки выглядит максимально стандартно:
Затем она зашивается в приложение с помощью go:embed:
//go:embed upload.html var uploadTemplate string
Т.е. во время запуска приложения, переменная uploadTemplate будет содержать HTML-шаблон выше для загрузки картинки — зашивание происходит во время сборки.
За загрузку картинки отвечает функция:
func uploadFile(w http.ResponseWriter, r *http.Request) { .. }
Внутри стандартная скучная логика разборки multipart-формы и обработки загруженного файла - ее нет смысла описывать, зато дальше происходит кое-что интересное:
// build full path to image imagePath, err := windows.UTF16PtrFromString(dst.Name())
Тут происходит формирование ссылки на UTF-16 строку, содержающую полный путь к загруженному файлу.
Затем эта ссылка используется для вызова API:
// call WinAPI to change wallpaper to just uploaded image _, _, err = procSystemParamInfo.Call(20, 0, uintptr(unsafe.Pointer(imagePath)), 0x001A) // check for errors, respond 500 if any if err, ok := err.(syscall.Errno); ok { if err != 0 { fmt.Println("Error :") fmt.Println(err) http.Error(w, err.Error(), http.StatusInternalServerError) return } }
Вызывается функция SystemParametersInfoW, которая на самом деле используется для очень большого количества разных действий:
Retrieves or sets the value of one of the system-wide parameters. This function can also update the user profile while setting a parameter.
Нужный нам для смены обоев actionName называется SPI_SETDESKWALLPAPER который и указывается при вызове.
Эпилог
Разумеется я такой не один и уже достаточномногоразработчиков по всему миру делятся своим опытом разработки на Go и WinAPI. Надеюсь эта статья также добавит читателям восторгов и позволит взглянуть на любимый инструмент под другим углом.
В общем, Python и Java мне кодить лень, но кодить на языке cmd ума мне хватило. Кто знает, я уже писал пост о игре "Magnet-Fishing", которую тоже создал на чистом batch. Сервис запустится на "любой капле воды", если там есть Windows.
В этой утилите есть многое:
Её функционал:
Простая и очень быстрая установка.
Множество инструментов в одном файле.
Красивое меню из рамок (не знаю, как вам, но мне нравится!).
Инструменты в ней:
Очистка папок Temp.
Очистка логов по нужному пути.
Секундомер формата 00:00.
Конвертер км/ч в м/с или наоборот.
TWR калькулятор до сотых чисел (0.00).
Прогноз погоды (никакой магии, это просто отличный wttr.in!).
Пинг-тестер по нужной ссылке и количеству пакетов (от 1 до 3).
Переносчик файлов нужного формата и нужного пути.
Проверка обновлений сервиса.
Весь батник весит всего лишь какие-то 12 кб, а в сжатом виде вообще так 4.25 кб. Делает больше, чем простые чистилки под 50 мб.
К тому же, там есть раздел "От автора", где можно скачать тот же "Magnet-Fishing" или "Batch files pack", или просто зайти на корень моего репозитория GitHub.
Вот такая "красота". Значки скрыты, лень было лишние файлы удалять
Несколько дней назад многие пользователи Windows 10 и 11 стали жаловаться на появление белого прямоугольника на рабочем столе, который не отображается в списке окон, который не пропадает при обновлении экрана.
На англоязычных сайтах не нашел решения проблемы, многие писали, что это Хром, советовали выключить какой-то флаг в Планировщике задач винды.
Я попробовал описанное, не помогло.
Procmon тоже не помог найти процесс, выдававший это окно.
Решил дедовским способом найти виновника, сел и последовательно стал "убивать" процессы.
Виновник нашелся довольно быстро, угадайте, кто? Ну конечно же, наш дорогой Microsoft Edge. Грохаем его процессы по очереди, начиная с нижнего, на третьем или четвертом окно закрывается и до перезагрузки уже не появляется.
Хз, поможет ли всем, но я проверил, 3 перезагрузки дали одинаковый положительный результат.