Использование обработчика событий MCN_GETDAYSTATE и динамических массивов MONTHDAYSTATE, вызывающих ошибки

Вот интересная проблема, с которой я столкнулся. Я использовал MCN_GETDAYSTATE обработчик событий с моим CMonthCalendarControl уже несколько лет без проблем. Обработчик выглядит так:

void CHomeAwayMaintPage::OnGetDayStateCalendar(NMHDR* pNMHDR, LRESULT* pResult) 
{
    NMDAYSTATE      *pDayState = (NMDAYSTATE*)pNMHDR;
    MONTHDAYSTATE   mdState[3]; // last, this, next
    COleDateTime    datStart(pDayState->stStart);

    if (pDayState != nullptr)
    {
        InitDayStateArray(pDayState->cDayState, mdState, datStart);
        pDayState->prgDayState = mdState;
    }

    *pResult = 0;
}

Это всегда работало нормально. Календарь в окне выглядит так:

Календарь

Теперь, за последние два дня, я обновил это окно, чтобы оно поддерживало изменение размера, и поэтому я настроил календарь для изменения размера. Пример:

Несколько календарей

Я настроил обработчик событий (поскольку у нас потенциально может быть любое количество календарей) следующим образом:

void CHomeAwayMaintPage::OnGetDayStateCalendar(NMHDR* pNMHDR, LRESULT* pResult) 
{
    NMDAYSTATE      *pDayState = (NMDAYSTATE*)pNMHDR;
    COleDateTime    datStart(pDayState->stStart);

    DWORD dwCount = MonthCal_GetMonthRange(m_Calender.GetSafeHwnd(), GMR_DAYSTATE, NULL);
    MONTHDAYSTATE *pmdState = new MONTHDAYSTATE[dwCount];

    if (pDayState != nullptr)
    {
        InitDayStateArray(pDayState->cDayState, pmdState, datStart);
        pDayState->prgDayState = pmdState;
    }

    delete[] pmdState;

    *pResult = 0;
}

Тем не менее, когда я закрываю окно, VS2017 вызывает эту ошибку:

Ошибка 1

Я попытался закомментировать строку delete[] pmdState;, и это не имело никакого значения. Я также довольно регулярно получаю эту ошибку при попытке отобразить окно:

Ошибка 2

Если я сбросил динамический макет для элемента управления календарем, чтобы просто переместить элемент управления, а не изменить его размер (чтобы был виден один месяц), и вернуть обработчик событий дневного состояния обратно к тому, что было раньше, эти две ошибки исчезнут, и он станет стабильным как раньше.

Так что я делаю неправильно здесь?

Обновлять

Это метод InitDayStateArray. Просто читает базу данных:

void CHomeAwayMaintPage::InitDayStateArray(int iMonthCount,
     LPMONTHDAYSTATE pDayState, COleDateTime datStart)
{
    int                 iStartMonth, iLastMonth, iThisMonth, iMonth = 0;
    COleDateTime        datDay;
    COleDateTimeSpan    spnDay;
    CString             strDate;
    SCHEDULE_DATA_S     *psTalk = NULL;
    S_JOURNAL_ITEM      *psJournal = NULL; // AJT v11.2.0

    if (pDayState != NULL)
    {
        memset(pDayState, 0, sizeof(MONTHDAYSTATE)*iMonthCount);

        spnDay.SetDateTimeSpan(1,0,0,0);

        datDay = datStart;
        iStartMonth = datStart.GetMonth();
        iThisMonth = iStartMonth;
        iLastMonth = iThisMonth;
        do 
        {
            strDate.Format(_T("%d-%02d-%02d"),
                datDay.GetYear(),
                datDay.GetMonth(),
                datDay.GetDay());

            // try to get this entry from map
            psTalk = NULL;
            m_mapSPTalkDates.Lookup(strDate, (void*&)psTalk);
            if (psTalk != NULL && psTalk->uTalkNumber != 1000)
                BOLDDAY(pDayState[iMonth], datDay.GetDay());

            // AJT v11.2.0
            strDate = datDay.Format(_T("%Y-%m-%d"));
            psJournal = NULL;
            m_mapStrPtrJournalCalendar.Lookup(strDate, (void*&)psJournal);
            if (psJournal != NULL)
                BOLDDAY(pDayState[iMonth], datDay.GetDay());

            datDay = datDay + spnDay;
            iThisMonth = datDay.GetMonth();
            if (iThisMonth != iLastMonth)
            {
                iLastMonth = iThisMonth;
                iMonth++;
            }
        } while(iMonth < iMonthCount);
    }
}

person Andrew Truckle    schedule 21.06.2018    source источник
comment
Что такое InitDayStateArray? А остальной код производить MCVE? Я никогда раньше не видел такого, когда Windows показывает отладочные сообщения о программе, которая закончилась раньше.   -  person Barmak Shemirani    schedule 22.06.2018
comment
@BarmakShemirani Это мой метод, который считывает мою базу данных, чтобы определить, какие битовые значения должны быть в массиве. И я не имею в виду, когда программа завершается, я имею в виду, когда лист свойств закрывается. Это также происходит, если я максимизирую лист свойств. Мне пришлось бы подготовить MCVE, так как этой программе более 10 лет. Я думаю, нам просто нужно поместить календарь в тестовый диалог и изменить его размер. Добавьте поддержку установки дневных состояний и попробуйте максимизировать. Кажется, это связано с тем, как я выделяю память для массива состояний дня. Позже сделаю тестовый проект.   -  person Andrew Truckle    schedule 22.06.2018


