C 语言中的“回调”是什么? 它们是如何实现的?

根据我所做的阅读,Core Audio 严重依赖于回调(和 C + + ,但那是另一回事)。

我理解设置一个由另一个函数重复调用以完成任务的函数的概念。我只是不明白他们是怎么安排的,他们到底是怎么工作的。任何例子都可以。

343702 次浏览

C 语言中的回调通常使用函数指针和关联的数据指针来实现。将函数 on_event()和数据指针传递给框架函数 watch_events()(例如)。当事件发生时,将使用数据和一些特定于事件的数据调用函数。

回调也用于 GUI 编程。 GTK + 教程信号与回调理论上有一个很好的部分。

通常这可以通过使用一个函数指针来实现,这是一个特殊的变量,它指向一个函数的内存位置。然后您可以使用这个函数调用具有特定参数的函数。所以可能会有一个函数来设置回调函数。这将接受一个函数指针,然后将该地址存储在可以使用的地方。在此之后,当指定的事件被触发时,它将调用该函数。

这个 维基百科文章有一个 C 语言的例子。

一个很好的例子是,新的模块通过传递函数指针来增加主 apache 进程的 Apache HTTP Server 寄存器,这样这些函数就会被调用回来处理网页请求。

C 语言中没有“回调”这个概念,其他的泛型概念也没有这个概念。

它们是使用函数指针实现的,下面是一个例子:

void populate_array(int *array, size_t arraySize, int (*getNextValue)(void))
{
for (size_t i=0; i<arraySize; i++)
array[i] = getNextValue();
}


int getNextRandomValue(void)
{
return rand();
}


int main(void)
{
int myarray[10];
populate_array(myarray, 10, getNextRandomValue);
...
}

在这里,populate_array函数接受一个函数指针作为它的第三个参数,并调用它来获取用于填充数组的值。我们已经编写了回调函数 getNextRandomValue,它返回一个随机值,并将一个指向它的指针传递给 populate_arraypopulate_array将调用我们的回调函数10次,并将返回的值分配给给定数组中的元素。

下面是 C 语言中回调的一个例子。

假设您想编写一些代码,允许在发生某些事件时调用注册回调。

首先定义用于回调的函数类型:

typedef void (*event_cb_t)(const struct event *evt, void *userdata);

现在,定义一个用于注册回调的函数:

int event_cb_register(event_cb_t cb, void *userdata);

下面是注册回调的代码:

static void my_event_cb(const struct event *evt, void *data)
{
/* do stuff and things with the event */
}


...
event_cb_register(my_event_cb, &my_custom_data);
...

在事件调度程序的内部,回调可能存储在一个结构中,类似于下面这样:

struct event_cb {
event_cb_t cb;
void *data;
};

执行回调的代码如下所示。

struct event_cb *callback;


...


/* Get the event_cb that you want to execute */


callback->cb(event, callback->data);

一个简单的回拨程序。希望它能回答你的问题。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include "../../common_typedef.h"


typedef void (*call_back) (S32, S32);


void test_call_back(S32 a, S32 b)
{
printf("In call back function, a:%d \t b:%d \n", a, b);
}


void call_callback_func(call_back back)
{
S32 a = 5;
S32 b = 7;


back(a, b);
}


S32 main(S32 argc, S8 *argv[])
{
S32 ret = SUCCESS;


call_back back;


back = test_call_back;


call_callback_func(back);


return ret;
}

C 语言中的回调函数相当于分配给另一个 function.维基示例中使用的函数参数/变量

在下面的代码中,

#include <stdio.h>
#include <stdlib.h>


/* The calling function takes a single callback as a parameter. */
void PrintTwoNumbers(int (*numberSource)(void)) {
printf("%d and %d\n", numberSource(), numberSource());
}


/* A possible callback */
int overNineThousand(void) {
return (rand() % 1000) + 9001;
}


/* Another possible callback. */
int meaningOfLife(void) {
return 42;
}


/* Here we call PrintTwoNumbers() with three different callbacks. */
int main(void) {
PrintTwoNumbers(&rand);
PrintTwoNumbers(&overNineThousand);
PrintTwoNumbers(&meaningOfLife);
return 0;
}

函数调用 PrintTwoNumbers 中的函数(* numberSource)是一个在运行时由代码指示从 PrintTwoNumbers 中“回调”/执行的函数。

