Архив за месяц: Ноябрь 2015

Мнение о книге Оргуправленческое мышление: идеология, методология, технология

Книга представляет собой конспект лекций, которые читались в 1981 году на курсах повышения квалификации руководителям (замам или кто вместо них приехал) строительств атомных электростанций.
Читая эту книгу, я понимал, что очень многое из нее сейчас рассказывается чуть ли не как откровение, а это все рассказывалось как проходной материал уже тогда, когда я под стол пешком ходил. Например, в Антихрупкости Николас Талеб взахлеб, на треть книги расписывает что наука наще ничто, а вот инженерия (прилаживание) наше все. И вот он такой умный, это заметил и рассказывает. Георгий Шедровицкий в первой лекции констатирует это как факт, но не просто констатирует, но еще и дает вариант почему именно так все и произошло. И это не откровение, а так, проходной момент к основной теме.
Так как книга конспект и писалась она на основе аудиозаписи, то в книге есть и вопросы слушателей, ответы на них и даже некоторая полемика. Очень интересно, когда то что ты бы спросил у автора сейчас, спрашивают 35 лет назад и автор отвечает, как будто тебе. Неожиданное ощущение.
Еще в книге много зарисовок, как из советской жизни, так и из общемировой практики. Какие-то уже стали притчей во языцах (про каргокульт), какие-то очень интересны именно как байки из советского времени (по крайней мере для меня), но самое главное, что они очень хорошо оживляют материла, читать интересно.
Ладно, введение затянулась, перехожу к сути. В книге практически нет готовых решений. Т.е. если начинать ее читать как сборник рецептов, то нет. Те же 45 татуировок менеджера это сборник рецептов, здесь же очень большое количество поводов задуматься. В этом плане даже окончание книги... специфическое. Читаешь такой, читаешь, а тут на тебе. Что же есть в книге? Как я уже сказал много баек, ну и системный подход, что это, как это, зачем это. Есть наброски с чего можно строить свою картинку мира. На меня в свое время очень сильное впечатление произвел Черный лебедь, так вот, от этой книги впечатление намного сильнее. Она ломает картинку мира, заставляет смотреть на многие вещи по другому.
Читать ли эту книгу? Не знаю, я точно буду ее еще раз читать, а может быть и не один, сейчас у меня к ней уж очень много вопросов осталось. 

Как я отвечаю на вопросы

Эта публикация из серии ответы на вопросы с MSDN. Вот только на вопрос я уже ответил, но вот в топике был задан вопрос: "А как вы даете ответы?". Вопрос переформулирован мной, по ссылке он в полном виде можно сходить и посмотреть. Под катом я расскажу как я отвечал конкретно на оригинальный вопрос из топика и вообще как я действую столкнувшись с интересной проблемой.


Поехали.
Проблема была в том, что есть Rectangle. К нему применено несколько трансформаций. Одна из трансформаций RotateTransform на некоторый угол. Как, например, при попадании мышки на Rectangle узнать угол поворота. Вот так выглядел вопрос (картинка кликабельна):
Т.к. из текста вопроса проблема не понятна, то создал пустой проект, скопировал в него XAML и код чтобы посмотреть что происходит. У меня монитор меньше 21000 пикселей, поэтому прямоугольник после запуска приложения я не увидел. Пришлось менять размеры и положение.
Когда же прямоугольник стал виден, то добавил в метод точку останова:
Навел мышку, начал отладку по шагам и увидел, что RenderTransform содержит экземпляр класса TransformGroup и, само собой, при приведении к типу RotateTransform будет null:
Дальше все просто. Посмотрел, что свойство RenderTransform типа Transform, а у него нет ни Items, ни Children. Смотрел просто, донаборщиком:

Ну а раз нет, то значит придется явно приводить к типу TransformGroup. У которого есть свойство Children.
Дальше все просто, по XAML видно, что в Children есть три трансформации, т.к. нам нужна трансформация известного типа, то применяем из Linq метод OfType (пользуюсь им достаточно часто, поэтому даже сомнений не было что здесь использовать). Ну а дальше все просто. По конкретному примеру все.

