programing tip

C에서 "콜백"이란 무엇이며 어떻게 구현됩니까?

itbloger 2020. 6. 16. 20:36
반응형

C에서 "콜백"이란 무엇이며 어떻게 구현됩니까?


내가 읽은 독서에서 Core Audio는 콜백 (및 C ++에 크게 의존하지만 다른 이야기입니다).

작업을 수행하기 위해 다른 함수에서 반복적으로 호출하는 함수를 설정하는 개념 (일종)을 이해합니다. 나는 그들이 어떻게 설정되고 실제로 어떻게 작동하는지 이해하지 못합니다. 모든 예가 이해 될 것이다.


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_array. populate_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의 콜백 함수는 다른 함수 내에서 사용되도록 지정된 함수 매개 변수 / 변수와 같습니다. 위키 예제

아래 코드에서

#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의 콜백은 일반적으로 함수 포인터와 관련 데이터 포인터를 사용하여 구현됩니다. 함수 on_event()및 데이터 포인터를 프레임 워크 함수에 전달합니다 watch_events()(예 :). 이벤트가 발생하면 데이터 및 일부 이벤트 특정 데이터로 함수가 호출됩니다.

콜백은 GUI 프로그래밍에도 사용됩니다. GTK + 튜토리얼 온 멋진 부분이 신호와 콜백의 이론을 .


위키 백과 기사 에는 C로 된 예제가 있습니다.

좋은 예는 Apache 웹 서버 레지스터에 함수 포인터를 전달하여 Apache 웹 서버 레지스터를 기능 보강하기 위해 작성된 새 모듈이 해당 함수를 다시 호출하여 웹 페이지 요청을 처리하는 것입니다.


C의 콜백은 다른 함수가 작업을 수행하는 시점에서 "콜백"하기 위해 다른 함수에 제공되는 함수입니다.

콜백을 사용하는 방법 에는 동기식 콜백과 비동기식 콜백의 두 가지가 있습니다. 다른 작업에 동기식 콜백이 제공되어 일부 작업을 수행 한 다음 작업이 완료된 상태로 호출자에게 반환됩니다. 작업을 시작한 다음 작업이 완료되지 않은 상태로 호출자에게 반환하는 다른 함수에 비동기 콜백이 제공됩니다.

동기식 콜백은 일반적으로 다른 함수가 작업의 일부 단계를 위임하는 다른 함수에 대리자를 제공하는 데 사용됩니다. 이 위임의 전형적인 예는 함수 bsearch()qsort()C 표준 라이브러리입니다. 이 두 함수는 함수가 제공하는 작업 중에 사용되는 콜백을 사용하여의 경우 검색되는 데이터의 유형 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유틸리티 또는 웹 브라우저를 사용하여 다른 창에서 연결을 시도하는 동안 한 창에서 실행하여 실행할 수 있습니다 .

Microsoft accept()https://msdn.microsoft.com/en-us/library/windows/desktop/ms737526(v=vs.85).aspx 에서 제공하는 예제에서 대부분의 WinSock 코드를 들었습니다.

이 응용 프로그램을 시작 listen()당신이 중 하나를 사용할 수 있도록 포트 8282를 사용하여 로컬 호스트, 127.0.0.1에 telnet 127.0.0.1 8282또는 http://127.0.0.1:8282/.

이 샘플 응용 프로그램은 Visual Studio 2017 Community Edition을 사용하여 콘솔 응용 프로그램으로 만들어졌으며 Microsoft WinSock 버전의 소켓을 사용하고 있습니다. 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);
}

일반적으로 이것은 함수의 메모리 위치를 가리키는 특수 변수 인 함수 포인터를 사용하여 수행 할 수 있습니다. 그런 다음이를 사용하여 특정 인수로 함수를 호출 할 수 있습니다. 따라서 콜백 함수를 설정하는 함수가있을 것입니다. 이것은 함수 포인터를 받아들이고 그 주소를 사용할 수있는 곳에 저장합니다. 그런 다음 지정된 이벤트가 트리거되면 해당 함수를 호출합니다.


It is lot easier to understand an idea through example. What have been told about callback function in C so far are great answers, but probably the biggest benefit of using the feature is to keep the code clean and uncluttered.

Example

The following C code implements quick sorting. The most interesting line in the code below is this one, where we can see the callback function in action:

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

The compare_s2b is the name of function which qsort() is using to call the function. This keeps qsort() so uncluttered (hence easier to maintain). You just call a function by name from inside another function (of course, the function prototype declaration, at the least, must precde before it can be called from another function).

The Complete Code

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

참고URL : https://stackoverflow.com/questions/142789/what-is-a-callback-in-c-and-how-are-they-implemented

반응형