如何使用SetTimer API

4

我尝试使用SetTimer API每隔X分钟调用一个函数。因此,我编写了以下的测试代码:

void f()
{
 printf("Hello");
}
int main() 
{
 SetTimer(NULL, 0, 1000*60,(TIMERPROC) &f); 
}

我应该每分钟都写Hello,但它不起作用。


你的 f() 函数必须是一个 timerproc。 - msam
1
你的程序在调用SetTimer()后立即终止,所以当然什么也不会发生。只有在你泵送消息循环(GetMessage + DispatchMesage)时,回调才会发生。你需要考虑一下你要写什么样的程序。 - Hans Passant
3个回答

20

你的程序存在一些问题:

  1. C程序离开main()时就会结束,因此计时器无法起作用。
  2. Win32计时器需要消息泵(见下文)在工作,因为它们是通过WM_TIMER消息实现的,即使它们没有与任何窗口相关联,并且如果您提供了函数回调。

    当您指定TimerProc回调函数时,默认窗口过程会在处理WM_TIMER时调用回调函数。因此,即使您使用TimerProc而不是处理WM_TIMER,您也需要在调用线程中分派消息。

    来源:MSDN: SetTimer function

  3. 回调函数的原型有问题。请参阅http://msdn.microsoft.com/en-us/library/windows/desktop/ms644907%28v=vs.85%29.aspx

    void CALLBACK f(HWND hwnd, UINT uMsg, UINT timerId, DWORD dwTime)
    {
      printf("Hello");
    }
    
    int main() 
    {
      MSG msg;
    
      SetTimer(NULL, 0, 1000*60,(TIMERPROC) &f);
      while(GetMessage(&msg, NULL, 0, 0)) {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
      }
    
      return 0;
    }
    

    (请注意,此示例程序永远不会结束,实际程序应通过发送 WM_QUIT 进行一些额外的逻辑以达到结束的目的。)


1
@ComFreek:谢谢你的编辑,你能告诉我你是怎么做到的吗?当我将一些(错误的)部分标记为代码时,我经常会遇到如何修复它的问题,编辑器表现得很奇怪。 - mity
1
Np ;) 我已经用四个缩进(加上列表的缩进)替换了您的<code><pre>标签。此外,您可以在这里查看编辑帮助。我还添加了一条来自MSDN的引用,证明了您的第二点观点。 - ComFreek
谢谢Mity,但是如果我有两个方法,我想每15分钟调用第一个方法,而另一个方法只在我的程序中调用一次。 - user2219913
你可以使用两个定时器,其中一个在其处理程序中使用 KillTimer 停止。或者使用一个带有标志变量和定时器过程中的一个 if 的定时器。 - mity
1
那已经是无关的问题了。需要考虑编写Win32程序(并使用计时器)意味着事件驱动编程。这与编写传统的C程序有很大不同,后者接受输入,进行一些计算并输出结果。 - mity
显示剩余2条评论

1
这可以编译和运行:
#include <stdio.h>
#include <windows.h>

void CALLBACK f(HWND hwnd, UINT uMsg, UINT timerId, DWORD dwTime)
{
    printf("Hello\n");
}

int main()
{
    MSG msg;

    SetTimer(NULL, 0, 1000 * 3, (TIMERPROC)& f);
    while (GetMessage(&msg, NULL, 0, 0)) {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return 0;
}

0

我发现最佳实现方式如下:

#define TIMER1 1001
#define TIMER2 1002


    SetTimer(hWndMainWnd,             // handle to main window 
        TIMER1,            // timer identifier 
        1000, 
        NULL);     // no timer callback 

    SetTimer(hWndMainWnd,             // handle to main window 
        TIMER2,            // timer identifier 
        5000, 
        NULL);     // no timer callback 

然后,在主事件循环的一部分:

LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch (uMsg)
    {
    case WM_TIMER:
        if (wParam == TIMER1)
        {
              // do something1
        }
        else
        if (wParam == TIMER2)
        {
             // do smoething2
        }
        break;

3
楼主没有窗口,想使用回调函数。这个答案并没有解决这种情况。此外,计时器ID需要是唯一的。使用硬编码的值会与其他计时器代码冲突。最好使用具有足够生命周期的对象的地址作为ID。这就是为什么ID的大小要与指针相同。另外,在复制代码时,应该附上适当的归属信息。 - IInspectable
1
“window”参数是可选的,可以替换为NULL。请参阅https://msdn.microsoft.com/en-us/library/windows/desktop/ms644906(v=vs.85).aspx了解更多信息。该答案解决了确切的场景,并经过完全测试。这是我们自己的代码,它有效。使用硬编码值不会与其他计时器代码冲突,因为所有由SetTimer()定义的计时器都在应用程序范围内定义。为了回答问题,使用这些预处理定义是最好的选择。 - Michael Haephrati
1
如果您提供了回调函数,则window参数是可选的。当您提供回调函数时,WM_TIMER消息将不再发布到您的窗口。在这种情况下,窗口过程是无意义的。如果使用计时器回调,则计时器ID不再与窗口关联,并且必须在应用程序中全局唯一。使用第三方代码的应用程序不能使用硬编码的ID值,否则可能会发生冲突。使用具有适当生命周期的对象的地址是确保唯一性的传统技术。 - IInspectable
1
我的答案没有缺陷,并且已经经过充分测试。你要么是在重复我已经写过的东西,要么是在挑选无关紧要的细节(比如我正在使用的预处理器指令)。我真的没有更多要补充的了。在这个答案的范围内使用硬编码ID没有任何问题,即使使用第三方库,通常也不会产生干扰(而且你会发现大多数第三方库都包含了大量自己的“DEFINE…”,这可能是一个问题,但很少见。当发生这种情况时,你只需要更改你的定义。这不是不使用DEFINE的原因。 - Michael Haephrati
3
计时器可用于两种不同的方式:通过处理WM_TIMER消息和让系统调用回调来使用。问题提出者询问的是后者,而您的答案解释了如何使用前者。这两种方法有显著的区别:处理计时器(在窗口过程中处理消息 vs. 提供回调)和ID唯一性的范围(窗口 vs. 应用程序)。虽然此答案未能解释如何使用带有回调的计时器,但也未能解释为什么问题提出者的代码无法运行。此答案没有用处。 - IInspectable

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接