Проблема HCURSOR, или как делить свой ресурс с другими?
От: Marty Пират https://www.youtube.com/channel/UChp5PpQ6T4-93HbNF-8vSYg
Дата: 08.03.24 17:51
Оценка:
Здравствуйте!

Что-то не очень понимаю, как разделять ресурс с операционной системой, ну и вообще, со сторонними сущностями, которыми не всегда я управляю.

Может, это вопрос скорее в "Архитектуру", но вопрос в тч и по плюсикам, по реализации.

Краткая справка, кто не знаком с WinAPI.
WinAPI предоставляет следующие функции:
BOOL DestroyCursor(HCURSOR hCursor);
HCURSOR CreateCursor(...); // Создаёт курсор из битиков
HCURSOR LoadCursor(HINSTANCE hInstance, LPCSTR lpCursorName); // Загружает курсор из ресурсов, или из файла, или стоковый
HCURSOR SetCursor(HCURSOR hCursor); // Устанавливает текущий курсор и возвращает предыдущий


Тут проблема с SetCursor — она передаёт владение над HCURSOR системе, и возвращает предыдущий установленный хэндл, прекращая владение над ним. Тут ещё возможно важный нюанс, что HCURSOR это глобальный системный ресурс, т.е. если я в приложении установлю свой HCURSOR, то он вероятно останется в системе даже после гибели приложения. Я не проверял, но, судя по тому, что там не требуется хэндл окна, то хэндлом курсора либо приложение владеет, либо сама система.

Простой кейс длительной блокирующей операции:
struct ScopeCursor
{
    HCURSOR hcurDestroy;
    HCURSOR hcurRestore;
    ScopeCursor(HCURSOR hcNew) : hcurDestroy(hcNew), hcurRestore(SetCursor(hcNew)) {}
    ~ScopeCursor() { SetCursor(hcurRestore); DestroyCursor(hcurDestroy); }
};

void longBlockingJob()
{
    auto savedCursor = ScopeCursor(CreateStockCursor(IDC_WAIT));
    // Долгая работа
    // Тут курсор сам будет восстановлен
}


Тут всё нормально, нет никаких проблем.

Другой вариант — я хочу создать несколько курсоров заранее при создании окна, они грузятся из файлов и на каждый чих мне дорого их грузить, в процессе работы хочу произвольно их устанавливать, но когда окно разрушается и/или приложение завершается, я хочу установить тот курсор, который был до меня, и удалить то, что создал.

Тут для автоматического управления загруженными курсорами я начинаю управлять временем жизни HCURSOR, но проблема в том, что я уже не знаю, какой HCURSOR сейчас выбран.

Предположим, у меня есть объект курсора, и функция создания таких курсоров типа такого:
class Cursor
{
    HCURSOR hCursor;
    Cursor(HCURSOR hc) : hCursor(hc) {}
    // всякие copy/move ctor/op=
    ~Cursor()
    {
        ::DestroyCursor(hCursor);
    }
};

// Где-то в классе окна, или может, глобальная ф-я
Cursor createStockCursor(int id)
{
    return Cursor(LoadCursor(0, id));
}

void setDefaultCursor(); // установка дефолтного, см ниже

void setCursor(Cursor c);


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

Дефолтный курсор — можно при старте приложения сделать так для его получения:
class CMainWindow : // ...
{
    HCURSOR hCursorDefault;
// ...

    void OnCreate()
    {
        HCURSOR hTmpCursor = ::LoadCursor(NULL, IDC_WAIT);
        hCursorDefault     = ::SetCursor(hTmpCursor);
        ::SetCursor(hDefaultCursor); // Восстановили оригинальный курсор, но хэндл у нас уже есть
        ::DestroyCursor(hTmpCursor);
    }
};


При завершении на OnClose просто восстановить дефолтный:
    void OnCreate()
    {
        setDefaultCursor(); // ::SetCursor(hDefaultCursor);
        // Больше ничего не делаем, просто установили тот курсор, который был до нашего вмешательства, значит, все наши курсоры точно не заняты системой и их деструкторы отработают нормально
    }


Я могу написать в доке, что курсоры надо хранить в какой-то переменной, но доки никто не читает, и могут сделать тупо так:
setCursor(createStockCursor())


