您的位置:首页 > 其它

章节10 集成向导 - Segger SystemView使用手册(译文)

2017-11-17 15:14 253 查看
本文博客链接:http://blog.csdn.net/bjr2016,作者:bjr2016,未经允许不得转载。

章节10 集成向导

本节介绍如何将SEGGER SystemView集成到操作系统或中间件模块中,以记录其执行情况。

10.1 在操作系统中集成SEGGER SystemView

SEGGER SystemView可以集成在任何(实时)操作系统中,用于获取关于任务执行、OS内部活动(如调度程序)和OS API调用的信息。对于以下的RTOS,已经完成了这个集成,并且可以在盒子外使用。

SEGGER embOS(V4.12a 或者更高版本)

Micrium uC/OS-III(即将到来的V3.06)

FreeRTOS(使用V8.2.3测试)

要集成到其他操作系统中,请与操作系统分销商联系,或者按照本节中的说明进行集成。

本节中的示例是用来说明何时调用特定的SystemView函数的伪代码。为了让跟踪检测工具对这些函数进行通用集成,可以将这些函数集成为函数宏,或者通过可配置的跟踪API来集成。

集成到操作系统核心

为了能够记录任务执行和上下文切换,操作系统核心必须被检测用来在适当的核心函数中生成SystemView事件。

在大多数情况下,中断执行也是由操作系统处理的。这允许在输入和退出中断的情况下对操作系统进行检测,否则将在应用程序中对每个ISR进行处理。

操作操作系统核心的第三个方面是提供运行时信息,以便进行更详细的分析。该信息包括系统时间,允许SystemView显示相对于应用程序开始时的时间戳,而不是开始记录的时间,并且任务列表被SystemView使用,以显示任务名称、堆栈信息和按优先级排序任务。

10.1.1 记录任务活动

SystemView可以记录系统和操作系统活动主要信息(如任务执行)的预定义系统事件集合。这些事件应该由操作系统在相应的函数中生成。

预定义的事件有:

事件描述SystemView API
Task Create创建了新任务SEGGER_SYSVIEW_OnTaskCreate
Task Start Ready任务被标记为准备好启动或者恢复执行SEGGER_SYSVIEW_OnTaskStartReady
Task Start Exec任务被激活(启动或者恢复执行)SEGGER_SYSVIEW_OnTaskStart
Task Stop Ready任务被阻塞或暂停SEGGER_SYSVIEW_OnTaskStopReady
Task Stop Exec任务终止SEGGER_SYSVIEW_OnTaskStopExec
System Idle没有任务执行,系统进入空闲状态SEGGER_SYSVIEW_OnIdle

10.1.1.1 Task Create任务创建

任务创建事件发生在系统创建一个任务时。

在任务创建事件调用带有新任务的Id的SEGGER_SYSVIEW_OnTaskCreate()函数。此外,建议使用SEGGER_SYSVIEW_SendTaskInfo()记录新任务的任务信息。

示例

void OS_CreateTask(TaskFunc* pF, unsigned Prio, const char* sName, void* pStack) {
SEGGER_SYSVIEW_TASKINFO Info;
OS_TASK* pTask; // Pseudo struct to be replaced
[OS specific code ...]
SEGGER_SYSVIEW_OnTaskCreate((unsigned)pTask);
memset(&Info, 0, sizeof(Info));
//
// Fill elements with current task information
//
Info.TaskID = (U32)pTask;
Info.sName = pTask->Name;
Info.Prio = pTask->Priority;
Info.StackBase = (U32)pTask->pStack;
Info.StackSize = pTask->StackSize;
SEGGER_SYSVIEW_SendTaskInfo(&Info);
}


10.1.1.2 Task Start Ready 任务准备好启动或者恢复执行

当任务的延迟时间结束,或者当任务等待的资源可用,或者当事件触发时,任务开始准备事件可以发生。

在使用Task Start Ready事件时,将调用带有已准备就绪的任务的Id的SEGGER_SYSVIEW_OnTaskStartReady()函数。

示例

int OS_HandleTick(void) {
int TaskReady = 0; // Pseudo variable indicating a task is ready
[OS specific code ...]
if (TaskReady) {
SEGGER_SYSVIEW_OnTaskStartReady((unsigned)pTask);
}
}


10.1.1.3 Task Start Exec