В общем случае, когда хочу что-то сделать, то уже сразу, исходя из предыдущего опыта, есть несколько идей как это сделать. Дальше смотрю какие есть методы, классы что-то через MSDN, что-то просто в донаборщике иногда переходя по F12 к сигнатуре. Отладчик очень часто подсказывает где какой тип, тоже можно воспользоваться. Ну а если все равно не получается или выскакивает какая-нибудь заковыристая ошибка, то ищу поисковиками. Очень часто решение находится на MSDN, Stackoverflow или в блогах. Ответ в большинстве случаев будет на английском, поэтому решив проблему, бывает, пишу статью в этот бложик, вдруг кому пригодится. Совсем редко бывают ситуации когда все уперся, не могу ничего путного найти/придумать. Ок, тогда пытаюсь реализовать другой способ решения. В описанной выше задаче, это могло быть хранение углов трансформации во ViewModel или Model с прокидыванием во View через Binding. При этом подходе с визуальными компонентами вообще дела можно не иметь, т.к. все значения у меня уже доступны бизнес-логике приложения.

Создан cribs.red-bee.ru

Закинул на red-bee.ru несколько страниц с заметками, которые могут оказаться интересными при разработке различного рода административных утилит. Опубликованы здесь. Материал не претендует на абсолютную истину (не исключено, что какие-то задачи можно было решить и более простым путём).

Быстрый способ проверить наличие запущенного процесса acad.exe или accoreconsole.exe

То, что предоставлено в данной заметке не является документированным способом и получено на основе анализирования состава мьютексов, создаваемых и используемых AutoCAD в процессе своей работы. Способ применим как к acad.exe, так и к accoreconsole.exe. Проверялся с AutoCAD 2009-2016 x64, а так же с AutoCAD 2008 x86, запущенном в Windows x64. Решение продемонстрировано в двух вариантах: C++ и C#.

В .NET получить процессы по имени можно при помощи статического метода Process.GetProcessesByName("acad"). Однако, если переименовать любой EXE файл в acad.exe, то  будучи запущенным, он тоже попадёт в выборку, возврашаемую этим методом, хотя на самом деле это не процесс AutoCAD.

Конечно же, приведённый в этой заметке код не является "серебрянной пулей", т.к. всегда можно программно создать Mutex с указанным в нашем коде именем... Однако, обозначенный мною вариант проверки не удастся обмануть простым переименованием любого EXE файла в acad.exe или accoreconsole.exe, поэтому он, как мне кажется, имеет право на жизнь...

Как это сделать на C++:


/*  © Andrey Bushman, 2015
    http://bushman-andrey.blogspot.ru/2015/11/acadexe-accoreconsoleexe.html


    This is the quick way of checking is any AutoCAD launched (acad.exe
    or accoreconsole.exe) or not. I've checked my code for the usual
    AutoCAD 2009-2016 x64. But I haven't their x86 versions, I haven't
    older AutoCAD versions, and I haven't their vertical products,
    therefore I can't check my code for these versions.

 
    Additionally, Alexander Rivilis checked this code for AutoCAD 2008 x86
    which was launched in Windows x64.


    NOTE
    Visual C++ Redistributable for Visual Studio (your version) must be
    installed on the target computers.
*/

#include<Windows.h>
#include<iostream>
#include<exception>
#include<string>
#include<tchar.h>
#include<strsafe.h>

using namespace std;

#define UNEXPECTED_EXCEPTION 1
#define UNKNOWN_ERROR 2

BOOL IsLaunchedAnyAutoCAD();

int wmain(int argc, wchar_t *argv[])
try {
    // setlocale(LC_ALL, "Russian");

    SetConsoleTitle(TEXT("Is any AutoCAD launched?"));

    BOOL result = IsLaunchedAnyAutoCAD();

    if (result) {
        wcout << L"Any AutoCAD is launched." << endl;
    }
    else {
        wcout << L"Any AutoCAD is not launched." << endl;
    }   

    wcout << L"Press 'x' for exit..." << endl;
    wchar_t c;
    wcin >> c;
}
catch (exception ex) {
    wcerr << ex.what() << endl;
    return UNEXPECTED_EXCEPTION;
}
catch(...){
    wcerr << L"Unknown error." << endl;
    return UNKNOWN_ERROR;
}

