Преобразование указателя функции-члена в TIMERPROC

Как преобразовать указатель функции-члена в тип TIMERPROC для использования с WINAPI SetTimer? Фрагмент кода ниже показывает, как я это делаю сейчас, но при компиляции я получаю эту ошибку:

ошибка C2664: «SetTimer»: невозможно преобразовать параметр 4 из «void (__stdcall CBuildAndSend::*) (HWND, UINT, UINT_PTR, DWORD)» в «TIMERPROC»

Обратный вызов должен быть привязан к исходному экземпляру класса. Если есть лучший способ сделать это, я весь внимание. Спасибо.

class CMyClass
{
public:
    void (CALLBACK CBuildAndSend::*TimerCbfn)( HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime );

private:
    void CALLBACK TimeoutTimerProc( HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime );
};

CMyClass::CMyClass()
{
    ...

    this->TimerCbfn = &CBuildAndSend::TimeoutTimerProc;

    ...

    ::CreateThread(
        NULL,                           // no security attributes
        0,                              // use default initial stack size
        reinterpret_cast<LPTHREAD_START_ROUTINE>(BasThreadFn), // function to execute in new thread
        this,                           // thread parameters
        0,                              // use default creation settings
        NULL                            // thread ID is not needed
        )
}

void CALLBACK CMyClass::TimeoutTimerProc( HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime )
{
    ...
}

static DWORD MyThreadFn( LPVOID pParam )
{
    CMyClass * pMyClass = (CMyClass *)pParam;

    ...

    ::SetTimer( NULL, 0, BAS_DEFAULT_TIMEOUT, pMyClass->TimerCbfn ); // <-- Error Here

    ...
}

person Jim Fell    schedule 24.06.2011    source источник
comment
возможный дубликат Передача пользовательских данных с помощью SetTimer   -  person David Heffernan    schedule 25.06.2011
comment
Кстати, каждый раз, когда у вас есть приведение к функции (или указателю на функцию) в Win32 API, это обычно указывает на какую-то ошибку. Если он не компилируется как есть, обычно функцию необходимо изменить; добавление приведения просто скрывает проблему. К счастью для вас, оказывается, что С++ (указатели на) функции-члены — это совершенно другой зверь, чем (указатели на) простые функции, поэтому в этом случае даже невозможна реинтерпретация_приведения.   -  person BrendanMcK    schedule 25.06.2011


Ответы (1)


Функция-член и TIMEPROC не являются совместимыми типами.

Вам нужно сделать функцию-член static. Тогда это будет работать, если предположить, что список параметров одинаков как в статической функции-члене, так и в TIMEPROC.

class CMyClass
{
public:
    //modified
    void (CALLBACK *TimerCbfn)(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime);

private:
    //modified
    static void CALLBACK TimeoutTimerProc(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime );
};

Указатель функции, а также функция-член изменяются. Теперь это должно работать.

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

Чтобы получить доступ к нестатическим членам, вы можете сделать это:

class CMyClass
{
public:

    static void CALLBACK TimerProc( HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime );

    //add this static member
    static std::map<UINT_PTR, CMyClass*> m_CMyClassMap; //declaration
};

//this should go in the  CMyClass.cpp file
std::map<UINT_PTR, CMyClass*> CMyClass::m_CMyClassMap;  //definition

static DWORD MyThreadFn( LPVOID pParam )
{
    CMyClass * pMyClass = (CMyClass *)pParam;

    UINT_PTR id = ::SetTimer( NULL, 0, BAS_DEFAULT_TIMEOUT, CMyClass::TimerProc);

    //store the class instance with the id as key!        
    m_CMyClassMap[id]= pMyClass; 
}

void CALLBACK CMyClass::TimerProc( HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime )
{
    //retrieve the class instance
    CMyClass *pMyClass= m_CMyClassMap[idEvent];

    /*
      now using pMyClass, you can access the non-static 
      members of the class. e.g
      pMyClass->NonStaticMemberFunction();
    */
}

Я удалил TimerCbfn из своей реализации, так как он на самом деле не нужен. Вы можете передать TimerProc непосредственно в SetTimer в качестве последнего аргумента.

