【问题标题】:Windows Multimedia Timer and PASCAL return typeWindows 多媒体计时器和 PASCAL 返回类型
【发布时间】:2021-07-11 23:34:42
【问题描述】:

我正在编写一个 C++ 程序,它必须每毫秒执行一次函数才能与硬件交互。由于 Windows 10 不是实时的,我发现多媒体计时器是我可以使用的最好的东西。

设置:

我在 Windows 10 上使用适用于 x86 的 Visual Studio 2019 编译器版本 19.28.29914。

我有以下设置多媒体计时器的代码:

// Get the minimum timer resolution
TIMECAPS tc_;
timeGetDevCaps(&tc_, sizeof(TIMECAPS));

// Set the minimum timer resolution
UINT wTimerRes_ = min(max(tc_.wPeriodMin, 1), tc_.wPeriodMax);
timeBeginPeriod(wTimerRes_); 

// Start the timer
UINT uDelay = max(min(1, tc_.wPeriodMax), wTimerRes_);
MMRESULT status = timeSetEvent(uDelay, wTimerRes_, (LPTIMECALLBACK)&Foo::callback, 0, TIME_PERIODIC);

callback 函数声明为Foo 类的静态私有成员函数,如下所示:

static void PASCAL callback(UINT, UINT, DWORD_PTR, DWORD_PTR, DWORD_PTR);

并实现为:

void PASCAL Foo::callback(UINT wTimerID, UINT msg, DWORD_PTR dwUser, DWORD_PTR dw1, DWORD_PTR dw2){
    // My "real-time" code
}

问题:

一开始我的返回类型中没有PASCAL关键字,我的程序一启动就崩溃了。其他时候,它跑了一会儿,但最终也崩溃了。当我添加这个关键字时,所有的问题似乎都消失了。我在网上找不到太多关于此的信息,所以我的问题是:

  1. 这个神秘的关键字是什么?为什么多媒体计时器需要它才能正常工作?
  2. 还有哪些其他多媒体计时器替代方案可以在 Windows 上实现尽可能实时的性能?

【问题讨论】:

  • 我建议避免强制转换,除非它们是必要的。如果没有强制转换,程序就不会崩溃,而不是崩溃。

标签: c++ c windows timer real-time


【解决方案1】:

我只回答第一个问题。 timeSetEvent 的声明表示回调必须是 LPTIMECALLBACK 类型:

MMRESULT timeSetEvent(
   UINT           uDelay,
   UINT           uResolution,
   LPTIMECALLBACK lpTimeProc,
   DWORD_PTR      dwUser,
   UINT           fuEvent
);

LPTIMECALLBACK 的声明说调用约定必须是CALLBACK

typedef void ( CALLBACK *LPTIMECALLBACK)(
   UINT      uTimerID,
   UINT      uMsg,
   DWORD_PTR dwUser,
   DWORD_PTR dw1,
   DWORD_PTR dw2
);

CALLBACK 为您的 Visual Studio 版本声明为 __stdcall

#define CALLBACK    __stdcall

因此timeSetEvent 需要一个指向具有__stdcall 调用约定的函数的指针。 当你没有改变你的projects default calling convention, the defalut calling convention for static member function is cdecl

不匹配的调用约定会导致未定义的行为。在您的情况下,这有时会导致崩溃。

编译器应该警告您有关不匹配的类型,但您对LPTIMECALLBACK 进行了显式转换,从而隐藏了错误。

偶然PASCAL也被声明为__stdcall

#define PASCAL      __stdcall

因此将您的功能更改为PASCAL 有效。但正确的解决方案是对callback 使用CALLBACK 调用约定。

【讨论】:

  • "不匹配的调用约定会导致崩溃。" - 更准确地说,它会导致未定义的行为,这可能会也可能不会导致崩溃。
  • 谢谢您的回答;这真的很有帮助!我读到 stdcall 和 cdecl 之间的区别在于被调用者负责 stdcall 中的堆栈清理,而调用者则负责 cdecl 中的堆栈清理。因此,通过不为Foo:callback 指定__stdcall,没有人清理堆栈,因为Foo:callback 的调用者期望被调用者,反之亦然,最终导致堆栈溢出。这可以解释为什么我的程序在执行大约一分钟后就崩溃了。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-01-23
  • 2022-12-09
  • 1970-01-01
  • 2021-04-27
  • 2016-11-26
  • 2012-06-20
相关资源
最近更新 更多