BOOL IsLaunchedAnyAutoCAD() {
    BOOL result = FALSE;
    LPCTSTR anyAcadMutexName = TEXT(
        // This works for AutoCAD 2008 and newer (I haven't older
        // AutoCAD versions, therefore I can't check it for them).
        "Global\\8C84DAD6-9865-400e-A6E3-686A61C16968"

        // This is for AutoCAD 2009 and newer
        // "Local\\AcadProfileStorage_54519085-6DDA-4070-BB93-3A095D7E1140"
        );
    HANDLE hAnyAcadMutex = OpenMutex(READ_CONTROL, FALSE, anyAcadMutexName);
    if (NULL != hAnyAcadMutex) {
        result = TRUE;
        CloseHandle(hAnyAcadMutex);
    }
    return (result);
}



Как это сделать на C#:


/*  © Andrey Bushman, 2015
    http://bushman-andrey.blogspot.ru/2015/11/acadexe-accoreconsoleexe.html

    This is the quick way of checking is any AutoCAD launched (acad.exe
    or accoreconsole.exe) or not. I've checked my code for the usual
    AutoCAD 2009-2016 x64. But I haven't their x86 versions, I haven't
    older AutoCAD versions, and I haven't their vertical products,
    therefore I can't check my code for these versions.


    Additionally, Alexander Rivilis checked this code for AutoCAD 2008 x86
    which was launched in Windows x64.
*/
using System;
using System.Threading;

namespace Bushman.Sandbox.AutoCAD
{
    class Program
    {
        static Boolean IsAnyAutoCadLaunched()
        {
            try
            {
                Mutex m = Mutex.OpenExisting(
                    // This works for AutoCAD 2008 and newer (I haven't older
                    // AutoCAD versions, therefore I can't check it for them).
                    "Global\\8C84DAD6-9865-400e-A6E3-686A61C16968"

                    // This is for AutoCAD 2009 and newer
                    // "Local\\AcadProfileStorage_54519085-6DDA-4070-BB93-3A095D7E1140"
                    );
                m.Close();
                return true;
            }
            catch
            {
                return false;
            }
        }


        static void Main(string[] args)
        {
            String msg = IsAnyAutoCadLaunched() ? "Any AutoCAD is launched." :
                "Any AutoCAD is not launched.";

            Console.WriteLine(msg);

            Console.WriteLine("Press any key for exit...");
            Console.ReadKey();
        }
    }
}




Открытие и закрытие консольного окна для GUI-приложения

Работая с GUI приложением иногда бывает удобно в режиме реального времени посмотреть, что оно отправляет себе на консоль (т.е. в потоки stdout и stderr), а порой может возникнуть и желание что-то отправить в поток stdin с клавиатуры. Можно, конечно же, выполнять перенаправление в файлы, но этот вариант не всегда удобен. В данной заметке, на примере AutoCAD, показано, как для GUI-приложения открыть консольное окно, выполнить перенаправление потоков и, после того как консольное окно не будет нужно, закрыть его.


Пример C++ кода, выполняющего открытие консольного окна и перенаправляющие потоки ввода-вывода:

// I open console window for AutoCAD GUI application
BOOL result = AllocConsole();

if (0 == result){
    DWORD errCode = GetLastError();
    LPTSTR msg = NULL;
    FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM
        | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, errCode, 0, (LPTSTR)&msg, 0, NULL);

    acutPrintf(_T("\nAllocConsole Error: %s"), msg);
    HeapFree(GetProcessHeap(), 0, msg);
}
else{
    acutPrintf(_T("\nAllocConsole: OK."));
    SetConsoleTitle(L"AutoCAD console window");

    // Disable the close button of the Console window
    HWND hwnd = GetConsoleWindow();
    HMENU hmenu = GetSystemMenu(hwnd, FALSE);
    EnableMenuItem(hmenu, SC_CLOSE, MF_GRAYED);

    // redirecting of the streams
    freopen("CONOUT$", "w", stdout);
    freopen("CONOUT$", "w", stderr);
    freopen("CONIN$", "r", stdin);

    // You will see this messages in the console window
    wcout << L"wcout ping..." << endl;
    wcerr << L"wcerr ping..." << endl;
    cout << "cout ping..." << endl;
    cerr << "cerr ping..." << endl;

    // check wcin accessibility
    wcout << L"Press any char: ";
    wchar_t c;
    wcin >> c;
    wcout << L"You pressed the '" << c << "' char." << endl;
}