Task Start Exec事件发生发生在上下文即将被切换到激活任务。这通常是调度程序在有准备好的任务时完成的。

调用带有将要执行的任务的Id的SEGGER_SYSVIEW_OnTaskStartExec()函数,创建Task Start Exec事件。

示例

void OS_Switch(void) {
[OS specific code ...]
//
// If a task is activated
//
SEGGER_SYSVIEW_OnTaskStartExec((unsigned)pTask);
//
// Else no task activated, go into idle state
//
SEGGER_SYSVIEW_OnIdle()
}


10.1.1.4 Task Stop Ready

任务被阻塞或暂停。

当任务被暂停或阻塞时, Task Stop Ready事件发生。例如因为它延迟了一个特定的时间,或者当它试图声明一个资源正在被另一个任务使用,或者当它等待事件发生时。当任务被暂停或阻塞时,调度器将激活另一个任务或进入空闲状态。

调用SEGGER_SYSVIEW_OnTaskStopReady()函数,并传入被阻塞的任务的Id,以及一个可以指示为什么任务被阻塞的理由,来创建Task Stop Ready事件。

示例

void OS_Delay(unsigned NumTicks) {
[OS specific code ...]
SEGGER_SYSVIEW_OnTaskStopReady(OS_Global.pCurrentTask, OS_CAUSE_WAITING);
}


10.1.1.5 Task Stop Exec

任务终止。

当任务最终停止执行时,例如当它完成任务并终止执行时,Task Stop Exec事件发生。

在Task Stop Ready事件中,调用SEGGER_SYSVIEW_OnTaskStopExec()来记录当前任务的停止状态。

示例

void OS_TerminateTask(void) {
[OS specific code ...]
SEGGER_SYSVIEW_OnTaskStopExec();
}


10.1.1.6 System Idle

没有任务执行时,系统进入空闲状态。

System Idle事件是当一个任务被暂停或停止时,而没有其他任务准备好时发生的。系统可以切换到一个空闲状态以节省电力,等待中断或任务准备就绪。

在某些操作系统中,空闲是由另一个任务处理的。在这种情况下,当空闲任务被激活时,建议记录系统空闲事件。

在SystemView中,空闲状态时间被显示为不是CPU负载。

在系统空闲事件中调用SEGGER_SYSVIEW_OnIdle()。

示例

void OS_Switch(void) {
[OS specific code ...]
//
// If a task is activated
//
SEGGER_SYSVIEW_OnTaskStartExec((unsigned)pTask);
//
// Else no task activated, go into idle state
//
SEGGER_SYSVIEW_OnIdle()
}


10.1.2 记录中断

SystemView可以记录进入和退出中断服务例程(ISRs)。SystemView API为这些事件提供了函数,当它提供用于标记中断执行的函数时,该函数应该添加到操作系统中。

当OS调度程序被中断控制时,例如SysTick中断,退出中断事件应该区分恢复正常执行还是切换到调度器,并调用适当的SystemView函数。

10.1.2.1 进入中断

当操作系统提供一个函数来通知OS,中断代码正在执行时,在中断服务函数(ISR)开始时调用,OS函数应该调用SEGGER_SYSVIEW_RecordEnterISR()来记录进入中断事件。

当操作系统没有提供进入中断函数,或者ISR没有调用它时,用户有必要调用SEGGER_SYSVIEW_RecordEnterISR()来记录中断执行。

segger_sysview_conf. h中的SEGGER_SYSVIEW_RecordEnterISR()函数通过SEGGER_SYSVIEW_GET_INTERRUPT_ID()函数宏自动检索中断ID。

示例

void OS_EnterInterr
10927
upt(void) {
[OS specific code ...]
SEGGER_SYSVIEW_RecordEnterISR();
}


10.1.2.2 Exit Interrupt

当操作系统提供一个函数来通知OS ,中断代码已经执行完时,该函数应该在中断服务函数结尾调用,OS函数应该调用:

SEGGER_SYSVIEW_RecordExitISR()。 当系统将恢复正常执行时

SEGGER_SYSVIEW_RecordExitISRToScheduler() 当中断引发一个上下文切换时。

示例

void OS_ExitInterrupt(void) {
[OS specific code ...]
//
// If the interrupt will switch to the Scheduler
//
SEGGER_SYSVIEW_RecordExitISRToScheduler();
//
// Otherwise
//
SEGGER_SYSVIEW_RecordExitISR();
}


