Apa itu "panggilan balik" dalam C dan bagaimana mereka diterapkan?

153

Dari bacaan yang telah saya lakukan, Core Audio sangat bergantung pada callback (dan C ++, tapi itu cerita lain).

Saya memahami konsep (semacam) pengaturan fungsi yang dipanggil oleh fungsi lain berulang kali untuk menyelesaikan tugas. Saya hanya tidak mengerti bagaimana mereka diatur dan bagaimana mereka sebenarnya bekerja. Setiap contoh akan dihargai.

noizetoys
sumber

Jawaban:

203

Tidak ada "callback" di C - tidak lebih dari konsep pemrograman generik lainnya.

Mereka diimplementasikan menggunakan pointer fungsi. Ini sebuah contoh:

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

Di sini, populate_arrayfungsi mengambil pointer fungsi sebagai parameter ketiga, dan memanggilnya untuk mendapatkan nilai untuk mengisi array. Kami telah menulis panggilan balik getNextRandomValue, yang mengembalikan nilai acak-ish, dan meneruskan pointer ke sana populate_array. populate_arrayakan memanggil fungsi panggilan balik kami 10 kali dan menetapkan nilai yang dikembalikan ke elemen dalam array yang diberikan.

aib
sumber
2
Saya mungkin salah di sini, tetapi seharusnya bukan baris di populate_array yang memanggil fungsi pointer menjadi: array [i] = (* getNextValue) (); ?
Nathan Fellman
40
Operator dereferensi adalah opsional dengan pointer fungsi, seperti alamat operator. myfunc (...) = (* myfunc) (...) dan & myfunc = myfunc
aib
1
@NathanFellman Saya baru saja membaca Expert C Programming dan menjelaskan fungsi pointer yang memanggil dengan baik.
Matt Clarkson
1
@ Johnny Karena standar mengatakannya. Lihatlah komentar yang dibalik.
aib
3
@ Patrick: populateArray ada di perpustakaan (dan ditulis 12 tahun yang lalu) dan Anda menulis sendiri getNextRandomValue (kemarin); jadi tidak bisa memanggilnya secara langsung. Pikirkan fungsi semacam perpustakaan yang Anda sediakan sendiri untuk pembanding.
aib
121

Berikut adalah contoh panggilan balik di C.

Katakanlah Anda ingin menulis beberapa kode yang memungkinkan pendaftaran panggilan balik dipanggil saat beberapa peristiwa terjadi.

Pertama-tama tentukan jenis fungsi yang digunakan untuk callback:

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

Sekarang, tentukan fungsi yang digunakan untuk mendaftarkan panggilan balik:

int event_cb_register(event_cb_t cb, void *userdata);

Kode ini akan terlihat seperti yang mendaftarkan panggilan balik:

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);
...

Di bagian internal pengirim acara, panggilan balik dapat disimpan dalam struct yang terlihat seperti ini:

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

Ini adalah kode yang mengeksekusi panggilan balik.

struct event_cb *callback;

...

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

callback->cb(event, callback->data);
Russell Bryant
sumber
Apa yang saya butuhkan. Bagian data pengguna sangat membantu jika pengguna Anda ingin meneruskan data khusus (mis. Gagang perangkat) yang diperlukan dalam fungsi panggilan balik.
uceumern
pertanyaan verifikasi: Apakah callback typedef dengan tanda bintang karena itu adalah penunjuk ke alamat fungsi? Jika tanda bintang tidak ada, apakah itu salah? Jika itu salah maka ada dua bintang yang hilang di pustaka libsrtp dari cisco di github: github.com/cisco/libsrtp/blob/… github.com/cisco/libsrtp/blob/…
twildeman
@twildeman Tampaknya sepele untuk menjawab pertanyaan Anda sendiri dengan menyusun dalam mode C Standar dengan peringatan menyala. Anda juga dapat menulis program tes yang diperkecil. Kode seperti yang ada di dalam libsrtptidak memberikan peringatan. Saya menduga, kemudian, bahwa ketika jenis seperti itu muncul sebagai argumen fungsi, diperlukan untuk 'meluruh' ke fungsi-pointer, sama seperti array peluruhan ke pointer ke elemen pertama mereka, sehingga hal yang sama terjadi pada akhirnya bagaimanapun juga. Ini adalah menarik, meskipun, bahwa diskusi dari typedef seperti saya telah menemukan bahkan tidak melirik aspek ini, lebih fokus pada mendeklarasikan prototipe atau pointer dengan itu
underscore_d
Saya tidak tahu apa ini, dan tidak dapat dikompilasi dengan sukses. Adakah yang bisa menjelaskannya secara terperinci atau mengisi kode lainnya agar kompilasi berhasil?
Andy Lin
20

Program panggilan balik sederhana. Semoga ini menjawab pertanyaan Anda.

#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;
}
Gautham Kantharaju
sumber
9

Fungsi panggilan balik dalam C adalah setara dengan parameter fungsi / variabel yang ditetapkan untuk digunakan dalam fungsi lain. Contoh Wiki

Dalam kode di bawah ini,

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

Fungsi (* numberSource) di dalam panggilan fungsi PrintTwoNumbers adalah fungsi untuk "memanggil kembali" / mengeksekusi dari dalam PrintTwoNumbers seperti yang ditentukan oleh kode saat dijalankan.

Jadi jika Anda memiliki sesuatu seperti fungsi pthread, Anda dapat menetapkan fungsi lain untuk dijalankan di dalam loop dari instantiasinya.

daemondave
sumber
6

Panggilan balik dalam C adalah fungsi yang disediakan untuk fungsi lain untuk "memanggil kembali ke" di beberapa titik ketika fungsi lain melakukan tugasnya.

