CriticalSection без конструктора
От: AssAsin  
Дата: 25.01.03 01:40
Оценка:
Отладил на стареньком C++Builder. Голый API, должно работать всюду. С благодарностью рассмотрю любые предложения по части усовершенствования.

================================== .h =======================================

/* Был глюк при написании Shell Extension DLL (IconHandler): когда Explorer
   загружал DLL, RTL не инициализировался, конструкторы статических объектов
   не вызывались, DllEntryPoint не вызывалась. Класс AssStaticCriticalSection
   обходит указанный глюк. Конструкторов/деструкторов нет. Инициализируется при
   первом обращении к любому методу. Удаляется при уничтожении процесса. :))
   Работает под 9x/NT.

   Использование: для создания экземпляра класса пользуйтесь приведенными ниже
   макросами, которые инициализируют данные объекта на этапе компиляции.
   Пустой конструктор по умолчанию специально сделан приватным, чтобы нельзя
   было по ошибке объявить объект обычным способом.

   Реализация: используется mutex с именем "{GUID}/ProcessId/ObjectAddress".
   Это позволяет на этапе инициализации хранить несколько хэндлов на один
   mutex в автоматических переменных метода Initialize() в разных потоках
   без конфликтов. Со вложенными вызовами Enter()/Leave() все нормально:
   mutex'ы реализованы так же как и критические секции.

   Замечания: возможно, стоит в проверках Leave() without Enter() выбрасывать
   исключение вместо вызова assert(), который под BCB все рушит. */

class AssStaticCriticalSection
{
private:
    /* См. комментарии выше. */

    AssStaticCriticalSection() {}

public:
    /* Для внутреннего использования. Не получилось сделать private, поскольку
       используется макросами. Два метода Initialize() и Perform() нужны для
       скорости кода и во избежание конфликтов: Initialize() всегда открывает
       mutex по имени и захватывает его, прежде чем обращаться к данным
       объекта; Perform() вызывается в условиях, когда данные объекта уже
       проинициализированы, то есть имеется готовый mutex handle, и не надо
       тратить время на формирование имени mutex'а и его создание. */

    enum OPERATION { opTryEnter, opEnter, opLeave };
    struct DATA
    {
        HANDLE hMutex;
        bool (*pFunc)(DATA&, OPERATION);
    };
    DATA data;

    static bool Initialize(DATA& data, OPERATION operation);
    static bool Perform(DATA& data, OPERATION operation);

public:
    /* Собственно интерфейс. */

    bool TryEnter()  { return data.pFunc(data, opTryEnter); }
    void Enter()     { data.pFunc(data, opEnter); }
    void Leave()     { data.pFunc(data, opLeave); }
};

/* Макросы для объявления/определения объекта. Опираются на тот факт, что
   sizeof(AssStaticCriticalSection::DATA) == sizeof(AssStaticCriticalSection). */

#if sizeof(AssStaticCriticalSection::DATA) != sizeof(AssStaticCriticalSection)
#error sizeof(class AssStaticCriticalSection) is wrong!
#endif

#define DECLARE_PUBLIC_AssStaticCriticalSection(name) \
    extern AssStaticCriticalSection& name;

#define DEFINE_PUBLIC_AssStaticCriticalSection(name) \
    static AssStaticCriticalSection::DATA name##__data__ = {0, AssStaticCriticalSection::Initialize}; \
    AssStaticCriticalSection& name = *(AssStaticCriticalSection*) & name##__data__;

#define DEFINE_STATIC_AssStaticCriticalSection(name) \
    static AssStaticCriticalSection::DATA name##__data__ = {0, AssStaticCriticalSection::Initialize}; \
    static AssStaticCriticalSection& name = *(AssStaticCriticalSection*) & name##__data__;

================================== .cpp =====================================

bool AssStaticCriticalSection::Initialize(DATA& data, OPERATION operation)
{
    static const char* guid = "{C47770E8-85DB-45EC-9154-9CA77D33A910}";
    //static const GUID guid = { 0xc47770e8, 0x85db, 0x45ec, { 0x91, 0x54, 0x9c, 0xa7, 0x7d, 0x33, 0xa9, 0x10 } };

    /* Leave() without Enter(). Любой успешный вызов Enter() через эту
       функцию ведет к изменению data.pFunc на Perform(), так что Leave()
       никогда не должна здесь вызываться. */

    assert(operation != opLeave);  // Leave() without Enter()

    char name[60];  // 38+'/'+8+'/'+8+'\0'
    sprintf(name, "%s/%08X/%08X", guid, GetCurrentProcessId(), (DWORD)&data);
    HANDLE h = CreateMutex(NULL, false, name);  // проверял: разным потокам возвращает разные хэндлы.
    assert(h);
    DWORD r = WaitForSingleObject(h, operation == opEnter ? INFINITE : 0);
    assert(r != WAIT_FAILED);

    /* Если TryEnter() и не смогли захватить mutex чтобы проинициализировать
       data, ну и черт с ним, в следующий раз проинициализируем. Проверки на
       Leave() without Enter() будут выполняться корректно. */

    if (r == WAIT_TIMEOUT)
    {
        CloseHandle(h);
        return false;
    }

    assert(!data.hMutex);
    data.hMutex = h;
    data.pFunc = AssStaticCriticalSection::Perform;
    return true;
}
//---------------------------------------------------------------------------
bool AssStaticCriticalSection::Perform(DATA& data, OPERATION operation)
{
    if (operation == opLeave)
    {
        assert(ReleaseMutex(data.hMutex)); // Leave() without Enter()
        return true;
    }
    else
    {
        DWORD r = WaitForSingleObject(data.hMutex, operation == opEnter ? INFINITE : 0);
        assert(r != WAIT_FAILED);
        return (r == WAIT_OBJECT_0 || r == WAIT_ABANDONED);
    }
}
================================== <EOF> ====================================


Пример использования:

DEFINE_STATIC_AssStaticCriticalSection(CS);

bool func()
{
    if (!CS.TryEnter())
        return false;
    try {
        ...
    }
    __finally {
        CS.Leave();
    }
    return true;
}
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.