所以如果你有一个类似 pthread 的函数,你可以指定另一个函数在循环中运行,从它的实例化开始。

C 语言中的回调函数是一个提供给另一个函数的函数,当另一个函数执行任务时,该函数在某个时刻“回调”。

使用回调的两种方式: 同步回调和异步回调。同步回调被提供给另一个函数,这个函数将执行一些任务,然后在任务完成后返回给调用者。异步回调提供给另一个函数,该函数将启动一个任务,然后返回给调用者,而该任务可能还没有完成。

同步回调

同步回调通常用于为另一个函数提供委托,而另一个函数将任务的某个步骤委托给该函数。这种委托的经典例子是 C 标准库中的函数 bsearch()qsort()。这两个函数都接受一个回调函数,这个回调函数在函数提供的任务期间使用,这样,在 bsearch()的情况下,被搜索的数据的类型,或者在 qsort()的情况下,被排序的数据的类型,不需要被所使用的函数知道。

例如,下面是一个使用不同比较函数的 bsearch()小示例程序,演示了同步回调。通过允许我们将数据比较委托给一个回调函数,bsearch()函数允许我们在运行时决定要使用哪种比较。这是同步的,因为当 bsearch()函数返回时,任务就完成了。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>


typedef struct {
int iValue;
int kValue;
char label[6];
} MyData;


int cmpMyData_iValue (MyData *item1, MyData *item2)
{
if (item1->iValue < item2->iValue) return -1;
if (item1->iValue > item2->iValue) return 1;
return 0;
}


int cmpMyData_kValue (MyData *item1, MyData *item2)
{
if (item1->kValue < item2->kValue) return -1;
if (item1->kValue > item2->kValue) return 1;
return 0;
}


int cmpMyData_label (MyData *item1, MyData *item2)
{
return strcmp (item1->label, item2->label);
}


void bsearch_results (MyData *srch, MyData *found)
{
if (found) {
printf ("found - iValue = %d, kValue = %d, label = %s\n", found->iValue, found->kValue, found->label);
} else {
printf ("item not found, iValue = %d, kValue = %d, label = %s\n", srch->iValue, srch->kValue, srch->label);
}
}


int main ()
{
MyData dataList[256] = {0};


{
int i;
for (i = 0; i < 20; i++) {
dataList[i].iValue = i + 100;
dataList[i].kValue = i + 1000;
sprintf (dataList[i].label, "%2.2d", i + 10);
}
}


//  ... some code then we do a search
{
MyData srchItem = { 105, 1018, "13"};
MyData *foundItem = bsearch (&srchItem, dataList, 20, sizeof(MyData), cmpMyData_iValue );


bsearch_results (&srchItem, foundItem);


foundItem = bsearch (&srchItem, dataList, 20, sizeof(MyData), cmpMyData_kValue );
bsearch_results (&srchItem, foundItem);


foundItem = bsearch (&srchItem, dataList, 20, sizeof(MyData), cmpMyData_label );
bsearch_results (&srchItem, foundItem);
}
}

异步回调

异步回调的不同之处在于,当我们提供回调的被调用函数返回时,任务可能无法完成。这种类型的回调通常与异步 I/O 一起使用,其中一个 I/O 操作被启动,然后当它完成时,回调被调用。

在下面的程序中,我们创建一个套接字来监听 TCP 连接请求,当接收到请求时,执行监听的函数将调用提供的回调函数。这个简单的应用程序可以在一个窗口中运行,同时使用 telnet实用工具或 Web 浏览器尝试在另一个窗口中连接。

我从 Microsoft 提供的 accept()函数在 https://msdn.microsoft.com/en-us/library/windows/desktop/ms737526(v=vs.85).aspx的示例中提取了大部分 WinSock 代码

该应用程序使用端口8282在本地主机127.0.0.1上启动 listen(),因此您可以使用 telnet 127.0.0.1 8282http://127.0.0.1:8282/

此示例应用程序是使用 VisualStudio2017社区版创建的控制台应用,它使用的是 MicrosoftwinSock 版本的套接字。对于 Linux 应用程序,WinSock 函数需要用 Linux 替代品替换,而 Windows 线程库将使用 pthreads

#include <stdio.h>
#include <winsock2.h>
#include <stdlib.h>
#include <string.h>


#include <Windows.h>


// Need to link with Ws2_32.lib
#pragma comment(lib, "Ws2_32.lib")


// function for the thread we are going to start up with _beginthreadex().
// this function/thread will create a listen server waiting for a TCP
// connection request to come into the designated port.
// _stdcall modifier required by _beginthreadex().
int _stdcall ioThread(void (*pOutput)())
{
//----------------------
// Initialize Winsock.
WSADATA wsaData;
int iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
if (iResult != NO_ERROR) {
printf("WSAStartup failed with error: %ld\n", iResult);
return 1;
}
//----------------------
// Create a SOCKET for listening for
// incoming connection requests.
SOCKET ListenSocket;
ListenSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (ListenSocket == INVALID_SOCKET) {
wprintf(L"socket failed with error: %ld\n", WSAGetLastError());
WSACleanup();
return 1;
}
//----------------------
// The sockaddr_in structure specifies the address family,
// IP address, and port for the socket that is being bound.
struct sockaddr_in service;
service.sin_family = AF_INET;
service.sin_addr.s_addr = inet_addr("127.0.0.1");
service.sin_port = htons(8282);


if (bind(ListenSocket, (SOCKADDR *)& service, sizeof(service)) == SOCKET_ERROR) {
printf("bind failed with error: %ld\n", WSAGetLastError());
closesocket(ListenSocket);
WSACleanup();
return 1;
}
//----------------------
// Listen for incoming connection requests.
// on the created socket
if (listen(ListenSocket, 1) == SOCKET_ERROR) {
printf("listen failed with error: %ld\n", WSAGetLastError());
closesocket(ListenSocket);
WSACleanup();
return 1;
}
//----------------------
// Create a SOCKET for accepting incoming requests.
SOCKET AcceptSocket;
printf("Waiting for client to connect...\n");


//----------------------
// Accept the connection.
AcceptSocket = accept(ListenSocket, NULL, NULL);
if (AcceptSocket == INVALID_SOCKET) {
printf("accept failed with error: %ld\n", WSAGetLastError());
closesocket(ListenSocket);
WSACleanup();
return 1;
}
else
pOutput ();   // we have a connection request so do the callback


// No longer need server socket
closesocket(ListenSocket);


WSACleanup();
return 0;
}


// our callback which is invoked whenever a connection is made.
void printOut(void)
{
printf("connection received.\n");
}


#include <process.h>


int main()
{
// start up our listen server and provide a callback
_beginthreadex(NULL, 0, ioThread, printOut, 0, NULL);
// do other things while waiting for a connection. In this case
// just sleep for a while.
Sleep(30000);
}

通过例子更容易理解一个想法。 到目前为止,关于 C 语言中的回调函数的介绍都是很好的答案,但是使用该特性的最大好处可能是保持代码的整洁和整洁。

例子

下面的 C 代码实现了快速排序。 下面的代码中最有趣的一行是这一行,我们可以看到回调函数的运行情况:

qsort(arr,N,sizeof(int),compare_s2b);

Bill _ s2b 是 qsort ()用来调用该函数的函数名。这样可以使 qsort ()保持整洁(因此更容易维护)。您只需从另一个函数内部按名称调用一个函数(当然,函数原型声明至少必须在从另一个函数调用之前进行)。

完整密码

#include <stdio.h>
#include <stdlib.h>


int arr[]={56,90,45,1234,12,3,7,18};
//function prototype declaration


int compare_s2b(const void *a,const void *b);


int compare_b2s(const void *a,const void *b);


//arranges the array number from the smallest to the biggest
int compare_s2b(const void* a, const void* b)
{
const int* p=(const int*)a;
const int* q=(const int*)b;


return *p-*q;
}


//arranges the array number from the biggest to the smallest
int compare_b2s(const void* a, const void* b)
{
const int* p=(const int*)a;
const int* q=(const int*)b;


return *q-*p;
}


int main()
{
printf("Before sorting\n\n");


int N=sizeof(arr)/sizeof(int);


for(int i=0;i<N;i++)
{
printf("%d\t",arr[i]);
}


printf("\n");


qsort(arr,N,sizeof(int),compare_s2b);


printf("\nSorted small to big\n\n");


for(int j=0;j<N;j++)
{
printf("%d\t",arr[j]);
}


qsort(arr,N,sizeof(int),compare_b2s);


printf("\nSorted big to small\n\n");


for(int j=0;j<N;j++)
{
printf("%d\t",arr[j]);
}


exit(0);
}