Т.е. тупо передать временный объект, и тогда, после установки курсора объект разрушится, и HCURSOR, которым он управлял, тоже будет уничтожен. Не уверен, что система у HCURSOR ведёт счетчик какой-то, и тогда окажется, что система владеет разрушенным HCURSOR. Не понятно, как быть в такой ситуации?

Ещё проблема в том, что описанный сценарий не позволяет восстановить не дефолтный курсор, а тот курсор, который был установлен ранее.

В общем, не очень понятно, как такую ситуацию разрулить?

Есть вариант, когда класс Cursor не владеет HCURSOR, а всеми созданными HCURSOR владеет фабрика, их создающая, и она разрушает при своём разрушении только те HCURSOR, которые создала сама.

Или, может, как-то ещё можно разрулить проблему?
Маньяк Робокряк колесит по городу
Отредактировано 08.03.2024 17:51 Marty . Предыдущая версия .
Re: Проблема HCURSOR, или как делить свой ресурс с другими?
От: qaz77  
Дата: 08.03.24 18:20
Оценка:
Здравствуйте, Marty, Вы писали:
M>Тут проблема с SetCursor — она передаёт владение над HCURSOR системе, и возвращает предыдущий установленный хэндл, прекращая владение над ним.

Ничего SetCursor не передает.

В GDI/USER системах есть функции создающие новый HANDLE (Create..., Load...) и освобождающие (Destroy..., Delete...).
Все остальные функции используют HANDLE без влияния на его время жизни.

Это как с указателями new, delete и использование указателя.
Если delete вызван раньше, чем используется указатель, то бум.

Курсор тут ничем не отличается от иконок, битмапов, брашей и т.п.
Например, CreateBitmap -> SelectObject -> DeleteObject — правильная последовательность вызовов.

Есть исключения из общего правила.
Для LoadAcceleratorTable не надо вызывать DestroyAcceleratorTable.
Ну и для возврата LoadIcon или LoadImage с флагом LR_SHARED не надо ничего освобождать.
Тут общие разделяемые объекты для процесса подгружаются.
Re[2]: Проблема HCURSOR, или как делить свой ресурс с другими?
От: Marty Пират https://www.youtube.com/channel/UChp5PpQ6T4-93HbNF-8vSYg
Дата: 08.03.24 18:48
Оценка:
Здравствуйте, qaz77, Вы писали:

M>>Тут проблема с SetCursor — она передаёт владение над HCURSOR системе, и возвращает предыдущий установленный хэндл, прекращая владение над ним.


Q>Ничего SetCursor не передает.


Ну окей, я неправильно выразился. Суть в том, что когда я делаю SetCursor, то значение, которое я передал, запоминается в системе и начинает ею использоваться, и в этот момент курсор нельзя удалять. В этом и есть проблема.
Маньяк Робокряк колесит по городу
Re[3]: Проблема HCURSOR, или как делить свой ресурс с другими?
От: CreatorCray  
Дата: 08.03.24 23:03
Оценка:
Здравствуйте, Marty, Вы писали:

M>значение, которое я передал, запоминается в системе и начинает ею использоваться, и в этот момент курсор нельзя удалять. В этом и есть проблема.


А зачем вообще его удалять пока прога не сдохла?
... << RSDN@Home 1.3.110 alpha 5 rev. 62>>
Забанили по IP, значит пора закрыть эту страницу.
Всем пока
Re[4]: Проблема HCURSOR, или как делить свой ресурс с другими?
От: Marty Пират https://www.youtube.com/channel/UChp5PpQ6T4-93HbNF-8vSYg
Дата: 08.03.24 23:46
Оценка:
Здравствуйте, CreatorCray, Вы писали:

M>>значение, которое я передал, запоминается в системе и начинает ею использоваться, и в этот момент курсор нельзя удалять. В этом и есть проблема.


CC>А зачем вообще его удалять пока прога не сдохла?


Ну, конкретно с курсорами — я не дождался мудрых советов, и запилил фабрику курсоров, которая хранит у себя все созданные курсоры, удаляет их при завершении аппы, а при повторных запросах на создание возвращает уже ранее созданное, а Cursor не управляет временем жизни объекта операционной системы HCURSOR.

Но хотелось бы понять, есть ли какие-то альтернативы. Вопрос в целом в том, что вот есть объект, я его создал, управляю временем его жизни, но приходится отдавать сырой хэндл в сторонние сервисы, эти сервисы мне могут вернуть свой предыдущий сырой хэндл, который уже нельзя просто и однозначно ассоциировать с моим объектом. При этом, если сторонний сервис использует этот мой сырой хэндл, то мне нельзя ничего с ним делать. Ну и отличать как-то, хэндлы были мной созданы, или их не надо освобождать. Я полагал, что есть для подобного что-то проработанное, а я не знаю.
Маньяк Робокряк колесит по городу
Re[5]: Проблема HCURSOR, или как делить свой ресурс с другими?
От: CreatorCray  
Дата: 09.03.24 04:28
Оценка:
Здравствуйте, Marty, Вы писали:

M>запилил фабрику курсоров, которая хранит у себя все созданные курсоры, удаляет их при завершении аппы, а при повторных запросах на создание возвращает уже ранее созданное, а Cursor не управляет временем жизни объекта операционной системы HCURSOR.

Так и надо.

M> эти сервисы мне могут вернуть свой предыдущий сырой хэндл

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

M> который уже нельзя просто и однозначно ассоциировать с моим объектом.

Дык это и незачем

M> При этом, если сторонний сервис использует этот мой сырой хэндл, то мне нельзя ничего с ним делать.

Ну как нельзя... Можешь его даже грохнуть, ничего эдакого не произойдёт, система не рухнет.

M> Ну и отличать как-то, хэндлы были мной созданы, или их не надо освобождать.

Фабрика ж твоя является владельцем и потому знает всё то, что создано тобой и что будет тобой же освобождено. Всё остальное — не твоё, и париться не надо.

M> Я полагал, что есть для подобного что-то проработанное, а я не знаю.

Чот до сих пор не понятно в чём именно твоя проблема то
... << RSDN@Home 1.3.110 alpha 5 rev. 62>>
Забанили по IP, значит пора закрыть эту страницу.
Всем пока
Re: Проблема HCURSOR, или как делить свой ресурс с другими?
От: _FRED_ Черногория
Дата: 29.03.24 07:56
Оценка:
Здравствуйте, Marty, Вы писали:

M>Простой кейс длительной блокирующей операции:

struct ScopeCursor
M>{
M>    HCURSOR hcurDestroy;
M>    HCURSOR hcurRestore;
M>    ScopeCursor(HCURSOR hcNew) : hcurDestroy(hcNew), hcurRestore(SetCursor(hcNew)) {}
M>    ~ScopeCursor() { SetCursor(hcurRestore); DestroyCursor(hcurDestroy); }
M>};

M>void longBlockingJob()
M>{
M>    auto savedCursor = ScopeCursor(CreateStockCursor(IDC_WAIT));
M>    // Долгая работа
M>    // Тут курсор сам будет восстановлен
}

M>Тут всё нормально, нет никаких проблем.

Ну как сказать Почему не так:
struct ScopeCursor // За синтаксис извиняюсь, могу в чём-то наврать
{
private:
    const HCURSOR& hcurCurrent;
    const HCURSOR& hcurPrevious;

public:
    ScopeCursor(const HCURSOR& hCursor) : hcurCurrent(hCursor), hcurPrevious(::SetCursor(hcurCurrent)) /* RAII/SRP: ScopeCursor "жизнью" не владеет */ {}
    ~ScopeCursor() { ::SetCursor(hcurPrevious); }

    // Иногда бывает полезно явно восстанавлявать "свой" курсор после передачи управления в другой код, который может курсор поменять
    HCURSOR Restore() { return ::SetCursor(hcurCurrent); }
};


M>Есть вариант, когда класс Cursor не владеет HCURSOR, а всеми созданными HCURSOR владеет фабрика, их создающая, и она разрушает при своём разрушении только те HCURSOR, которые создала сама.


Кажется, это тот самый вариант.
Help will always be given at Hogwarts to those who ask for it.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.