love5an: (Default)
[personal profile] love5an
Почему "модные"?
Ну потому, что look-and-feel в стиле Windows 2000 в 2011 году не моден.


Начну издалека.

Стандартные win32-контролы(так называемые Common Controls)(обертками над которыми являются классы MFC, Windows.Forms, или других подобных GUI-тулкитов), начиная с кнопок и заканчивая всякими там TreeView, в винде реализованы в библиотеке comctl32.dll. И с этой библиотекой связан один очень неприятный момент.

Дело в том, что давным-давно, а именно с появлением Windows XP, Microsoft выпустила 6ю версию этой библиотеки, в которую добавила кучу свистелок и перделок("темы" и т.п.), связанных главным образом с отрисовкой(благодаря этому, новые версии винды выглядят не так уныло, как раньше, т.е. во времена Win95-2000). Но, к несчастью, 6я comctl32 стала немножко обратно-несовместимой с предыдущими версиями, и по этой причине, если мы линкуемся с этой библиотекой, или же подружаем ее динамически(через LoadLibrary etc.), винда по дефолту подгружает 5ю версию, и все стандартные виджеты в нашем приложении выглядят так же, как они должны были выглядеть 11 лет назад.

Заставить систему подключать нам как минимум 6ю comctl32 можно через так называемые "контексты активации". К несчастью для динамичного лиспового видения программ, единственный способ задать стандартный контекст активации, или создать новый довольно статичен по своей сути - это так называемые "манифесты". Манифест представляет собой xml-документ, в котором описываются какие-либо свойства приложения/библиотеки(assembly), считываемые Windows при ее загрузке.

Возвращаясь к теме постинга - одними из самых важных вещей, описываемых в манифестах, являются требования к библиотекам и COM-компонентам, от которых данное конкретное assembly зависит, и именно таким образом мы можем сказать системе, что нам нужна как минимум 6я версия библиотеки Common Controls.

Задать манифест для приложения можно двумя способами - первый, и наиболее предпочтительный - вкомпилировать его в секцию "ресурсов" PE, а второй - записать его в файл с именем "имя-приложения.exe.manifest" и положить рядом с исполняемым файлом программы. Со вторым способом связана одна небольшая хитрость - дело в том, что Windows кэширует манифесты такого рода, и это значит, что если программа раньше была без манифеста, и уже когда-нибудь запускалась, а потом нам вдруг приспичило манифест создать, то система не будет о нем знать до того, как мы изменим(обновим дату изменения) исполняемый файл нашей программы.

Я предпочитаю использовать первый способ и собирать рантаймы лиспов с уже включенным в них манифестом.

Как это делается? Ну для примера возьмем SBCL.
Предположим, манифест имеет такой вид:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>

<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
  <assemblyIdentity type="Win32" name="MyProgram"
                    processorArchitecture="x86" version="1.0.0.0" />
  <trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
    <security>
      <requestedPrivileges>
        <requestedExecutionLevel level="asInvoker" uiAccess="false"/>
      </requestedPrivileges>
    </security>
  </trustInfo>
  <dependency>
    <dependentAssembly>
      <assemblyIdentity type="Win32" name="Microsoft.Windows.Common-Controls" version="6.0.0.0"
                        processorArchitecture="x86" publicKeyToken="6595b64144ccf1df" language="*"/>
    </dependentAssembly>
  </dependency>
</assembly>      


Запишем его в файл с именем, ну, пусть "program.manifest".
После этого создаем файл описания ресурсов(пусть будет "program.rc"),

#include <winuser.h>
1 ICON program.ico // application icon
CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST program.manifest


и берем утилиту windres из тулчейна MinGW, с помощью которого мы компилируем этот файл:
windres -i program.rc -o resources.res -O coff

Далее мы складываем полученный файл в директорию путь_к_сорцам_sbcl/src/runtime/ и немного изменяем файл GNUMakefile, находящийся там же; в частности, строчку
OBJS = $(C_SRC:.c=.o) $(ASSEM_SRC:.S=.o) ${OS_OBJS}

меняем на
OBJS = $(C_SRC:.c=.o) $(ASSEM_SRC:.S=.o) resources.res ${OS_OBJS}

Ну и после этого собираем SBCL как обычно(кстати, вот если кому надо, бинарник(x86-32) из HEAD форка [livejournal.com profile] akovalenko, собранный таким образом, с иконкой от lisperati.com: http://rghost.ru/18554241).

Ну и напоследок, вот пример одной и той же программы(http://paste.lisp.org/display/124128) на Windows 7 со включенными Visual Styles(т.е. comctl32 >= 6.0.0.0)(справа) и без них(слева):

Date: 2011-08-19 05:49 pm (UTC)
wizzard: (Default)
From: [personal profile] wizzard
ээ, а как же переключение типов контролов в рунтайме в винформах?

Date: 2011-08-19 05:55 pm (UTC)
From: [identity profile] love5an.livejournal.com
1) Их можно только в одну сторону переключить, и только раз( Application.EnableVisualStyles)
2) Там хитрый хак, похоже такой же, какой я видел в каком-то перловом модуле - в отдельную dll'ку суется манифест, этот манифест потом в рантайме вытаскивается, и из него создается контекст. В винформах этот манифест в какой-то из ассемблей BCL, наверное. Ну, а нам в лиспах лепить отдельную dll, и таким макаром извращаться - это еще сложнее, чем вот так вот, как я описал.

Date: 2011-08-19 07:16 pm (UTC)
From: [identity profile] love5an.livejournal.com
А, ну и забыл сказал почему это хак то.
В том перловом модуле библиотека comctl сначала выгружалась, если она уже загружена, и потом загружалась снова, уже после создания контекста.

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

Date: 2011-08-19 07:34 pm (UTC)
From: [identity profile] love5an.livejournal.com
>и его реализация ну слишком накладно выйдет.
всмысле, его реализация вышла бы совсем дико накладно, если бы была двусторонняя(т.е. если бы можно было как включить контекст с 6й comctl, так и выключить его).

Date: 2011-08-19 07:55 pm (UTC)
wizzard: (Default)
From: [personal profile] wizzard
> если да, то это вообще совершенно дикий хак

Пересоздаются :)

Date: 2011-08-19 07:56 pm (UTC)
wizzard: (Default)
From: [personal profile] wizzard
там есть неск сценариев типа изменения Opacity с нулевой на ненулевую итд, которые вызывают пересоздание окон, помимо всего прочего.

и танцы на хендлах, да.

Date: 2011-08-19 08:57 pm (UTC)
From: [identity profile] maxim.livejournal.com
я бы все же гуи на винде делал через WTL
но CL так CL. кроме того что подрубить манифест там еще нужно окнам стили выставлять строковые (сабклассы), что бы контролы получались такими как в Explorer.exe.
Эти обертки тоже надо тогда обязательно понаписывать.

Date: 2011-08-22 02:57 am (UTC)
From: [identity profile] akovalenko.livejournal.com
Возможно, стоит обратить внимание на BeginUpdateResource / UpdateResource / EndUpdateResource: это "у себя дома" уместно использовать windres, а поставлять что-то с зависимостью от mingw toolchain я бы не стал.

(Примечание для SBCL: если натравить UpdateResource на исполняемый дамп, он откусит built-in core. Так что работать надо с чистым sbcl.exe, а когда туда засунут манифест, иконки, versioninfo и что ещё захотелось -- тогда уже дампить (примечание 2: распилить исполняемый дамп на runtime+core довольно просто, в том числе из лиспа)).

Date: 2011-08-29 02:20 pm (UTC)
From: [identity profile] love5an.livejournal.com
>а поставлять что-то с зависимостью от mingw toolchain я бы не стал.
Ну, да, это так. Но я как раз и пишу про "у себя дома", в принципе, ну то есть, как настроить это всё под себя, когда оно надо.

>Примечание для SBCL: если натравить UpdateResource на исполняемый дамп, он откусит built-in core.
Угу, я уже сталкивался с этим.

Date: 2011-08-29 02:21 pm (UTC)
From: [identity profile] love5an.livejournal.com
Кстати, а как насчет того, чтобы в виндовом форке SBCL завести все-таки под дамп какую-либо секцию? Ну, чтобы вот тот же UpateResource не откусывал его.

Date: 2011-08-30 05:21 pm (UTC)
From: [identity profile] akovalenko.livejournal.com
Отдельную секцию (или ресурс) я не сделал потому, что лелеял более амбициозный план: хотелось всобачивать дамп таким образом, чтобы dynamic space на старте прямо сразу отображался куда надо. Оказалось, что такое сделать нетривиально, хотя и возможно в принципе (например, я не ожидал, что загружаемые секции обязаны следовать непрерывно, и нигде про это не читал -- узнал, только когда винды отказались грузить "прерывистый" образ). В общем, буду откатываться на "запасной план" пока.

В смысле генерации имиджей у нас любопытная хрень только что появилась: возможность указывать комбинированный executable в качестве аргумента --core (оно есть в моих исходниках и прошло в апстрим, но бинарники с этой фичей я пока не собирал). Т.е., например, если у тебя вначале есть большой-развесистый dumped executable, он может скопировать свой исполняемый префикс (искать его длину проще всего через предпоследнее слово embedded core, а не через pe header), потом отредактировать ресурсы в копии, а потом запустить копию, указав в --core текущий *runtime-pathname*, и скомандовать копии, чтобы она сдампилась.

Есть ещё такая идея, кстати: завести ресурс, из которого будет читаться относительный путь к non-embedded core (более приоритетный, чем любая дедукция из SBCL_HOME и из "хвоста" GetModuleFileName). Таким образом можно будет деплоить пару exe+core, которая будет независима от окружения -- и я подозреваю, что в ситуации, когда одним exe дело уже не ограничивается, это не хуже embededded core по удобству (к примеру, если всё равно нужно воткнуть sqlite3.dll рядом -- нет особой разницы, в двух частях едет runtime+core или в одной).

Date: 2011-08-30 06:04 pm (UTC)
From: [identity profile] akovalenko.livejournal.com
UPD. Бинарники только что обновил, так что новый способ обработки --core в них есть.

Кстати, я тут на днях документировал полезные команды SB-ACLREPL, которые добавляю в "standalone exe" билды (не в msi). Типа, говоришь :qload hunchentoot, и (при необходимости) автоматом качается quicklisp, ставится, притаскивает hunchentoot (жалуясь в процессе на libssl, правда), компилирует его и грузит. Может, сгодится для чего (а если есть идеи по инновациям-модернизациям, они сгодятся мне :).

Profile

love5an: (Default)
Dmitry Ignatiev

June 2020

S M T W T F S
 123456
78910 111213
14151617181920
21222324252627
282930    

Most Popular Tags

Style Credit

Expand Cut Tags

No cut tags
Page generated Jul. 14th, 2025 09:10 pm
Powered by Dreamwidth Studios