10.1.2.3 中断服务器函数举例

下面的两个例子展示了如何用OS中断处理和不使用OS中断来记录中断执行。

使用OS处理的例子

void Timer_Handler(void) {
//
// Inform OS about start of interrupt execution
// (records SystemView Enter Interrupt event).
//
OS_EnterInterrupt();
//
// Interrupt functionality could be here
//
APP_TimerCnt++;
//
// Inform OS about end of interrupt execution
// (records SystemView Exit Interrupt event).
//
OS_ExitInterrupt();
}


不使用OS处理的例子

void ADC_Handler(void) {
//
// Explicitly record SystemView Enter Interrupt event.
// Should not be called in high-frequency interrupts.
//
SEGGER_SYSVIEW_RecordEnterISR();
//
// Interrupt functionality could be here
//
APP_ADCValue = ADC.Value;
//
// Explicitly record SystemView Exit Interrupt event.
// Should not be called in high-frequency interrupts.
//
SEGGER_SYSVIEW_RecordExitISR();
}


10.1.3 记录运行时的信息

SystemView可以记录更详细的运行时的信息,如系统时间和任务信息。当SystemView运行时,记录开始并周期性地请求这些信息时,记录这些信息。

为了请求信息,在初始化时可以将带有操作系统特定函数的SEGGER_SYSVIEW_OS_API结构传递给SystemView。

SEGGER_SYSVIEW_OS_API设置是可选的,但建议允许SystemView显示更详细的信息。

SEGGER_SYSVIEW_OS_API

typedef struct {
U64 (*pfGetTime) (void);
void (*pfSendTaskList) (void);
} SEGGER_SYSVIEW_OS_API;


参数

参数描述
pfGetTime指向返回系统时间的函数
pfSendTaskList指向记录整个任务列表的函数

10.1.3.1 pfGetTime

描述

获取系统时间,也就是自系统启动后经过的时间,单位us。

如果pfGetTime为空,SystemView可以显示相对于记录开始时的时间戳。

原型

U64 (*pfGetTime) (void);

10.1.3.2 pfSendTaskList

描述

通过SEGGER_SYSVIEW_SendTaskInfo()记录整个任务列表。

如果pfSendTaskList为空,SystemView可能只会获取在记录时新创建任务的任务信息。当SystemView连接到当前任务列表时,将定期调用pfSendTaskList。

原型

void (*pfSendTaskList) (void);

例子

void cbSendTaskList(void) {
SEGGER_SYSVIEW_TASKINFO Info;
OS_TASK* pTask;
OS_EnterRegion(); // Disable scheduling to make sure the task list does not change.
for (pTask = OS_Global.pTask; pTask; pTask = pTask->pNext) {
//
// Fill all elements with 0 to allow extending the structure
// in future version without breaking the code.
//
memset(&Info, 0, sizeof(Info));
//
// Fill elements with current task information
//
Info.TaskID = (U32)pTask;
Info.sName = pTask->Name;
Info.Prio = pTask->Priority;
Info.StackBase = (U32)pTask->pStackBot;
Info.StackSize = pTask->StackSize;
//
// Record current task information
//
SEGGER_SYSVIEW_SendTaskInfo(&Info);
}
OS_LeaveRegion(); // Enable scheduling again.
}


10.1.4 记录操作系统API调用

除了操作系统核心工具之外,SystemView还可以记录应用程序中的OS API调用。API函数可以像操作系统的核心一样被检测。

当传递简单参数时,或使用适当的SEGGER_SYSVIEW_EncodeXXX()函数创建SystemView事件并调用SEGGER_SYSVIEW_SendPacket()来记录它时,可以使用现成的SEGGER_SYSVIEW_RecordXXX()函数来完成记录API事件。

示例

/*********************************************************************
*
* OS_malloc()
*
* Function description
* API function to allocate memory on the heap.
*/
void OS_malloc(unsigned Size) {
SEGGER_SYSVIEW_RecordU32(ID_OS_MALLOC, // Id of OS_malloc (>= 32)
Size // First parameter
);
[OS specific code...]
}


为了记录API函数执行时间并记录其返回值的时间,API函数的返回也可以通过调用SEGGER_SYSVIEW_RecordEndCall来只记录返回值或者调用SEGGER_SYSVIEW_RecordEndCallReturnValue来记录退出事件以及返回值。

10.1.5 操作系统描述文件

为了使SystemView正确识别API调用,它需要在SystemView的 /description/ 目录中显示一个描述文件。文件的名称必须是SYSVIEW_<操作系统名>.txt,其中<操作系统名>是系统描述中发送的名称。

10.1.5.1 API函数描述

描述文件包括所有可以由操作系统记录的API函数。文件中的每一行都是一个函数,格式如下:

<EventID> <FunctionName> <ParameterDescription> | <ReturnValueDescription>


EventID是为API函数记录的Id。取值范围:32~511。

FunctionName是API函数的名称,显示在SystemView的事件列中。它可以不包含空格。

ParameterDescription是用API函数记录的参数的描述字符串。

ReturnValueDescription是返回值的描述字符串,可以用SystemView进行记录。ReturnValueDescription是可选的。

参数显示可以通过一组修饰符来配置:

% b - 显示参数为二进制。

%B - 参数显示为十六进制字符串(例如00 AA FF…)。

%d - 显示参数为有符号的十进制整数。

%D - 显示参数为时间值。

%I - 显示参数作为资源名,如果资源id已知为SystemView。

%p - 显示参数为4字节十六进制整数(如0xAABBCCDD)。

%s - 显示参数为字符串。

%t - 显示参数作为任务名称,如果任务id已知为SystemView。

%u - 显示参数为无符号十进制整数。

%x - 显示参数为十六进制整数

例子

下面的例子显示了SYSVIEW_embOS.txt的部分内容

35      OS_CheckTimer           pGlobal=%p
42      OS_Delay                Delay=%u
43      OS_DelayUntil           Time=%u
44      OS_setPriority          Task=%t Pri=%u
45      OS_WakeTask             Task=%t
46      OS_CreateTask           Task=%t Pri=%u Stack=%p Size=%u


除了默认的修饰符,描述文件还可以定义NamedTypes来将数值映射到字符串,例如,可以用来显示枚举的文本值或错误代码。

NamedTypes有以下格式:

NamedType <TypeName> <Key>=<Value> [<Key1>=<Value1> ...]


NamedTypes可以在参数描述和返回值描述中使用。

示例

#
# Types for parameter formatters
#
NamedType OSErr 0=OS_ERR_NONE
NamedType OSErr 10000=OS_ERR_A 10001=OS_ERR_ACCEPT_ISR
NamedType OSErr 12000=OS_ERR_C 12001=OS_ERR_CREATE_ISR
NamedType OSErr 13000=OS_ERR_D 13001=OS_ERR_DEL_ISR
NamedType OSFlag 0=FLAG_NONE 1=FLAG_READ 2=FLAG_WRITE 3=FLAG_READ_WRITE
#
# API Functions
#
34 OSFunc Param=%OSFlag | Returns %OSErr


10.1.5.2 任务状态描述

当一个任务暂停执行时,它的状态被记录在SystemView事件中。

这个任务状态可以被转换为SystemView中的文本表示,并使用任务状态解析。

TaskState以下格式:

TaskState <Mask> <Key>=<Value>, [<Key1>=<Value1>, ...]


示例

#
# Task States
#
TaskState 0xFF 0=Ready, 1=Delayed or Timeout, 2=Pending, 3=Pending with Timeout,
4=Suspended, 5=Suspended with Timeout, 6=Suspended and Pending, 7=Suspended and
Pending with Timeout, 255=Deleted


10.1.5.3 Option 描述

在描述文件中也可以设置操作系统特定选项来配置SystemView。

目前可以在描述文件中插入的选项为:

Option ReversePriority:更高的任务优先级值等于较低的任务优先级。

10.1.6 操作系统集成示例

下面的代码展示了如何在基于伪代码片段的操作系统中集成SystemView,并可作为参考使用。

/*********************************************************************
*               (c) SEGGER Microcontroller GmbH & Co. KG             *
*                        The Embedded Experts                        *
*                           www.segger.com                           *
**********************************************************************
-------------------------- END-OF-HEADER -----------------------------

Purpose : Pseudo-code OS with SEGGER SystemView integration.
*/

/*********************************************************************
*
*       OS_CreateTask()
*
*  Function description
*    Create a new task and add it to the system.
*/
void OS_CreateTask (TaskFunc* pF, unsigned Prio, const char* sName, void* pStack) {
SEGGER_SYSVIEW_TASKINFO Info;
OS_TASK*                pTask;  // Pseudo struct to be replaced

[OS specific code ...]

SEGGER_SYSVIEW_OnTaskCreate ((unsigned)pTask);
memset (&Info, 0, sizeof(Info));
//
// Fill elements with current task information
//
Info.TaskID     =  (U32)pTask;
Info.sName      = pTask->Name;
Info.Prio       = pTask->Priority;
Info.StackBase  =  (U32)pTask->pStack;
Info.StackSize  = pTask->StackSize;
SEGGER_SYSVIEW_SendTaskInfo (&Info);
}

/*********************************************************************
*
*       OS_TerminateTask()
*
*  Function description
*    Terminate a task and remove it from the system.
*/
void OS_TerminateTask (void) {

[OS specific code ...]

SEGGER_SYSVIEW_OnTaskStopExec ();
}

/*********************************************************************
*
*       OS_Delay()
*
*  Function description
*    Delay and suspend a task for the given time.
*/
void OS_Delay (unsigned NumTicks) {

[OS specific code ...]

SEGGER_SYSVIEW_OnTaskStopReady (OS_Global.pCurrentTask, OS_CAUSE_WAITING);
}

/*********************************************************************
*
*       OS_HandleTick()
*
*  Function description
*    OS System Tick handler.
*/
int OS_HandleTick (void) {
int TaskReady = 0;   // Pseudo variable indicating a task is ready

[OS specific code ...]

if  (TaskReady) {
SEGGER_SYSVIEW_OnTaskStartReady ((unsigned)pTask);
}
}

/*********************************************************************
*
*       OS_Switch()
*
*  Function description
*    Switch to the next ready task or go to idle.
*/
void OS_Switch (void) {

[OS specific code ...]

//
// If a task is activated
//
SEGGER_SYSVIEW_OnTaskStartExec ((unsigned)pTask);
//
// Else no task activated, go into idle state
//
SEGGER_SYSVIEW_OnIdle ()
}

/*********************************************************************
*
*       OS_EnterInterrupt()
*
*  Function description
*    Inform the OS about start of interrupt execution.
*/
void OS_EnterInterrupt (void) {

[OS specific code ...]

SEGGER_SYSVIEW_RecordEnterISR ();
}
/*********************************************************************
*
*       OS_ExitInterrupt()
*
*  Function description
*    Inform the OS about end of interrupt execution and switch to
*    Scheduler if necessary.
*/
void OS_ExitInterrupt (void) {

[OS specific code ...]
//
// If the interrupt will switch to the Scheduler
//
SEGGER_SYSVIEW_RecordExitISRToScheduler ();
//
// Otherwise
//
SEGGER_SYSVIEW_RecordExitISR ();
}


10.2 在中间层模块集成SEGGER SystemView

SEGGER SystemView还可以集成到中间件模块,甚至是应用程序模块中,以获取关于这些模块的执行的信息,比如API调用或中断触发事件。此集成用于在SEGGER embOS/IP中使用,以监视通过IP和SEGGER emFile发送和接收数据包,以记录API调用。

要集成到其他模块,请与您的分销商联系,或者按照本节中的说明进行集成。

10.2.1 注册模块

为了能够记录中间件模块事件,模块必须通过SEGGER_SYSVIEW_RegisterModule()在SystemView进行注册。

该模块传递一个SEGGER_SYSVIEW_MODULE 结构体指针,该指针包含有关模块的信息,并接收模块可以生成的事件id的事件偏移量。

在注册时,必须在SEGGER_SYSVIEW_MODULE结构中设置sDescription和NumEvents。也可以设置pfSendModuleDesc。

当SEGGER_SYSVIEW_RegisterModule()返回时,SEGGER_SYSVIEW_MODULE结构中的EventOffset被设置为模块可能生成的最低的事件Id,而pNext将指向下一个注册模块,以创建一个链表。因此,SEGGER_SYSVIEW_MODULE结构必须是可写的,并且可能不会在堆栈上分配。

SEGGER_SYSVIEW_MODULE

struct SEGGER_SYSVIEW_MODULE {
const char*                   sModule;
U32                     NumEvents;
U32                     EventOffset;
void                     (*pfSendModuleDesc)(void);
SEGGER_SYSVIEW_MODULE*  pNext;
};


参数

参数描述
sModule指向包含模块名称和模块事件描述的字符串的指针。
NumEvents模块需要注册的事件数
EventOffset事件id的偏移量。输出参数,由这个函数设置。调用此函数后不修改
pfSendModuleDesc回调函数指针,向SystemView发送更详细的模块描述。
pNext指向下一个注册模块的指针。输出参数,由这个函数设置。调用此函数后不修改
示例

SEGGER_SYSVIEW_MODULE IPModule = {
"M=embOSIP, " \
"0 SendPacket IFace=%u NumBytes=%u, " \
"1 ReceivePacket Iface=%d NumBytes=%u", // sModule
2,                                      // NumEvents
0,
// EventOffset, Set by SEGGER_SYSVIEW_RegisterModule()
NULL,
// pfSendModuleDesc, NULL: No additional module description
NULL,
// pNext, Set by SEGGER_SYSVIEW_RegisterModule()
};

static void _IPTraceConfig (void) {
//
// Register embOS/IP at SystemView.
// SystemView has to be initialized before.
//
SEGGER_SYSVIEW_RegisterModule (&IPModule);
}


10.2.2 记录模块活动

为了能够记录模块的活动,模块必须被检测以在适当的函数中生成SystemView事件。

通过对SystemView函数进行直接集成,可以通过可配置的宏函数或API结构来实现对模块的检测,这些功能可以通过SystemView来填充和设置。

当传递简单参数时,或使用适当的SEGGER_SYSVIEW_EncodeXXX()函数创建SystemView事件,并调用SEGGER_SYSVIEW_SendPacket()来记录该事件时,可以使用随时可用的SEGGER_SYSVIEW_RecordXXX()函数来完成记录事件。

示例

int SendPacket (IP_PACKET *pPacket) {
//
// The IP stack sends a packet.
// Record it according to the module description of SendPacket.
//
SEGGER_SYSVIEW_RecordU32x2 (
// Id of SendPacket (0) + Offset for the registered module
ID_SENDPACKET + IPModule.EventOffset,
// First parameter  (displayed as event parameter IFace)
pPacket->Interface,
// Second parameter (displayed as event parameter NumBytes)
pPacket->NumBytes
);

[Module specific code...]
}


有关更多信息,请参阅第108页上的OS API调用和第117页的API引用。

与操作系统一样,可以在描述文件中使用模块的名称(M=的值)提供中间件模块描述。参见第108页的OS描述文件。

10.2.3 提供模块描述

SEGGER_SYSVIEW_MODULE.sModule指向一个包含注册模块的基本信息的字符串,该模块是一个逗号分隔的列表,可以包含以下内容:

标识举例
模块名称M“M=embOSIP”
模块令牌T“T=IP”
描述S“S=’embOS/IP V12.09’”
模块事件“0 SendPacket IFace=%u NumBytes=%u”
字符串长度不能超过SEGGER_SYSVIEW_MAX_STRING_LEN,默认情况下是128。

要发送额外的描述字符串,并发送由模块所使用和记录的资源的名称,在注册模块时可以设置SEGGER_SYSVIEW_MODULE.fSendModuleDesc。

SEGGER_SYSVIEW_MODULE.pfSendModuleDesc在SystemView连接时被周期性调用。它可以调用SEGGER_SYSVIEW_RecordModuleDescription()和SEGGER_SYSVIEW_NameResource()。

示例

static void _cbSendIPModuleDesc (void) {
SEGGER_SYSVIEW_NameResource ((U32)&(RxPacketFifo), "Rx FIFO");
SEGGER_SYSVIEW_NameResource ((U32)&(TxPacketFifo), "Tx FIFO");
SEGGER_SYSVIEW_RecordModuleDescription (&IPModule, "T=IP, S='embOS/IP V12.09'");
}

SEGGER_SYSVIEW_MODULE IPModule = {
"M=embOSIP, " \
"0 SendPacket IFace=%u NumBytes=%u, " \
"1 ReceivePacket Iface=%d NumBytes=%u", // sModule
2,                                      // NumEvents
0,                                      // EventOffset, Set by RegisterModule()
_cbSendIPModuleDesc,                    // pfSendModuleDesc
NULL,                                   // pNext, Set by RegisterModule()
};
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