标签归档:C/C++

浅谈HAL设计(3)- 回调函数

在设计HAL,或者把代码结构分层的时候我们不可避免会遇到这种情况,一般函数调用顺序总是高级别函数调用低级别函数,比如HAL调用dirver层,或者app调用middleware。但是总有一些情况需要我们在较低级别的函数中调用较高级别的函数,这时候回调函数(callback)就十分有用了。

什么是回调

举一个例子,老板让张三去参加一个培训,培训时间要1000秒:

void start_training(char* name)
{
    delay(1000);
}

老板还想要让张三去给自己买一杯咖啡,时间需要100秒

void buy_coffee(char* name)
{
    delay(100)
}

老板同时执行了两个并行的任务线程:

execute_task_1()
{
    start_training("ZhangSan")
}
execute_task_2()
{
    buy_cooffee("ZhangSan")
}

张三不能去买咖啡,因为他还在参加培训。于是,老板只能一遍一遍发这个命令,或者等到张三结束培训之后再告诉张三。老板很恼火,觉得自己浪费了很多时间。

张三建议老板使用“回调”:

typedef void(*boss_cb)(char* name);
void start_training(char* name, boss_cb callback)
{
    delay(1000);
    callback(name);
}
void buy_coffee(char* name)
{
    delay(100)
}

于是老板只需要一条指令:

execute_task_1()
{
    start_training("ZhangSan", buy_coffee)
}

张三在培训完会直接去执行买咖啡的指令,老板不用一直等着或者反复催促,节省了老板很多时间,老板很满意,给张三加了薪。

张三在培训时候使用的就是回调函数,他不需要知道回调函数的具体内容,只提供这个接口。具体的内容由老板实现,老板让干什么就干什么,可以培训完去买咖啡,可以培训完去开车,也可以培训完去参加会议。

所以,回调函数是对某段代码的引用,该代码作为参数传递给其他代码,该代码允许较低级别的软件层调用较高级别的层中定义的函数[1]。在软件设计中,回调这种机制允许dirver这种较低层代码中设计某种接口,然后将具体如何实现这个接口功能留给上层的应用程序层。C语言中可以通过传递函数指针实现。

最简单的回调函数只是作为参数传递给另一个函数的函数指针。 在大多数情况下,回调包含三部分:

  • 回调函数
  • 注册回调函数
  • 执行回调函数

下图显示了这三个部分在回调实现中是如何工作的[2]:

一个简单的例子

在这个例子中有三个文件:

  • callback.c
  • reg_callback.h
  • reg_callback.c
/* callback.c */
#include<stdio.h>
#include"reg_callback.h"
/* callback function definition goes here */
void my_callback(void)
{
    printf("inside my_callback\n");
}
int main(void)
{
    /* initialize function pointer to
    my_callback */
    callback ptr_my_callback=my_callback;                           
    printf("This is a program demonstrating function callback\n");
    /* register our callback function */
    register_callback(ptr_my_callback);                             
    printf("back inside main program\n");
    return 0;
}
/* reg_callback.h */
typedef void (*callback)(void);
void register_callback(callback ptr_reg_callback);
/* reg_callback.c */
#include<stdio.h>
#include"reg_callback.h"
/* registration goes here */
void register_callback(callback ptr_reg_callback)
{
    printf("inside register_callback\n");
    /* calling our callback function my_callback */
    (*ptr_reg_callback)();                                  
}

编译运行后可以得到输出:

This is a program demonstrating function callback
inside register_callback
inside my_callback
back inside main program

正如图片里所展示的关系,较高层的函数将较低层的函数作为普通调用来调用,并且回调机制允许较低层的函数通过指向回调函数的指针来调用较高层的函数。使用回调函数,相当于把两个异步的任务关联了起来。

所以,在C语言中回调函数只不过是将函数指针传递到需要调用回调函数的代码,使用这种方法,我们可以实现一些比如,错误处理,退出前清理内存等很有实用性的功能。

Reference

[1] https://en.wikipedia.org/wiki/Callback_%28computer_programming%29

[2] https://www.beningo.com/embedded-basics-callback-functions/

My C/C++ Coding Style

编码风格对于软件开发者而言十分重要,对大型的开发团队更是如此,每个公司也都有自己的风格规定。在这里分享一套我在 C/C++项目中使用的 coding style。这套编码风格参考整理了 Google C++ coding style,RDK coding guidelines,NASA coding style,和一些开源的编码风格(见附录)。

规范编码风格的目的不是形式化,而是为了提高写代码效率和代码的一致性,同事提高代码的可读性与可维护性。比如注释部分我花了很大的篇幅介绍Doxygen工具的注释风格,并且对每一个public API都要求有清晰、详细的注释。

将整理好的风格整理成Astyle的配置,写一个batch脚本或者在git hook中添加Astyle,即可使用batch文件或者git commit后自动修改代码格式。

1 Project Structure

1.1 File Guards

All header files should use #define to avoid multi-declaration. Naming format should be <PLATFORM>_<FILE>_H_For example, the file adsp/dirver/include/adsp_driver_adc.h in platform adsp should have the following guard.:

#ifndef ADSP_DRIVER_ADC_H_
#define ADSP_DRIVER_ADC_H_

...

#endif  /* ADSP_DRIVER_ADC_H_ */

1.1 Order of Includes

Include headers in the following order: Related header, C system headers, C++ standard library headers, other libraries’ headers, your project’s headers.For example, In adsp/dirver/src/adsp_driver_adc.c, whose main purpose is to implement or test the stuff in adsp/dirver/include/adsp_driver_adc.h, order your includes as follows:

  1. self.h.
  2. A blank line
  3. C/C++ system headers (more precisely: headers in angle brackets with the .h extension), e.g. <unistd.h>, <stdlib.h>.
  4. A blank line
  5. Other libraries’ .h files.
  6. Your project’s .h files.

1.2 File Names

File names should include both platform name and module functions. For example:

  • A driver file name example is mcu_driver_adc.c.
  • A utility file name example is utility_crc32.c.
  • A HAL file name example is hal_adc.c, hal_adc.h.
  • A app file name example is app_gateway.c.
  • A board file name example of project XXX isxxx_audio.c.

2. Code Format

2.1 Brace Placement

Allman style is used for brace placement, e.g.

while (x == y) 
{
    something(); 
    somethingelse(); 
}

2.2 Indentations

Use 4 spaces rather than tabs as printers as users might have different tab settings.Use single line spacing between logical blocks of code.Use double line spacing between functions.Linux(LF) end line style is used.

2.3 Code Length

Code in single line should not exceed 80 characters. When breaking lines, use the natural logical breaks to determine where the newline goes. Indent the continuation line to illustrate its logical relationship to the rest of the code in the line.

if (thisVariable1 == thatVariable1 || thisVariable2 == thatVariable2 || thisVariable3 == thatVariable3)
    bar();

becomes:

if (thisVariable1 == thatVariable1 ||
        thisVariable2 == thatVariable2 ||
        thisVariable3 == thatVariable3)
    bar();

2.4 Brackets

Use a pure-block, fully bracketed style for blocks of code. This means put brackets around all conditional code blocks, even one-line blocks.

if(statement == true)
{
    foo_true();
}
else
{
    foo_false();
}

2.5 Spaces

  • Insert space padding around operators. E.g.,
if (foo == 2)
    a = bar((b - c) * a, d--);

3. Naming Conventions

  • All identifiers (variables, constants, Classes etc. ) declared should have meaningful names.
  • Have naming conventions to differentiate between local and global data.
  • Identifiers may have their types attached to their names for clarity and consistency.
  • In case where the language has support for header file, ensure all user defined header file should have the same name as the source file that is referenced in.
  • Names should be readable and self documenting. Abbreviations and contractions are to be discouraged. Abbreviations are allowed when they follow common usage within the domain.
  • Identifiers should not exceed 31 characters.

3.1 Files

File names should be named with lower case and underscores.

adsp_driver_adc.c 
adsp_driver_adc.h

3.2 Macros

Macro names should be named with all capitals and underscores.

#define ROUND(x) ...
#define PI_ROUNDED 3.0

3.3 Variables

Data TypePrefix/postfixSample Variable Names
general variablelowerCamelCasesequenceNo
constkconst int32_t kSequenceNo
staticsstatic int32_t sSequenceNo
enumerated data typeeeCapabilityMode
arrayaint32_t aSequenceNo[10]
pointerpint32_t *pSequenceNo
globalggAudioStruct
structPascalCase_sAudioStruct_s
typedefPascalCase_ttypedef struct AudioInfo_st { } AudioInfo_t;
member variable (C++)mmClassMember
class (C++)Pascal Caseclass AudioObject
template (C++)Ttemplate<…> class TAudioTemp
namespace (C++)Nnamespace NAudioName …
  • Use <stdint.h> (uint8_t, int32_t etc).
  • Use the smallest required scope.
  • Variables in a file (outside functions) are always static.
  • Avoid to use global variables (use functions to set/get static variables).
  • # &,* is aligned to names, e.g., uint32_t *pAddress

3.4 Functions

Global function names start with lower case module name + underscore + camel case function name. E.g., one function in doxygen.c is

uint32_t doxygen_theFirstFunction(uint32_t param1, uint32_t param2);

one function in adsp_driver_adc.c is

void adsp_driver_adc_readValue(uint8_t channel);

Local static function names apply lowerCamelCase rule, e.g., one function in adsp_driver_adc.c is

// file adsp_driver_adc.c
static void getInstance(void)

Functions in C++ class should always apply PascalCase rule, e.g. ,

class AudioObject
{
    uint8_t *GetObjectName(void);
}

4. Comments

Comments are used for the benefits of code readers as well as developers. So it is recommended that all comments should be written with simple and straightforward words, and English words only.

Doxygen is applied to generate documents according to code comments. Therefore, the rules of Doxygen should be strictly followed.

For better document graphic illustration, Grapghviz is recommended to be installed and used in Doxygen. Both Doxygen and Grapghviz can be installed in Cygwin.

Download link:

Grapghviz

https://graphviz.gitlab.io/_pages/Download/windows/graphviz-2.38.msi​graphviz.gitlab.io

Doxygen

http://doxygen.nl/files/doxygen-1.8.18-setup.exe​doxygen.nl

4.1 File Banners

Every header file should have a function banner as follows :

/*****************************************************************************
 * Portfolio Info
 ****************************************************************************/
 
/**
 * @file header.h
 * @brief Brief file introduction.
 *
 * Detailed file introduction.
 *
 * @author Name
 * @date day month year
 * @see Related link.
 */

/** 
 * @addtogroup API name
 * @brief Brief API description.
 * 
 * Detailed api description.
 * 
 * @{
 */

#ifndef _HEADER_NAME_H
#define _HEADER_NAME_H

#ifdef __cplusplus
extern "C" {
#endif

/* INCLUDE FILES */

/* GLOBAL DEFINES */

/* GLOBAL VARIABLES */

/* GLOBAL FUNCTIONS */

 ...

#ifdef __cplusplus
}
#endif

#endif /* _HEADER_NAME_H */
/** @}*/

Every source file should have a function banner as follows :

/*****************************************************************************
 * Portfolio Info
 ****************************************************************************/
 
/**
 * @file file.c
 * @brief Brief introduction.
 *
 * Here typically goes a more extensive explanation of what the source contains.
 *
 * @author Name
 * @date day month year
 * @see Related link.
 */

/* INCLUDE FILES */

/* MODULE CONSTANTS */

/* MODULE VARIABLES */

/* MODULE PROTOTYPES */

/* MODULE FUNCTIONS */

4.2 Function Banners

Detailed function banners should be used in header files since the corresponding c file(s) are not necessarily made available to the user, nor should the user need to read the c file in order to understand how the functions there should be used. Everything should be made obvious from the header file alone.

For static functions in c file(s), function banners are required to use for developing maintenance.

Every function should have a function banner as follows:

/**
 * @brief Brief introduction.
 *
 * Detailed introduction.
 *
 * @param [in] param1 Input parameter.
 * @param [out] param2 Output parameter.
 *
 * @return Describe what the function returns.
 * @retval XXX_OK return value.
 * 
 * @see Related link.
 * @note Something to note.
 * @warning Warning.
 */

4.3 Comments in Codes

For comments in codes, /* */ is suggested to use. And space are recommended to add before and after statements. E.g., Use /* example comment */ instead of /*examplecomment*/

5. Code Format Tool

Windows

AStyle is used to unify the code format. Before running the batch file, make sure AStyle.exe is included in system PATH. The format rules are configured in file config.astyle and all codes will be formated by running astyle.bat.

Reference

Google C++ coding style (good)
https://zh-google-styleguide.readthedocs.io/en/latest/google-cpp-styleguide/scoping/

RDK coding guidelines
https://wiki.rdkcentral.com/display/RDK/Coding+Guidelines

NASA coding style (bad)
http://web.archive.org/web/20190125125043if_/http://homepages.inf.ed.ac.uk/dts/pm/Papers/nasa-c-style.pdf

C/C++ coding style by Fred Richards (good)
http://index-of.co.uk/C++/C%20&%20C++%20Programming%20Style%20Guidlines.pdf

Indian Hill C style
https://www.maultech.com/chrislott/resources/cstyle/indhill-cstyle.pdf