RealTimeCallBack示例讲解
目录
项目打开请查看【BaseFunction精讲】。
结构说明
-
RealTimeCallBack:应用层程序,主要用于人机交互、数据显示、内核层数据交互等;
- RealTimeCallBack.h : 数据定义
- RealTimeCallBack.cpp:用户应用层源码
-
RealTimeCallBack_64: 内核层程序(实时层程序),主要用于实时数据处理;
- RealTimeCallBack.h : 数据定义
- RealTimeCallBack_dll.cpp : 内核层源码
-
其余文件说明请查看【BaseFunction精讲】中的结构说明。
ps : 内核层中的数据、结构体需要一字节对齐,需要以MT方式构建
RealTimeCallBack.h : 应用层与内核层共用一个头文件
cpp
/* Copyright (c) 2015-2024 by Kithara Software GmbH. All rights reserved. */
//##############################################################################################################
//
// 文件: RealTimeCallBack.h
//
// 模块: RealTime 模块
//
// 描述: 用户应用程序和内核DLL之间的共享定义,示例:内核DLL中使用实时定时器例程
//
// 创建人: p.gin 2015-09-24
//
//##############################################################################################################
/*=====================================================================*\
| *** 免责声明 *** |
| |
| 本代码仅是示例程序,您可以随意使用,我们不承担任何法律责任! |
| |
\*=====================================================================*/
//##############################################################################################################
//
// 目的:
//
// 此示例代码演示了定时器编程的示例,该示例将在内核级别执行实时上下文中执行。
//
// 此机制具有最佳的准确性,因为所有其他系统活动都被抢占。
// 实时机制在系统中具有最高优先级。
//
// 另一方面,如果在内核级别执行,您的定时器必须处理一些处理和限制。
// 允许应用程序使用此功能,如将测量值保存到硬盘或可视化数据。
// 计时器周期可编程,频率最高可达1 MHz。
//
// 您将被要求在10到1000微秒之间设置一个计时器,计时器例程将运行
// 大约持续2秒。在此期间,我们将停止并重新启动计时器。
// 只有使用C/C++和Delphi才能实现。
//
//##############################################################################################################
#ifndef __SMP_REALTIMECALLBACK_H
#define __SMP_REALTIMECALLBACK_H
#include "../_KitharaSmp/_KitharaSmp.h"
//--------------------------------------------------------------------------------------------------------------
// SharedData 是一个用户定义的参数结构,用于在内核 DLL 和用户应用程序之间使用共享内存进行信息交换。
// 您可以自由定义任何与系统位大小无关的参数。
//--------------------------------------------------------------------------------------------------------------
struct SharedData {
KSHandle hKernel; // 内核句柄
KSHandle hFinishedEvent; // 用于指示中断处理结束的事件的句柄
KSHandle hTimer; // 定时器句柄
KSHandle hTimerCallBack; // 定时器回调句柄
int period; // 计时器周期,单位为微秒
int counter; // 计时器增加的计数器
};
#endif // __SMP_REALTIMECALLBACK_H
RealTimeCallBack.cpp
cpp
/* Copyright (c) 2015-2024 by Kithara Software GmbH. All rights reserved. */
//##############################################################################################################
//
// 文件: RealTimeCallBack.cpp
//
// 模块: RealTime 模块
//
// 描述: 用户应用程序和内核DLL之间的共享定义,示例:内核DLL中使用实时定时器例程
//
// 创建人: p.gin 2015-09-24
//
//##############################################################################################################
/*=====================================================================*\
| *** 免责声明 *** |
| |
| 本代码仅是示例程序,您可以随意使用,我们不承担任何法律责任! |
| |
\*=====================================================================*/
//##############################################################################################################
//
// 目的:
//
// 此示例代码演示了定时器编程的示例,该示例将在内核级别执行实时上下文中执行。
//
// 此机制具有最佳的准确性,因为所有其他系统活动都被抢占。
// 实时机制在系统中具有最高优先级。
//
// 另一方面,如果在内核级别执行,您的定时器必须处理一些处理和限制。
// 允许应用程序使用此功能,如将测量值保存到硬盘或可视化数据。
// 计时器周期可编程,频率最高可达1 MHz。
//
// 您将被要求在10到1000微秒之间设置一个计时器,计时器例程将运行
// 大约持续2秒。在此期间,我们将停止并重新启动计时器。
// 只有使用C/C++和Delphi才能实现。
//
//##############################################################################################################
//--------------------------------------------------------------------------------------------------------------
// 为了在主程序(用户层)和内核 DLL 之间共享数据结构的定义,我们使用了一个通用的头文件。
//--------------------------------------------------------------------------------------------------------------
#include "RealTimeCallBack.h"
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
//
// 别忘了输入你的序列号(6位客户编号),这是打开驱动程序所必需的。
//
// 如果你使用Demo版本,也可以使用"DEMO"代替。
// 如果你使用Beta版本,也可以使用"BETA"代替。
//
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// 如上说所,定义的客户号
const char _pCustomerNumber[] = "DEMO";
// 主程序入口
void runSample() {
outputTxt("***** Kithara example program 'RealTimeCallBack' *****");
// 错误码定义,KSError 是 Kithara API 所有函数的返回类型,通过 【KSError】 可以查询接口的返回错误信息。
KSError ksError;
//------------------------------------------------------------------------------------------------------------
// 打开驱动程序的第一步,所有KRTS程序必须进行的操作。
// 只要该函数调用成功后,我们可以使用其他函数。如果打开失败,则无法调用其他函数。
// 此函数接受您的客户编号作为参数,其中包含 Kithara(如果适用可以为"DEMO"或"BETA")。
//------------------------------------------------------------------------------------------------------------
ksError = KS_openDriver(
_pCustomerNumber); // 客户序列号
if (ksError != KS_OK) {
outputErr(ksError, "KS_openDriver", "Unable to open the driver!");
return;
}
//------------------------------------------------------------------------------------------------------------
// 创建共享内存
// 为实时层中的DLL和此用户层应用程序之间的通信。
//------------------------------------------------------------------------------------------------------------
KSHandle hSharedMemory;
ksError = KS_createSharedMemEx(
&hSharedMemory, // 返回创建的共享内存句柄
"", // 共享内存的名称
sizeof(SharedData), // 共享内存的大小
KSF_NO_FLAGS); // 无标记,此选项可以进行一些特殊设定
if (ksError != KS_OK) {
outputErr(ksError, "KS_createSharedMemEx", "Unable to create shared memory!");
KS_closeDriver();
return;
}
// 要访问共享内存,应用程序需要使用刚创建的共享内存的句柄来获取指向分配的共享内存的指针。
SharedData* pApp = NULL; // 自定义的共享内存结构体
ksError = KS_getSharedMemEx(
hSharedMemory, // 共享内存的句柄
(void**)&pApp, // 指向共享内存的结构的指针
KSF_NO_FLAGS); // 无标记
if (ksError != KS_OK) {
outputErr(ksError, "KS_getSharedMemEx", "Unable to map shared memory!");
KS_closeDriver();
return;
}
// 确定操作系统的位数大小以决定是加载32位还是64位内核DLL。
KSSystemInformation systemInfo; // 获取系统信息的结构体
systemInfo.structSize = sizeof(KSSystemInformation); // 不要忘记设备结构体大小
ksError = KS_getSystemInformation(
&systemInfo, // 结构体指针用于获取结构体数据
KSF_NO_FLAGS); // 无标记
if (ksError != KS_OK) {
outputErr(ksError, "KS_getSystemInformation", "Unable to get system information to distinguish bitsize!");
KS_closeDriver();
return;
}
//------------------------------------------------------------------------------------------------------------
// 想要在内核级别上使用DLL中的函数,必须加载dll,在调用里面的函数!
// 注意!加载程序必须找到DLL,因此它应该放在搜索路径的目录中!
// 因为我们想要在共享内存中传递加载的内核的句柄,所以我们不使用加载内核时的初始化函数,
// 而是在填充内核句柄和初始化内核所需的所有信息之后,显式调用初始化函数。
//------------------------------------------------------------------------------------------------------------
ksError = KS_loadKernel(
&pApp->hKernel, // 返回内核操作句柄
systemInfo.isSys64Bit ? // 根据系统位数加载内核Dll
"RealTimeCallBack_64.dll" :
"RealTimeCallBack_32.dll",
NULL, // 需要支持的函数名称(未使用)
NULL, // 函数参数 (未使用)
KSF_KERNEL_EXEC); // 内核空间中加载(实时层运行)
if (ksError != KS_OK) {
outputErr(ksError, "KS_loadKernel", "Unable to load DLL! Is the DLL in the search path?");
KS_closeDriver();
return;
}
//------------------------------------------------------------------------------------------------------------
// 询问用户他想要的计时器周期。顺便说一下,Kithara工具的所有计时器值都是取自
// 以100纳秒为单位,因此不要忘记稍后乘以10000,其定义为"ms"。
// 参见_KitharaSmp.h中us、ms和s常量的定义
//------------------------------------------------------------------------------------------------------------
int period = inputDec("Enter the timer period in microseconds (10 ... 1000): ", 100);
if (period < 10 || period > 1000) {
outputTxt("Sorry, this timer period is not supported!");
period = 100;
}
outputDec(period, "Running with a period of ", " us...");
// 将用户输入时间(us)转换为内部时间基准(100ns == 0.1us)。
pApp->period = period * us;
//------------------------------------------------------------------------------------------------------------
// 显式调用初始化函数。
// 调用内核的【_initFunction】函数,并传递共享内存的句柄,这样内核就可以从句柄中检索到共享内存的指针,
// 并根据共享内存中存储的信息进行所有必要的资源分配。
// 更为详细的内核初始化操作可以查看内核层【_initFunction】函数
//------------------------------------------------------------------------------------------------------------
ksError = KS_execKernelFunctionEx(
pApp->hKernel, // 内核句柄
"_initFunction", // 函数名称
hSharedMemory, // 共享内存的句柄
KS_INVALID_HANDLE, // 上下文
KSF_NO_FLAGS); // 未使用
if (ksError != KS_OK) {
outputErr(ksError, "KS_execKernelFunctionEx", "Unable to initialize the kernel DLL!");
KS_closeDriver();
return;
}
outputTxt("The timer is now running...");
//------------------------------------------------------------------------------------------------------------
// 首先我们等待一段时间,然后使用共享内存中的定时器句柄暂时停止定时器。
// 为了测试定时器是否真的停止了,我们要比较中断前后的定时器计数。
// 两个值之间可能存在 1 的差异,因为可能有一个事件已经在路上了。
//------------------------------------------------------------------------------------------------------------
waitTime(500 * ms);
ksError = KS_stopTimer(
pApp->hTimer); // 定时器句柄
if (ksError != KS_OK)
outputErr(ksError, "KS_stopTimer", "Unable to stop the timer!");
else {
outputTxt(".");
outputDec(pApp->counter, "The timer has been stopped after ", " calls.");
}
int lastCount = pApp->counter;
waitTime(500 * ms);
if (lastCount != pApp->counter && lastCount < pApp->counter - 1) {
outputErr(KSERROR_INTERNAL, "KS_stopTimer", "What is that? Not stopped?");
outputDec(lastCount, " lastCount: ");
outputDec(pApp->counter, " pApp->counter: ");
}
else
outputTxt("Ok. Timer restarted.");
//------------------------------------------------------------------------------------------------------------
// 现在我们以相同的周期重新启动定时器。
// 我们可以通过传递一个新的定时器周期来设定一个新的定时器周期。
//------------------------------------------------------------------------------------------------------------
pApp->counter = 0; // 重置计时器个数
ksError = KS_startTimer(
pApp->hTimer, // 定时器句柄
KSF_NO_FLAGS, // 无标记
0); // 计时器周期,0表示使用上一个周期
if (ksError != KS_OK)
outputErr(ksError, "KS_startTimer", "Unable to re-start the timer!");
//------------------------------------------------------------------------------------------------------------
// 等待事件信号。
// 事件将由内核级 DLL 的定时器回调函数发出信号。
// 之后,我们将清理所有使用的资源。
// 为了确保在事件从未被设置的情况下不会挂起,我们给出了一个适当的超时值(3 秒)。
//
// 注意!注意!超时值不能用于精确的时间延迟,因为它是基于非实时的操作系统机制!
//------------------------------------------------------------------------------------------------------------
ksError = KS_waitForEvent(
pApp->hFinishedEvent, // 事件句柄
KSF_NO_FLAGS, // 无标记
3 * s); // 超时值,以100 ns为单位
if (ksError != KS_OK)
outputErr(ksError, "KS_waitForEvent", "Waiting for event failed!");
outputDec(pApp->counter, "The timer has been called ", " times.");
//------------------------------------------------------------------------------------------------------------
// 清理内核DLL中分配的资源。
//------------------------------------------------------------------------------------------------------------
ksError = KS_execKernelFunctionEx(
pApp->hKernel, // 内核句柄
"_exitFunction", // 内核层退出函数
KS_INVALID_HANDLE, // 传参
KS_INVALID_HANDLE, // 上下文
KSF_NO_FLAGS); // 无标记
if (ksError != KS_OK) {
outputErr(ksError, "KS_execKernelFunctionEx", "Error while deallocating resources on kernel level!");
KS_closeDriver();
return;
}
//------------------------------------------------------------------------------------------------------------
// 使用共享句柄卸载内核DLL。
// 尽管KS_closeDriver()释放了所有分配的资源(如共享内存和加载的内核),
// 明确释放您分配的资源是很好的习惯。
//------------------------------------------------------------------------------------------------------------
// 释放内核
ksError = KS_freeKernel(
pApp->hKernel); // 内核句柄
if (ksError != KS_OK)
outputErr(ksError, "KS_freeKernel", "Unable to unload the kernel!");
// 清理共享内存
ksError = KS_freeSharedMemEx(
hSharedMemory, // 共享内存句柄
KSF_NO_FLAGS); // 无标记
if (ksError != KS_OK)
outputErr(ksError, "KS_freeSharedMemEx", "Unable to remove shared memory!");
// 关闭设备清理所有资源
ksError = KS_closeDriver();
if (ksError != KS_OK)
outputErr(ksError, "KS_closeDriver", "Unable to close the driver!");
waitTime(500 * ms);
outputTxt(" ");
outputTxt("End of program 'RealTimeCallBack'.");
}
RealTimeCallBack_dll.cpp
cpp
/* Copyright (c) 2015-2024 by Kithara Software GmbH. All rights reserved. */
//##############################################################################################################
//
// 文件: RealTimeCallBack_dll.cpp
//
// 模块: RealTime 模块
//
// 描述: 用户应用程序和内核DLL之间的共享定义,示例:内核DLL中使用实时定时器例程
//
// 创建人: p.gin 2015-09-24
//
//##############################################################################################################
/*=====================================================================*\
| *** 免责声明 *** |
| |
| 本代码仅是示例程序,您可以随意使用,我们不承担任何法律责任! |
| |
\*=====================================================================*/
//##############################################################################################################
//
// 目的:
//
// 此示例代码演示了定时器编程的示例,该示例将在内核级别执行实时上下文中执行。
//
// 此机制具有最佳的准确性,因为所有其他系统活动都被抢占。
// 实时机制在系统中具有最高优先级。
//
// 另一方面,如果在内核级别执行,您的定时器必须处理一些处理和限制。
// 允许应用程序使用此功能,如将测量值保存到硬盘或可视化数据。
// 计时器周期可编程,频率最高可达1 MHz。
//
// 您将被要求在10到1000微秒之间设置一个计时器,计时器例程将运行
// 大约持续2秒。在此期间,我们将停止并重新启动计时器。
// 只有使用C/C++和Delphi才能实现。
//
//##############################################################################################################
//--------------------------------------------------------------------------------------------------------------
// 为了在主程序(用户层)和内核 DLL 之间共享数据结构的定义,我们使用了一个通用的头文件。
//--------------------------------------------------------------------------------------------------------------
#include "RealTimeCallBack.h"
// 共享内存用于在内核层 DLL 和用户层的应用程序之间共享数据。
SharedData* _pSys = NULL;
// 前置声明,在此文件末尾定义的定时器回调函数。
KSError __stdcall timerCallBack(void* /*pArgs*/, void* /*pContext*/);
//--------------------------------------------------------------------------------------------------------------
// 这是初始化函数。
// 它在加载内核后被调用,并将共享内存的句柄作为参数传递。
//
// 注意!请记住,所有函数都应声明为 'extern "C"'!
// 否则它们的名字可能无法被加载器找到。
// 必须通过 '__declspec(dllexport)' 导出它们。
//--------------------------------------------------------------------------------------------------------------
extern "C" KSError __declspec(dllexport) __stdcall _initFunction(void* pArgs, void* /*pContext*/) {
KSError ksError;
// 共享内存的指针通过 KS_execKernelFunctionEx() 作为 'pArgs' 参数传递。
_pSys = (SharedData*)pArgs;
_pSys->counter = 0; // 清空计时器个数
// 现在创建事件。稍后我们将在用户层等待该事件。
// 事件将由定时器设置。
ksError = KS_createEvent(
&_pSys->hFinishedEvent, // 返回事件操作句柄
"MyRealTimeCallBackEvent", // 事件名称
KSF_NO_FLAGS); // 无标记
if (ksError != KS_OK)
return ksError;
//------------------------------------------------------------------------------------------------------------
// 现在创建回调。每次定时器周期结束后,都会调用该回调函数。
// 设置了 KSF_DIRECT_EXEC 标志,回调函数可以在实时上下文中执行。
//------------------------------------------------------------------------------------------------------------
// 创建定时器回调
ksError = KS_createCallBack(
&_pSys->hTimerCallBack, // 返回回调句柄
NULL, // 回调参数
KSF_DIRECT_EXEC, // 内核级别的执行
0); // 优先级(在内核级别未使用)
if (ksError != KS_OK)
return ksError;
//------------------------------------------------------------------------------------------------------------
// 最后,用选定的周期创建定时器。hTimerCallBack 是触发对象。
// 请注意,定时器的最小周期为 1 us= 10 * 100 ns。
//------------------------------------------------------------------------------------------------------------
ksError = KS_createTimer(
&_pSys->hTimer, // 返回定时器操作句柄
_pSys->period, // 计时器周期为100 ns单位
_pSys->hTimerCallBack, // 定时器回调
KSF_REALTIME_EXEC); // 实时层执行
if (ksError != KS_OK)
return ksError;
return KS_OK;
}
//--------------------------------------------------------------------------------------------------------------
// 此清理函数用于删除已安装的定时器、存储的回调函数和信号事件。
//--------------------------------------------------------------------------------------------------------------
extern "C" KSError __declspec(dllexport) __stdcall _exitFunction(void* /*pArgs*/, void* /*pContext*/) {
if (_pSys == NULL) // 共享内存未映射!
return KSERROR_FUNCTION_NOT_AVAILABLE; // _initFunction未被调用?
KSError ksError;
//------------------------------------------------------------------------------------------------------------
// 使用定时器句柄删除定时器。
// 注意,定时器可能仍在运行。删除前不必停止。
//------------------------------------------------------------------------------------------------------------
ksError = KS_removeTimer(
_pSys->hTimer); // 定时器句柄
if (ksError != KS_OK)
return ksError;
//------------------------------------------------------------------------------------------------------------
// 使用回调句柄删除回调对象。
//------------------------------------------------------------------------------------------------------------
ksError = KS_removeCallBack(
_pSys->hTimerCallBack); // 定时器回调句柄
if (ksError != KS_OK)
return ksError;
//------------------------------------------------------------------------------------------------------------
// 使用事件句柄删除事件对象。
//------------------------------------------------------------------------------------------------------------
ksError = KS_closeEvent(
_pSys->hFinishedEvent); // 事件句柄
if (ksError != KS_OK)
return ksError;
return KS_OK;
}
//--------------------------------------------------------------------------------------------------------------
// 这是回调函数。每次定时器周期结束时都会调用它。
// 当函数返回 KS_OK 以外的值时,定时器将被取消。
//--------------------------------------------------------------------------------------------------------------
KSError __stdcall timerCallBack(void* /*pArgs*/, void* /*pContext*/) {
//------------------------------------------------------------------------------------------------------------
// 计时器回调中使用的标志,用于避免重复调用。
//------------------------------------------------------------------------------------------------------------
static bool finishedMeasuring = false;
++_pSys->counter; // 在共享内存中递增计数器
//------------------------------------------------------------------------------------------------------------
// 大约运行 2 秒钟之后,我们通过设置事件来完成。
// 将 2 秒超时转换为定时器的内部时间尺度(100ns):
// 参见 _KitharaSmp.h 中 us、ms 和 s 常量的定义。
//------------------------------------------------------------------------------------------------------------
if (!finishedMeasuring && _pSys->counter * _pSys->period >= 2 * s) {
finishedMeasuring = true; // 向自己发送信号
KS_setEvent( // 向等待的用户层发出事件信号。
_pSys->hFinishedEvent); // 事件句柄
}
return finishedMeasuring ? KSERROR_CATEGORY_USER // 通过不返回KS_OK来停止计时器
: KS_OK;
}
//--------------------------------------------------------------------------------------------------------------
// 需要实现 DllMain 函数,该函数在 DLL 加载时不会被执行。
//
// 对于初始化,请定义一个特殊的 init 函数,并在调用 KS_loadKernel()时将其名称作为参数传递给它,
// 或者在加载内核的句柄以后在加载的 DLL 调用函数(如本例所示)时使用,
// 请不要在加载内核时执行的 init 函数,而是在加载内核后自己明确地调用它,并根据需要传递参数,如本例所示。
//--------------------------------------------------------------------------------------------------------------
#define WIN32_LEAN_AND_MEAN
#pragma pack(push, 8)
#include <windows.h>
#pragma pack(pop)
BOOL WINAPI DllMain(HINSTANCE hInstDll, DWORD reason, LPVOID pReserved) {
return TRUE;
}