Архив рубрики: Cpp

Об изменениях в новой книге Стровструпа "Практика программирования"

Из типографии вышел перевод очередной версии книги "Программирование. Принципы и практика использования C++", написанной Беарне Стровструпом - создателем языка C++. Поскольку на сайте автора я не нашёл информации об изменениях в новой книге, то сравнил оригинальные содержания предыдущего и нового издания этой книги.

Далее приведён перечень изменений в содержании новой книги.

+ 4.6.1        Traversing a vector
+ 8.5.9        constexpr functions
+ 9.5.1        "Plain" enumerations
+ 15.3.3    Lambda expressions
+ 16.3.3    A lambda expressions as a callback
+ 18.2        Initialization
+ 18.3.4    Moving
+ 19.3.3    Concepts
- 19.5.4    auto_ptr
+ 19.5.4    unique_ptr
+ 19.5.5    Return by moving
+ 20.5.1    Container traversal
+ 20.5.2    auto
+ 21.4.3    Lambda expressions
+ 21.9        Container algorithms
+ 23.6.1    Raw string literals
- 26.3.5    Testing classes
- B.6.3        pair
+ B.6.3        pair and tuple
+ B.6.4     initializer_list
+ B.6.5        Resource management pointers
+ B.9.6        Random numbers
+ B.10        Time

DETAILED_ERROR — расширенная информация об ошибке

Выложил на Bitbucket исходный код функции detailed_error и макроса DETAILED_ERROR, использующего её. Они позволяют сгенерировать исключение runtime_exception с нужным сообщением об ошибке, к которому автоматически добавляется информация, позволяющая понять - где именно в исходном коде произошло исключение. Наличие информации о дате компиляции позволяет узнать, какой commit проекта следует смотреть, дабы получить нужную версию файла исходного кода. Предоставляемая информация о разрядности приложения и о версии компилятора так же может быть полезной в ряде случаев.

Быстрый способ проверить наличие запущенного процесса 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);
}


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