Результат выглядит следующим образом:

После того, как консольное окно станет ненужным, его можно закрыть. Однако его нельзя закрывать обычным кликом мышки по кнопке "X" в верхнем правом углу, иначе это приведёт к аварийному завершению работы GUI-приложения (в нашем примере - AutoCAD). Для того, чтобы пользователь случайно не нажал этой кнопки, в обозначенном выше коде мы делаем её недоступной.

Закрытие консольного окна выполняем так же программно:


BOOL result = FreeConsole();

if (0 == result){
    DWORD errCode = GetLastError();
    LPTSTR msg = NULL;
    FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM
        | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, errCode, 0, (LPTSTR)&msg, 0, NULL);

    acutPrintf(_T("\nFreeConsole Error: %s"), msg);
    HeapFree(GetProcessHeap(), 0, msg);
}
else{
    // redirecting of the streams
    freopen("CONOUT$", "w", stdout);
    freopen("CONOUT$", "w", stderr);
    freopen("CONIN$", "r", stdin);
}





Пример создания именованных объектов ядра ОС в разных пространствах имён

Маленькая шпаргалка-пример на тему совместного использования именованных объектов ядра несколькими процессами. Показан вариант размещения именованных объектов ядра в глобальном, локальном и приватном пространствах имён.

Исходный код примера:

#include <Windows.h>
#include <iostream>
#include <aclapi.h>
#include <exception>
#include <tchar.h>

using namespace std;
void PrintErrorMsg(LPCTSTR prefix);

int main(int argc, TCHAR *argv[])
try {
    setlocale(LC_ALL, "Russian");

    HANDLE hGlobalMutex = CreateMutex(NULL, FALSE, L"Global\\G_Mutex");
    if (NULL == hGlobalMutex) {
        PrintErrorMsg(L"Global Mutex: ");
    }
    else {
        // Check the reason of success
        DWORD errCode = GetLastError();
        if (ERROR_ALREADY_EXISTS == errCode) {
            wcout << L"Named global mutex opened." << endl;
        }
        else {
            wcout << L"Named global mutex created." << endl;
        }
    }

    HANDLE hLocalMutex = CreateMutex(NULL, FALSE, L"Local\\L_Mutex");
    if (NULL == hLocalMutex) {
        PrintErrorMsg(L"Local Mutex: ");
    }
    else {
        // Check the reason of success
        DWORD errCode = GetLastError();
        if (ERROR_ALREADY_EXISTS == errCode) {
            wcout << L"Named local mutex opened." << endl;
        }
        else {
            wcout << L"Named local mutex created." << endl;
        }
    }

    LPCTSTR boundaryName = L"BushmanBoundary";
    HANDLE hBoundary = CreateBoundaryDescriptor(boundaryName, 0);
    HANDLE hNamespase = NULL;
    HANDLE hMutex = NULL;
    if (NULL == hBoundary) {
        PrintErrorMsg(L"CreateBoundaryDescriptor: ");
    }
    else {
        wcout << L"Boundary descriptor created." << endl;

        BYTE sid[SECURITY_MAX_SID_SIZE];
        ZeroMemory(sid, sizeof(sid));

        DWORD cbSID = sizeof(sid);
        BOOL result = CreateWellKnownSid(WinWorldSid, NULL, &sid, &cbSID);
        if (0 == result) {
            PrintErrorMsg(L"CreateWellKnownSid: ");
        }
        else {
            wcout << L"SID for boundary descriptor created." << endl;

            result = AddSIDToBoundaryDescriptor(&hBoundary, &sid);
            if (0 == result) {
                PrintErrorMsg(L"AddSIDToBoundaryDescriptor: ");
            }
            else {
                wcout << L"SID added to boundary descriptor." << endl;

                SECURITY_ATTRIBUTES sa = { 0 };
                sa.nLength = sizeof(sa);
                sa.bInheritHandle = FALSE;
                sa.lpSecurityDescriptor = NULL; // use the default value

                LPCTSTR ns = L"Local\\BushNamespace";

                hNamespase = CreatePrivateNamespace(&sa, hBoundary,
                    ns);
                if (NULL == hNamespase) {
                    // Check the reason of failure
                    DWORD errCode = GetLastError();
                    if (ERROR_ALREADY_EXISTS == errCode) {
                        hNamespase = OpenPrivateNamespace(hBoundary, ns);
                        if (NULL == hNamespase) {
                            PrintErrorMsg(L"OpenPrivateNamespace: ");
                        }
                        else {
                            wcout << L"Namespace opened." << endl;
                        }
                    }
                    else {
                        PrintErrorMsg(L"CreateNamespace: ");
                    }
                }
                else {
                    wcout << L"Namespace created." << endl;
                }
                if (NULL != hNamespase) {

                    hMutex = CreateMutex(NULL, FALSE,
                        L"Local\\BushNamespace\\BushMutex");

                    if (NULL == hMutex) {
                        PrintErrorMsg(L"CreateNamespaceMutex: ");
                    }
                    else {
                        // Check the reason of success
                        DWORD errCode = GetLastError();
                        if (ERROR_ALREADY_EXISTS == errCode) {
                            wcout << L"Named mutex in private namespace opened."
                                << endl;
                        }
                        else {
                            wcout << L"Named mutex in private namespace created."
                                << endl;
                        }
                    }
                }              
            }
        }
    }

    wcout << L"Press any char for exit..." << endl;
    wchar_t c;
    wcin >> c;

    if (NULL != hGlobalMutex) {
        CloseHandle(hGlobalMutex);
        hGlobalMutex = NULL;
    }

    if (NULL != hLocalMutex) {
        CloseHandle(hLocalMutex);
        hLocalMutex = NULL;
    }

    if (NULL == hMutex) {
        CloseHandle(hMutex);
        hMutex = NULL;
    }
    if (NULL != hNamespase) {
        ClosePrivateNamespace(hNamespase, 0);
        hNamespase = NULL;
    }
    if (NULL != hBoundary) {
        DeleteBoundaryDescriptor(hBoundary);
        hBoundary = NULL;
    }
}
catch (...) {
    wcerr << L"Unknown error." << endl;
    return 1;
}

