什么是C中的“回调”以及如何实现?


153

根据我的读物,Core Audio在很大程度上依赖于回调(和C ++,但这是另一回事)。

我了解设置功能的概念(某种程度上),该功能被另一个功能反复调用以完成任务。我只是不了解他们如何设置以及他们如何实际工作。任何示例将不胜感激。

Answers:


203

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次,并将返回的值分配给给定数组中的元素。


2
我在这里可能是错的,但populate_array中调用函数指针的行不应为:array [i] =(* getNextValue)(); ?
内森·费尔曼

40
取消引用运算符与函数指针一样是函数指针的可选项。myfunc(...)=(* myfunc)(...)和&myfunc = myfunc
aib

1
@NathanFellman我刚刚阅读了Expert C编程,它很好地解释了函数指针。
马特·克拉克森

1
@johnny因为标准是这样。查看已发表的评论。
2014年

3
@Patrick:populateArray在一个库中(写于12年前),您自己写了getNextRandomValue(昨天);因此它不能直接调用它。想一想您自己提供比较器的库排序功能。
aib

121

这是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);

正是我所需要的。如果您的用户希望传递回调函数中所需的自定义数据(例如设备句柄),则userdata部分将非常有用。
uceumern 2015年

验证问题:回调typedef是否带有星号,因为它是指向函数地址的指针?如果缺少星号,那会不正确吗?如果不正确,也有在GitHub上思科的libsrtp库两名失踪的明星: github.com/cisco/libsrtp/blob/... github.com/cisco/libsrtp/blob/...
twildeman

@twildeman似乎很简单,可以在带有警告的标准C模式下进行编译来回答您自己的问题。您还可以编写最小化的测试程序。诸如中的代码libsrtp不会发出警告。然后,我认为,当这样的类型作为函数参数出现时,就需要“衰减”到函数的指针,就像数组会衰减到指向其第一个元素的指针一样,所以最后也会发生同样的事情无论哪种方式。这有趣的,但是,我发现这种类型定义的讨论,甚至不看一眼这个方面,而是集中在声明它的原型或指针
underscore_d

我不知道这是做什么的,并且无法成功编译。谁能详细解释它或填充其余代码以便成功编译?
林迪(Andy Lin)

20

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

#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;
}

9

C中的回调函数等效于分配给另一个函数使用的函数参数/变量。维基范例

在下面的代码中,

#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函数的内容,则可以从实例化开始分配另一个函数在循环内运行。


6

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中,将启动I / O操作,然后在完成操作后调用回调。

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

我从Microsoft通过https://msdn.microsoft.com/zh-cn/library/windows/desktop/ms737526(v=vs.85).aspx提供的accept()功能示例中提取了大多数WinSock代码。

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

该示例应用程序是使用Visual Studio 2017 Community Edition作为控制台应用程序创建的,并且使用的是Microsoft WinSock版本的套接字。对于Linux应用程序,需要用Linux替代品替换WinSock函数,而改为使用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- * NIX中使用异步回调的另一个具体示例是异步信号及其信号处理程序。这是在Linux [link](stackoverflow.com/questions/6949025/…)中如何处理信号处理程序的出色描述。
drlolly

4

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

回调也用于GUI编程中。在GTK +教程对一个漂亮的部分信号和回调理论


2

这篇维基百科文章使用C语言编写了一个示例。

一个很好的例子是,为增强Apache Web服务器而编写的新模块通过将函数指针传递给主apache进程进行注册,以便将这些函数调回以处理Web页面请求。


0

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


0

通过示例来理解一个想法要容易得多。到目前为止,关于C中的回调函数的介绍是不错的答案,但是使用该功能的最大好处可能是保持代码整洁,整洁。

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

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

compare_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);
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.