Ada dua cara callback digunakan : callback sinkron dan callback asinkron. Callback sinkron disediakan untuk fungsi lain yang akan melakukan beberapa tugas dan kemudian kembali ke pemanggil dengan tugas selesai. Callback asinkron disediakan untuk fungsi lain yang akan memulai tugas dan kemudian kembali ke pemanggil dengan tugas yang mungkin tidak selesai.

Callback sinkron biasanya digunakan untuk memberikan delegasi ke fungsi lain yang fungsi lain mendelegasikan beberapa langkah tugas. Contoh klasik dari delegasi ini adalah fungsi bsearch()dan qsort()dari C Standard Library. Kedua fungsi ini mengambil panggilan balik yang digunakan selama tugas fungsi menyediakan sehingga jenis data yang dicari, dalam kasus bsearch(), atau diurutkan, dalam kasus qsort(), tidak perlu diketahui oleh fungsi yang sedang bekas.

Misalnya di sini adalah program sampel kecil dengan bsearch()menggunakan berbagai fungsi perbandingan, panggilan balik sinkron. Dengan memungkinkan kami mendelegasikan perbandingan data ke fungsi panggilan balik, bsearch()fungsi ini memungkinkan kami untuk memutuskan pada waktu berjalan perbandingan apa yang ingin kami gunakan. Ini sinkron karena ketika bsearch()fungsi mengembalikan tugas selesai.

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

Callback asinkron berbeda karena ketika fungsi yang dipanggil di mana kami memberikan panggilan balik, tugas mungkin tidak selesai. Jenis panggilan balik ini sering digunakan dengan I / O asinkron di mana operasi I / O dimulai dan kemudian ketika selesai, panggilan balik dipanggil.

Dalam program berikut ini kami membuat soket untuk mendengarkan permintaan koneksi TCP dan ketika permintaan diterima, fungsi melakukan mendengarkan kemudian memanggil fungsi panggilan balik yang disediakan. Aplikasi sederhana ini dapat dilakukan dengan menjalankannya di satu jendela saat menggunakan telnetutilitas atau browser web untuk mencoba terhubung di jendela lain.

Saya mengangkat sebagian besar kode WinSock dari contoh yang disediakan Microsoft dengan accept()fungsi di https://msdn.microsoft.com/en-us/library/windows/desktop/ms737526(v=vs.85).aspx

Aplikasi ini memulai listen()pada host lokal, 127.0.0.1, menggunakan port 8282 sehingga Anda dapat menggunakan salah satu telnet 127.0.0.1 8282atau http://127.0.0.1:8282/.

Aplikasi sampel ini dibuat sebagai aplikasi konsol dengan Visual Studio 2017 Community Edition dan menggunakan soket versi Microsoft WinSock. Untuk aplikasi Linux, fungsi WinSock perlu diganti dengan alternatif Linux dan pustaka thread Windows akan digunakan pthreadssebagai gantinya.

#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);
}
Richard Chambers
sumber
Jawaban luar biasa, menunjukkan call-back yang sinkron dan asinkron. Contoh konkret lain dari penggunaan call-back asinkron dalam C- * NIX adalah sinyal asinkron dan penangan sinyalnya. Berikut ini adalah deskripsi yang sangat baik tentang bagaimana penangan sinyal diproses di Linux [tautan] ( stackoverflow.com/questions/6949025/… ).
drlolly
4

Panggilan balik dalam C biasanya diimplementasikan menggunakan pointer fungsi dan pointer data terkait. Anda meneruskan fungsi on_event()dan pointer data Anda ke fungsi kerangka kerja watch_events()(misalnya). Ketika suatu peristiwa terjadi, fungsi Anda dipanggil dengan data Anda dan beberapa data khusus acara.

Callback juga digunakan dalam pemrograman GUI. The GTK + tutorial memiliki bagian bagus di teori sinyal dan callback .

John Millikin
sumber
2

Ini artikel wikipedia memiliki contoh di C.

Contoh yang baik adalah bahwa modul-modul baru ditulis untuk menambah register server Web Apache dengan proses apache utama dengan mengirimkan mereka fungsi pointer sehingga fungsi-fungsi tersebut dipanggil kembali untuk memproses permintaan halaman web.

Leonard
sumber
0

Biasanya ini dapat dilakukan dengan menggunakan pointer fungsi, yaitu variabel khusus yang menunjuk ke lokasi memori suatu fungsi. Anda kemudian dapat menggunakan ini untuk memanggil fungsi dengan argumen tertentu. Jadi mungkin akan ada fungsi yang mengatur fungsi panggilan balik. Ini akan menerima pointer fungsi dan kemudian menyimpan alamat itu di suatu tempat di mana ia dapat digunakan. Setelah itu ketika peristiwa yang ditentukan dipicu, ia akan memanggil fungsi itu.

mdec
sumber
0

Jauh lebih mudah untuk memahami ide melalui contoh. Apa yang telah diberitahukan tentang fungsi panggilan balik di C sejauh ini adalah jawaban yang bagus, tetapi mungkin manfaat terbesar dari menggunakan fitur ini adalah untuk menjaga kode tetap bersih dan tidak berantakan.

Contoh

Kode C berikut mengimplementasikan penyortiran cepat. Baris paling menarik dalam kode di bawah ini adalah yang ini, di mana kita bisa melihat fungsi callback beraksi:

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

Compare_s2b adalah nama fungsi yang digunakan qsort () untuk memanggil fungsi. Ini membuat qsort () jadi tidak berantakan (karenanya lebih mudah dirawat). Anda cukup memanggil fungsi dengan nama dari dalam fungsi lain (tentu saja, deklarasi prototipe fungsi, paling tidak, harus didahului sebelum dapat dipanggil dari fungsi lain).

Kode Lengkap

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