void PrintErrorMsg(LPCTSTR prefix) {
    DWORD errCode = GetLastError();
    LPTSTR msg = NULL;
    FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
        FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
        NULL, errCode, 0, (LPTSTR)&msg, 0, NULL);
    wcerr << prefix << msg << endl;
    HeapFree(GetProcessHeap(), 0, msg);
}


На скрине результат консольного вывода двух разных процессов:

В Process Explorer видим следующую картину (см. выделенное красным):


Смотрим WinObj, запустив её с правами администратора:



Ещё немного по теме локализованных ресурсов

В дополнение к предыдущей заметке...


Нейтральная локаль, как и все остальные, определяется в ресурсах EXE или DLL и имеет код 0x000. Я думаю, что зачастую имеет смысл определять более обобщённые варианты ресурсов, дабы не дублировать их для каждой конкретной локали, например:
  • ru вместо ru-RU
  • en вместо en-US, en-GB и т.п.
Если специфичная локаль (en-US или en-GB) не будет найдена в ресурсах модуля, то будет выполнена дополнительная попытка найти более обобщённый вариант локали: en. Однако обратное не верно: если задан поиск локали en, а в ресурсах вместо неё присутствуют более конкретные локали (en-US или en-GB), то они будут проигнорированы.

В качестве примера в файле resources.txt определим текстовые ресурсы с таким набором локализаций:
  • нейтральная
  • ru
  • en-US

; // ***** resources.txt *****
; // This is the header section.

MessageIdTypedef=DWORD

SeverityNames=(Success=0x0:STATUS_SEVERITY_SUCCESS
Informational=0x1:STATUS_SEVERITY_INFORMATIONAL
Warning=0x2:STATUS_SEVERITY_WARNING
Error=0x3:STATUS_SEVERITY_ERROR
)

FacilityNames=(System=0x0:FACILITY_SYSTEM
Runtime=0x2:FACILITY_RUNTIME
Stubs=0x3:FACILITY_STUBS
Io=0x4:FACILITY_IO_ERROR_CODE
)

; // The neutral locale.
LanguageNames=(Neutral=0x000:MSG00000)