person Nawaz    schedule 24.06.2011
comment
Простого использования статической функции-члена недостаточно, если вы действительно хотите вызвать функцию-член! Вероятно, вы захотите передать указатель экземпляра в качестве идентификатора таймера, а затем, когда будет получен WM_TIMER, отменить процесс и вызвать метод для экземпляра объекта. - person David Heffernan; 24.06.2011
comment
Спасибо! Чтобы заставить его скомпилироваться, мне также пришлось объявить статическими любые переменные-члены, на которые ссылается функция обратного вызова статического члена. Могу ли я по-прежнему ожидать, что для каждого экземпляра создаваемого класса будет отдельный экземпляр каждой переменной/функции-члена? Или, поскольку они объявлены статическими, каждый экземпляр класса будет ссылаться на одну переменную или функцию? Если второе, то будут проблемы. - person Jim Fell; 25.06.2011
comment
@David Heffernan: Это отличная идея! Отправьте это как ответ, и я приму его. - person Jim Fell; 25.06.2011
comment
На самом деле все сложнее. Windows выделяет идентификаторы для вас. Поэтому вам нужна статическая карта между идентификаторами и экземплярами. См.: stackoverflow.com/questions/4625184/ - person David Heffernan; 25.06.2011
comment
Еще одна хорошая статья здесь: greengingerwine.com/index.php/2011/05/ - person David Heffernan; 25.06.2011
comment
Но лично я бы выбрал библиотечный таймер или оконный таймер, в котором класс окна хранит ссылки на экземпляры. Я действительно не люблю оконные таймеры. - person David Heffernan; 25.06.2011
comment
@Nawaz Извините, я ввел вас в заблуждение. Это не работает. Windows выделяет идентификаторы: если параметр hWnd равен NULL, а nIDEvent не соответствует существующему таймеру, он игнорируется и создается новый идентификатор таймера. - person David Heffernan; 25.06.2011
comment
@ Дэвид: Да. Кажется. На самом деле этот трюк обычно работает с другими API Windows. Но это немного другое. :D - person Nawaz; 25.06.2011
comment
@Nawaz: выглядит хорошо; Я должен буду попробовать это, когда вернусь в офис в понедельник (сейчас собираюсь выйти за дверь). :) - person Jim Fell; 25.06.2011
comment
@David Heffernan: я работал над использованием CreateWindow для создания скрытого окна, дескриптор которого можно было бы использовать в SetTimer. Это не сработает? - person Jim Fell; 25.06.2011
comment
@Nawaz: я попробовал вашу реализацию. Исходный модуль компилируется нормально, но когда я делаю сборку сборки, я получаю эту ошибку ссылки: 2>MyClass.obj : error LNK2001: unresolved external symbol "public: static class std::map<unsigned int,class CMyClass *,struct std::less<unsigned int>,class std::allocator<struct std::pair<unsigned int const ,class CMyClass *> > > CMyClass::m_CMyClassMap" (?m_CMyClassMap@CMyClass@@2V?$map@IPAVCMyClass@@U?$less@I@std@@V?$allocator@U?$pair@$$CBIPAVCMyClass@@@std@@@3@@std@@A) Есть идеи? - person Jim Fell; 27.06.2011
comment
@Jim: Вам также необходимо определить член статических данных вне класса. Смотрите мой ответ еще раз. Я отметил это, где это должно быть. - person Nawaz; 27.06.2011
comment
@Jim: Особенно обратите внимание на эту строку: std::map<UINT_PTR, CMyClass*> CMyClass::m_CMyClassMap; //definition. - person Nawaz; 27.06.2011
comment
@Наваз: Спасибо! Я упустил из виду эту часть. Оконное решение, предложенное Дэвидом, тоже хорошо, но для того, чем я занимаюсь, оно намного проще и, думаю, вызовет меньше вопросов при рецензировании. Слава! - person Jim Fell; 27.06.2011
comment
@Nawaz: я закончил вносить другие необходимые изменения в свое приложение, но теперь, когда я его запускаю, я получаю исключение первого изменения, когда создается экземпляр CMyClass. Есть ли у вас какие-либо идеи относительно того, что является причиной этого? Единственное, что мне приходит в голову, это то, что это как-то связано с добавлением std::map. - person Jim Fell; 27.06.2011
comment
@Джим: Что такое first-change exception? - person Nawaz; 27.06.2011
comment
@Nawaz: я действительно не знаю. ^_^;; Я просто встроенный парень, который делает все возможное в мире Windows. Это то, что говорит окно вывода в VS: First-chance exception at 0x0526a9de (MyApp.dll) in ParentApp.exe: 0xC0000005: Access violation reading location 0x00000008. Это происходит, как только вызывается new CMyClass(...). ПК даже не доходит до первой строки в конструкторе, если я ставлю там точку останова. - person Jim Fell; 27.06.2011
comment
@Nawaz: Вот: ideone.com/NB8AW Следует отметить, что функция потока запускается из функция-член Send. Кроме того, я заметил, что мои вызовы к KillTimer неверны, но на данный момент это не должно иметь значения, потому что ПК никогда туда не доберется. Спасибо. - person Jim Fell; 27.06.2011
comment
@DavidHeffernan: мертвая ссылка! Архивная страница: http://web.archive.org/web/20150209024130/http://www.greengingerwine.com/index.php/2011/05/using-the-windows-settimer-api-with-cpp-objects/ - person sergiol; 02.05.2017