Ответы (1)


DWORD dwCount = MonthCal_GetMonthRange(m_Calender.GetSafeHwnd(), GMR_DAYSTATE, NULL);

В документации говорится что третий параметр не может быть NULL:

Указатель на двухэлементный массив структур SYSTEMTIME, которые получат нижний и верхний пределы области действия, заданной dwFlag. Нижний и верхний пределы помещаются в lprgSysTimeArray[0] и lprgSysTimeArray[1] соответственно. Временные элементы этих структур не будут изменены. Этот параметр должен быть действительным адресом и не может быть NULL.

При запуске вашего кода я получаю dwCount = 4 и pDayState->cDayState = 395234

Результат:

MONTHDAYSTATE *pmdState = new MONTHDAYSTATE[4];
...
InitDayStateArray(...)
{
    iMonthCount = pDayState->cDayState
    pDayState = pmdState;
    memset(pDayState, 0, sizeof(MONTHDAYSTATE)*395234);
    ...
}

Обратите внимание, что memset вызывает переполнение буфера на величину (395234 - 4) * sizeof(MONTHDAYSTATE). Это может вызвать серьезные проблемы.

Вы можете переписать код следующим образом:

void CHomeAwayMaintPage::OnMcnSelchangeMonthcalendar1(NMHDR *pNMHDR, LRESULT *pResult)
{
    SYSTEMTIME systime[2];
    int month_count = MonthCal_GetMonthRange(m_Calender.GetSafeHwnd(),
            GMR_DAYSTATE, &systime);
    std::vector<MONTHDAYSTATE> vec(month_count); //or use new/delete

    COleDateTime date(systime[0]);
    COleDateTime end(systime[1]);
    COleDateTimeSpan spnDay;
    spnDay.SetDateTimeSpan(1, 0, 0, 0);
    while (date < end)
    {
        CString str = date.Format(_T("%Y-%m-%d"));
        TRACE(_T("datDay %s\n"), str.GetString());
        date = date + spnDay;
    }
    *pResult = 0;
}

Обновлять

Основываясь на приведенном выше ответе (спасибо) и комментариях в комментариях, я смог упростить свой обработчик событий и сделать это правильно:

void CHomeAwayMaintPage::OnGetDayStateCalendar(NMHDR* pNMHDR, LRESULT* pResult) 
{
    NMDAYSTATE *pDayState = (NMDAYSTATE*)pNMHDR;

    if (pDayState != nullptr)
        InitDayStateArray(pDayState->cDayState, 
            pDayState->prgDayState, COleDateTime(pDayState->stStart));

    *pResult = 0;
}

Здесь написано:

«он получает адрес массива, который предоставляет эти данные».

Меня смутило это, где вам нужно установить буфер .

Как видите, переданная структура уже имеет выделенный буфер. Мне просто нужно было настроить значения. Нигде не выделяется память.

person Barmak Shemirani    schedule 22.06.2018
comment
Большое спасибо за объяснение проблемы. При дальнейшем исследовании NMDAYSTATE я исправил свой код. Теперь все в порядке. - person Andrew Truckle; 22.06.2018
comment
@AndrewTruckle: это похоже на ошибку использования после освобождения. Вы назначаете pmdState параметру out, а затем переходите к его удалению. До того, как звонящий успел его прочитать. Это должно быть очень заметно в отладочной сборке, где operator delete[] использует определенный шаблон заполнения (0xDD). - person IInspectable; 22.06.2018
comment
@IInspectable, как упомянул Бармак, когда я неправильно использовал MonthCal_GetMonthRange и, таким образом, создал буфер с неопределенным значением, которое взорвало систему. В конце концов мне даже не пришлось использовать этот макрос, потому что pDayState->cDayState уже говорит мне, на сколько месяцев нам нужно создать массив. Как только я правильно использовал макрос (или в итоге упростил код), ошибки ушли. - person Andrew Truckle; 22.06.2018
comment
@AndrewTruckle: Но есть ошибка. Вы передаете указатель на массив вызывающей стороне, а затем удаляете его, прежде чем вернуть управление вызывающей стороне. Я не уверен, является ли это ошибкой документации, и система уже выделяет буфер для заполнения. Если бы это было не так, я не вижу никакого мыслимого способа построить этот буфер с соответствующим временем жизни без утечки памяти. Из любопытства: каково значение pDayState->prgDayState при входе? Это ненулевое значение? - person IInspectable; 22.06.2018
comment
Это не нулевое значение. Я вижу здесь: msdn.microsoft .com/en-us/library/windows/desktop/ говорит, что получает адрес массива, который предоставляет эти данные. Меня это смутило: msdn.microsoft.com/ja-jp/library /ms936810.aspx. Я изменил свой обработчик, чтобы просто использовать переданный буфер, а не удалять его! Спасибо. - person Andrew Truckle; 22.06.2018