; // This locale will be used for any RU-based locale.
LanguageNames=(ru=0x019:MSG00019)

; // This locale will be used only for en-US locale, instead
; // of any EN-based locale.
LanguageNames=(en_US=0x409:MSG00409)

; // The following are message definitions.

MessageId=0x1
Severity=Success
Facility=System
SymbolicName=MSG_HELLO
Language=en_US
USA locale only (en-US).
.

Language=ru
Любая RU-основанная локаль (RU).
.

Language=Neutral
Neutral locale.
.



Далее разными способами программно пытаемся получить локализованный вариант нашего текста:


#include <Windows.h>
#include <tchar.h>
#include <iostream>
#include <exception>
#include "resources.h"
using namespace std;
/*
MSDN resources:
FormatMessage function:
https://msdn.microsoft.com/en-us/library/windows/desktop/ms679351%28v=vs.85%29.aspx
Message Text Files:
https://msdn.microsoft.com/en-us/library/windows/desktop/dd996906%28v=vs.85%29.aspx
Sample Message Text File:
https://msdn.microsoft.com/en-us/library/windows/desktop/dd996907%28v=vs.85%29.aspx
Message Compiler (MC.exe):
https://msdn.microsoft.com/en-us/library/windows/desktop/aa385638%28v=vs.85%29.aspx
*/
// Our function uses the WinAPI mechanism for notifying of error
void PrintMessage(DWORD);

int wmain(int argc, LPTSTR argv[])
try {
    // For right displaying of Cyrillic chars in the console window
    setlocale(LC_ALL, "Russian");

    wcout << L"For neutral locale:" << endl;
    DWORD nl = MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL);
    PrintMessage(nl);

    TCHAR x[LOCALE_NAME_MAX_LENGTH];
   
    DWORD du = GetUserDefaultLCID();   
    int n = GetUserDefaultLocaleName(x, LOCALE_NAME_MAX_LENGTH);
    if (0 != n) {
        wcout << L"For user default locale <" << x << L">:" << endl;
        PrintMessage(du);
    }   
       
    DWORD ds = GetSystemDefaultLCID();
    n = GetSystemDefaultLocaleName(x, LOCALE_NAME_MAX_LENGTH);
    if (0 != n) {
        wcout << L"For system default locale <" << x << L">:" << endl;
        PrintMessage(ds);
    }

    // Any RU-based locale (defined in the resources)
    wcout << L"For RU locale:" << endl;
    DWORD ru = MAKELANGID(LANG_RUSSIAN, SUBLANG_NEUTRAL);
    PrintMessage(ru);

    // Specific locale (NOT defined in the resources)
    // At this case will be used more common locale: RU
    wcout << L"For ru-RU locale:" << endl;
    DWORD ru_ru = MAKELANGID(LANG_RUSSIAN, SUBLANG_RUSSIAN_RUSSIA);
    PrintMessage(ru_ru);

    // Any EN-based locale (defined in the resources)
    wcout << L"For EN locale:" << endl;
    DWORD en = MAKELANGID(LANG_ENGLISH, SUBLANG_NEUTRAL);
    PrintMessage(en);

    // Specific locale (defined in the resources)
    wcout << L"For en-US locale:" << endl;
    DWORD en_us = MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US);
    PrintMessage(en_us);

    wcout << L"Press any char for exit..." << endl;
    wchar_t c;
    wcin >> c;
}
catch (...) {
    wcerr << L"Unknown exception." << endl;
    return 1;
}

void PrintMessage(DWORD localeId) {
    DWORD msgId = MSG_HELLO;
    LPTSTR buffer = NULL;
    HMODULE hModule = GetModuleHandle(NULL);

    DWORD result = FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
        FORMAT_MESSAGE_FROM_HMODULE | FORMAT_MESSAGE_IGNORE_INSERTS,
        hModule, msgId, localeId, (LPTSTR)&buffer, 0, NULL);

    if (0 == result) {
        msgId = GetLastError();
        FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
            FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
            NULL, msgId, 0, (LPTSTR)&buffer, 0, NULL);
    }
    wcout << buffer << endl;
    HeapFree(GetProcessHeap(), 0, buffer);
}


Результат работы кода выглядит следующим образом: