Hungry Mind , Blog about everything in IT - C#, Java, C++, .NET, Windows, WinAPI, ...

Copy paste shame

Не думал, что можно настолько тупо копипастить.

ATI Catalyst Vista install problem

Уже что-то около полугода мне не удается привычным способом обновить двайвера ATI Catalyst, установищик падает с сообщением CATALYST™ Install Manager has stopped working. Грешу на .NET Framework 3.5, но удалять его совершенно нет никакого желания. Приходится извращаться:

  1. запустить установщик драйвера для того, чтобы он перед вылетом распаковался в папку c:\ATI\SUPPORT\8-12_vista32_dd_ccc_wdm_enu_72275\Driver (зависит от версии);
  2. выполнить команду Setup.exe -Uninstall -output screen;
  3. через установку и удаление программ удалить ATI Catalyst Install Manager;
  4. выполнить команду Setup.exe -Install -output screen

Changing dialog font in MFC

Редактор диалогов Visual Studio позволяет задать базовый шрифт для диалога.

По умолчанию, это MS Shell Dlg 8-го размера. А что, если я хочу выставить Segoe UI? Точнее, что будет на системах, где этого шрифта физически нет? Не известно.

Уже понятно, что сию беду нужно, как обычно, писать в коде. Ниже - два универсальных способа решить проблему.

Способ 1

У класса CDialog имеется следующая функция-член: virtual BOOL CreateIndirect(LPCDLGTEMPLATE, CWnd*, void*); Создание любого диалога проходит через нее, поэтому для изменения шрифта в шаблоне диалога это очень подходящее место. В результате - создаем класс-потомок CDialog, переопределяем CreateIndirect, меняем шрифт, вызываем базовый метод. Недостаток этого подхода - все классы диалогов нужно будет наследовать от него. Вариант подходит для нового кода, а если в проекте уже 120 диалогов - что делать?

Способ 2

Хак, а как же еще?

class CFontOccManager : public COccManager
{
    virtual const DLGTEMPLATE *PreCreateDialog(_AFX_OCC_DIALOG_INFO *pOccDialogInfo, const DLGTEMPLATE *pOrigTemplate) {
        const DLGTEMPLATE *lpNewTemplate = __super::PreCreateDialog(pOccDialogInfo, pOrigTemplate);

        OSVERSIONINFO osvi;
        ZeroMemory(&osvi, sizeof(OSVERSIONINFO));
        osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
        GetVersionEx(&osvi);

        if ((osvi.dwMajorVersion >= 6) && !pOccDialogInfo->m_pNewTemplate) {
            CDialogTemplate dlgTemplate(lpNewTemplate);

            CString origFontFaceName;
            WORD wOrigFontSize;
            dlgTemplate.GetFont(origFontFaceName, wOrigFontSize);

            NONCLIENTMETRICS ncm;
            ncm.cbSize = sizeof(NONCLIENTMETRICS);
            SystemParametersInfo(SPI_GETNONCLIENTMETRICS, sizeof(NONCLIENTMETRICS), &ncm, 0);
            LOGFONT lf = ncm.lfMessageFont;
            HDC hDC = ::GetDC(NULL);
            const int logPixelSY = ::GetDeviceCaps(hDC, LOGPIXELSY);
            ::ReleaseDC(NULL, hDC);
            const WORD wProposedFontSize = static_cast<WORD>(MulDiv(-lf.lfHeight, 72, logPixelSY));
            dlgTemplate.SetFont(lf.lfFaceName, max(wOrigFontSize, wProposedFontSize));
            pOccDialogInfo->m_pNewTemplate = reinterpret_cast<DLGTEMPLATE*>(dlgTemplate.Detach());
            return(pOccDialogInfo->m_pNewTemplate);
        }
        else
            return(lpNewTemplate);
    }
};


AfxEnableControlContainer(new CFontOccManager());

Класс COccManager не документирован, но создание диалога проходит в том числе через него. Логика простая:

  1. проверить версию Windows (наличие шрифта Segoe UI);
  2. получить метрики экрана;
  3. выбрать нужный набор метрик и посчитать размер шрифта;
  4. поменять шрифт в шаблоне диалога.

В качестве метрик я взял характеристики lfMessageFont - LOGFONT structure that contains information about the font used in message boxes. Message box - это же диалог, остальное не подходит. Код простой, объяснений не требует. Единственный нюанс, я беру максимум из старого размера и посчитанного.

В результате - нормального размера диалоги с обалденным шрифтом. Удачи.

Inventing wheel for 1000001 time

А как же еще назвать пост, если эти классы написаны уже 1,000,000 раз?

Exception.h

#pragma once
#include <string>
#include <map>
#include <boost/any.hpp>

namespace win32 {

    class Exception
    {
    protected:
        typedef std::wstring properties_key_t;
        typedef std::map<properties_key_t, boost::any> properties_t;

    protected:
        static const properties_key_t propertyMessageKey;

    protected:
        Exception();
        Exception(const std::wstring &message);
    public:
        virtual ~Exception() = 0 {};

    public:
        __declspec(property(get=get_Message))
        std::wstring Message;
        virtual std::wstring get_Message() const;

    protected:
        properties_t _properties;

    };

}

Exception.cpp




#include "Exception.h"

namespace win32 {

    const Exception::properties_key_t Exception::propertyMessageKey(L"msg");

    Exception::Exception() {
    }

    Exception::Exception(const std::wstring &message) {
        _properties.insert(std::make_pair(propertyMessageKey, boost::any(message)));
    }

    /*virtual*/ std::wstring Exception::get_Message() const {
        properties_t::const_iterator i = _properties.find(propertyMessageKey);
        if (i != _properties.end())
            return(boost::any_cast<std::wstring>(i->second));
        return(std::wstring());
    }

}



Win32Exception.h



#pragma once
#include "Exception.h"
#include <Windows.h>
#include <string>

namespace win32 {

    class Win32Exception : public virtual Exception
    {

    protected:
        static const properties_key_t propertyErrorCodeKey;

    public:
        Win32Exception(DWORD errorCode = GetLastError());
        virtual ~Win32Exception();

    public:
        __declspec(property(get=get_ErrorCode))
        DWORD ErrorCode;
        DWORD get_ErrorCode() const;

    public:
        static std::wstring FormatMessage(DWORD errorCode);

    };

}


Win32Exception.cpp



#include "Win32Exception.h"
#include <sstream>
#include <cassert>

namespace win32 {

    const Exception::properties_key_t Win32Exception::propertyErrorCodeKey(L"dwErrorCode");

    Win32Exception::Win32Exception(DWORD errorCode/* = GetLastError()*/) : Exception(FormatMessage(errorCode)) {
        _properties.insert(std::make_pair(propertyErrorCodeKey, boost::any(errorCode)));
    }

    /*virtual*/ Win32Exception::~Win32Exception() {
    }

    DWORD Win32Exception::get_ErrorCode() const {
        properties_t::const_iterator i = _properties.find(propertyErrorCodeKey);
        assert(i != _properties.end());
        return(boost::any_cast<DWORD>(i->second));
    }

    std::wstring Win32Exception::FormatMessage(DWORD errorCode) {
        std::wstring returnValue;

        LPWSTR pwszMsg(NULL);
        DWORD nChars = ::FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
                                                    NULL,
                                                    errorCode,
                                                    MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
                                                    reinterpret_cast<LPWSTR>(&pwszMsg),
                                                    0,
                                                    NULL
                                                );

        if (nChars == 0) {
            std::wstringstream ss;
            ss << L"Error " << std::showbase << std::hex << errorCode;
            returnValue = ss.str();
        }
        if (pwszMsg) {
            int i = wcslen(pwszMsg);
            while (--i > 0)
                if (pwszMsg[i] == L'\r' || pwszMsg[i] == L'\n')
                    pwszMsg[i] = L'\0';
            returnValue = pwszMsg;
            ::LocalFree(pwszMsg);
        }

        return(returnValue);
    }

}



COMException.h



#pragma once
#include "Exception.h"
#include <Windows.h>

namespace win32 {

    class COMException : public virtual Exception
    {

    private:
        enum {
            WCODE_HRESULT_FIRST = MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, 0x200),
            WCODE_HRESULT_LAST = MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF + 1, 0) - 1
        };

    protected:
        static const properties_key_t propertyHrKey;
        static const properties_key_t propertyDescriptionKey;
        static const properties_key_t propertyInterfaceKey;
        static const properties_key_t propertyHelpContextKey;
        static const properties_key_t propertyHelpFileKey;
        static const properties_key_t propertySourceKey;

    public:
        COMException(HRESULT hr);
        virtual ~COMException();

    public:
        __declspec(property(get=get_HResult))
        HRESULT HResult;
        HRESULT get_HResult() const;

        __declspec(property(get=get_Description))
        std::wstring Description;
        std::wstring get_Description() const;

        __declspec(property(get=get_Interface))
        GUID Interface;
        GUID get_Interface() const;

        __declspec(property(get=get_HelpContext))
        DWORD HelpContext;
        DWORD get_HelpContext() const;

        __declspec(property(get=get_HelpFile))
        std::wstring HelpFile;
        std::wstring get_HelpFile() const;

        __declspec(property(get=get_Source))
        std::wstring Source;
        std::wstring get_Source() const;

    public:
        static std::wstring FormatMessage(HRESULT hr);
        static HRESULT WCodeToHRESULT(WORD wCode);
        static WORD HRESULTToWCode(HRESULT hr);

    };

}


COMException.cpp



#include "COMException.h"
#include <sstream>
#include <cassert>

namespace win32 {

    const Exception::properties_key_t COMException::propertyHrKey(L"hr");
    const Exception::properties_key_t COMException::propertyDescriptionKey(L"IErrorInfo_Description");
    const Exception::properties_key_t COMException::propertyInterfaceKey(L"IErrorInfo_GUID");
    const Exception::properties_key_t COMException::propertyHelpContextKey(L"IErrorInfo_HelpContext");
    const Exception::properties_key_t COMException::propertyHelpFileKey(L"IErrorInfo_HelpFile");
    const Exception::properties_key_t COMException::propertySourceKey(L"IErrorInfo_Source");

    COMException::COMException(HRESULT hr) : Exception(FormatMessage(hr)) {
        _properties.insert(std::make_pair(propertyHrKey, boost::any(hr)));
        IErrorInfo *pErrorInfo(NULL);
        if (::GetErrorInfo(0, &pErrorInfo) == S_OK) {
            BSTR bstr;
            if (SUCCEEDED(pErrorInfo->GetDescription(&bstr))) {
                _properties.insert(std::make_pair(propertyDescriptionKey, boost::any(std::wstring(bstr))));
                SysFreeString(bstr);
            }
            GUID guid;
            if (SUCCEEDED(pErrorInfo->GetGUID(&guid))) {
                _properties.insert(std::make_pair(propertyInterfaceKey, boost::any(guid)));
            }
            DWORD dw;
            if (SUCCEEDED(pErrorInfo->GetHelpContext(&dw))) {
                _properties.insert(std::make_pair(propertyHelpContextKey, boost::any(dw)));
            }
            if (SUCCEEDED(pErrorInfo->GetHelpFile(&bstr))) {
                _properties.insert(std::make_pair(propertyHelpFileKey, boost::any(std::wstring(bstr))));
                SysFreeString(bstr);
            }
            if (SUCCEEDED(pErrorInfo->GetSource(&bstr))) {
                _properties.insert(std::make_pair(propertySourceKey, boost::any(std::wstring(bstr))));
                SysFreeString(bstr);
            }

            pErrorInfo->Release();
        }
    }

    /*virtual*/ COMException::~COMException() {
    }

    HRESULT COMException::get_HResult() const {
        properties_t::const_iterator i = _properties.find(propertyHrKey);
        assert(i != _properties.end());
        return(boost::any_cast<HRESULT>(i->second));
    }

    std::wstring COMException::get_Description() const {
        properties_t::const_iterator i = _properties.find(propertyDescriptionKey);
        if (i != _properties.end())
            return(boost::any_cast<std::wstring>(i->second));
        else
            return(std::wstring());
    }

    GUID COMException::get_Interface() const {
        properties_t::const_iterator i = _properties.find(propertyInterfaceKey);
        if (i != _properties.end())
            return(boost::any_cast<GUID>(i->second));
        else
            return(GUID_NULL);
    }

    DWORD COMException::get_HelpContext() const {
        properties_t::const_iterator i = _properties.find(propertyHelpContextKey);
        if (i != _properties.end())
            return(boost::any_cast<DWORD>(i->second));
        else
            return(0);
    }

    std::wstring COMException::get_HelpFile() const {
        properties_t::const_iterator i = _properties.find(propertyHelpFileKey);
        if (i != _properties.end())
            return(boost::any_cast<std::wstring>(i->second));
        else
            return(std::wstring());
    }

    std::wstring COMException::get_Source() const {
        properties_t::const_iterator i = _properties.find(propertySourceKey);
        if (i != _properties.end())
            return(boost::any_cast<std::wstring>(i->second));
        else
            return(std::wstring());
    }

    std::wstring COMException::FormatMessage(HRESULT hr) {
        std::wstring returnValue;

        LPWSTR pwszMsg(NULL);
        DWORD nChars = ::FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
            NULL,
            static_cast<DWORD>(hr),
            MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
            reinterpret_cast<LPWSTR>(&pwszMsg),
            0,
            NULL
            );

        if (nChars == 0) {
            std::wstringstream ss;
            WORD wCode = HRESULTToWCode(hr);
            if (wCode != 0)
                ss << L"IDispatch error #" << wCode;
            else
                ss << L"Unknown error " << std::showbase << std::hex << hr;
            returnValue = ss.str();
        }
        if (pwszMsg) {
            int i = wcslen(pwszMsg);
            while (--i > 0)
                if (pwszMsg[i] == L'\r' || pwszMsg[i] == L'\n')
                    pwszMsg[i] = L'\0';
            returnValue = pwszMsg;
            ::LocalFree(pwszMsg);
        }

        return(returnValue);
    }

    HRESULT COMException::WCodeToHRESULT(WORD wCode) {
        return (wCode >= 0xFE00 ? WCODE_HRESULT_LAST : WCODE_HRESULT_FIRST + wCode);
    }

    WORD COMException::HRESULTToWCode(HRESULT hr) {
        return((hr >= WCODE_HRESULT_FIRST && hr <= WCODE_HRESULT_LAST) ? static_cast<WORD>(hr - WCODE_HRESULT_FIRST) : 0);
    }

}

BITS Manager v1.02.0003

Очередная версия BITS Manager. Окно Job profile опять пострадало - появились закладки с разными частями профиля задачи. Исправил несколько ошибок. Заголовок списка задач обзавелся менюхой и возможностями по группировке, сортировке и т.д. Появилась возможность менять настройки безопасности HTTP, а также устанавливать дополнительные HTTP заголовки.

BITS Manager v1.02.0003

Download BITS Manager v1.02.0003

Next version: BITS Manager v1.02.0004.

BITS proxy in minutest details

Background Intelligent Transfer Service - сложная и заковыристая часть Windows. Сложная она потому, что малоизвестная, малоиспользуемая простыми пользователями, слабо документированная и при этом я насчитал уже 5 ее версий! Думаю, основная причина ее создания - Windows Update. Она же - основная причина появления новых версий и такого программного интерфейса, как мы имеем сйечас.

Недавно я разбирался с настройками proxy для задачи, которые задаются методом IBackgroundCopyJob::SetProxySettings. Из документации можно предположить, что в параметр pProxyList можно передать сроку следующего вида: [protocol=][protocol://]server[:port] [protocol=][protocol://]server[:port] .... О точном формате строки и о реакции на нее самой службой не сказано ничегошеньки. Пришлось потратить прядочно времени на вскрытие черного ящика.

Мое исследование началось с двух вещей:

  1. Включить информативное логирование Background Intelligent Transfer Service

    Enable Background Intelligent Transfer Service Analytic Logging

  2. Включить логирование WinHTTP (а именно ее использует Background Intelligent Transfer Service начиная с версии 2.0)

    Как это сделать описано здесь.

    set tracing output=file trace-file-prefix=D:\Development\Logs\BITS\ level=default format=ansi max-trace-file-size=512000000 state=enabled
    

    Единственный нюанс, для того, чтобы логирование службы заработало, - необходимо перегрузить машину.

Как Background Intelligent Transfer Service разбирает строку proxy

Строка делится на части (назову proxy item) по разделителю " " (пробел). Далее, по порядку BITS проверяет, можно ли использовать proxy item для текущего файла. Если да - передает управление WinHTTP, установив proxy item в качестве строки proxy (см. структуру WINHTTP_PROXY_INFO - ничего не напоминает?). Если скачать файл не удалось, служба с радостью переходит к следующему proxy item, а если они закончились - получаем BG_JOB_STATE_TRANSIENT_ERROR. А вот если proxy item каким-то образом файлу не подошел - начинается самодеятельность: BITS пытается использовать режим BG_JOB_PROXY_USAGE_AUTODETECT, а если и с этим пролет - качает напрямую (BG_JOB_PROXY_USAGE_NO_PROXY)!

Судя по всему, не подошел - это если протокол proxy item не совпадает с протоколом файла. В результате, если строка прокси будет https://someproxy somegenericproxy или x=y http://someproxy somegenericproxy, то файл по протоколу http не будет качаться через somegenericproxy или someproxy!

Выводы напрашиваются следующие:

  • Можно указать много proxy серверов и служба будет пробовать их в порядке следования.
  • Служба может отказаться от использования proxy сервера.
  • Мусор в строке pProxyList автоматически ведет к предыдущему пункту.
  • Желательно не использовать префикс протокола в элементах строки (использовать протоколонезависимые proxy сервера).

    Тем более, если задача состоит из файлов с разными протоколами загрузки. https=... не подходит файлам http://..., а http=... - файлам https://.... Поэтому, часть файлов будет гарантированно качаться в обход proxy.

    Здесь, правда, можно обмануть BITS - воспользоваться тем, что есть еще один пробельный разделитель - \t (TAB). Если строка pProxyList выглядит, как \thttp=http_proxy1\thttps=https_proxy1 \thttp=http_proxy2\thttps=https_proxy2, то в качестве строки proxy в WinHTTP попадет сначала \thttp=http_proxy1\thttps=https_proxy1, а если произошла ошибка, то после и \thttp=http_proxy2\thttps=https_proxy2. А хитрость в том, что WinHTTP считает \t пробелом и выберет нужный сервер с учетом протокола. Использовать этот метод я не рекомендую, т.к. оно может поломаться с очередным пакетом обновлений. Да и не известно, как это будет работать с другими версиями службы (у меня 3.0 на Windows Vista) и не с WinHTTP, которую Background Intelligent Transfer Service начала использовать только с версии 2.0!

BITS Manager v1.02.0002

Очередная версия BITS Manager. Список задач пополнился двумя колонками - Created и Modified/Completed. Окно Job profile также пострадало - добавились поля Created и Modified, изменилось расположение элементов управления. Появились информативные подсказки. Реализована функциональность Take Ownership (кнопка TO), а также полноценный редактор настроек proxy!

BITS Manager v1.02.0002 BITS Manager v1.02.0002 Proxy Settings dialog

Download BITS Manager v1.02.0002

Next version: BITS Manager v1.02.0003.

Sorry, If I Love You

Всем рекомендую к просмотру итальянский(!) фильм Прости за любовь / Sorry, If I Love You / Scusa ma ti chiamo amore (2008). Один из самых ярких и душевных фильмов за длительное время! А актриса просто красавица. Все-таки, итальянки очень красивые женщины.

Sorry, If I Love You / Scusa ma ti chiamo amore

Всех с новым годом!

Copyright 2007-2011 Chabster