C में "कॉलबैक" क्या है और उन्हें कैसे लागू किया जाता है?


153

मेरे द्वारा किए गए पढ़ने से, कोर ऑडियो कॉलबैक (और C ++ पर बहुत निर्भर करता है, लेकिन यह एक और कहानी है)।

मैं एक फ़ंक्शन सेट करने की अवधारणा (सॉर्ट) को समझता हूं जिसे किसी कार्य को पूरा करने के लिए किसी अन्य फ़ंक्शन द्वारा बार-बार कहा जाता है। मुझे अभी समझ नहीं आया कि वे कैसे सेट होते हैं और वे वास्तव में कैसे काम करते हैं। किसी भी उदाहरण की सराहना की जाएगी।

जवाबों:


203

सी में कोई "कॉलबैक" नहीं है - किसी भी अन्य सामान्य प्रोग्रामिंग अवधारणा से अधिक नहीं है।

वे फ़ंक्शन पॉइंटर्स का उपयोग करके कार्यान्वित किए जाते हैं। यहाँ एक उदाहरण है:

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 में लाइन नहीं होनी चाहिए जो फ़ंक्शन पॉइंटर कहता है: सरणी [i] = (* getNextValue) (); ?
नाथन फेलमैन 20

40
Dereference संचालक फंक्शन पॉइंटर्स के साथ वैकल्पिक है, जैसा कि एड्रेसऑफ़ ऑपरेटर है। myfunc (...) = (* myfunc) (...) और myfunc = myfunc
aib

1
@ नथनफेलमैन मैं सिर्फ एक्सपर्ट सी प्रोग्रामिंग पढ़ता हूं और यह बताता है कि फंक्शन पॉइंटर अच्छी तरह से कॉल कर रहा है।
मैट क्लार्कसन

1
@ जॉनी क्योंकि मानक ऐसा कहता है। ऊपर की टिप्पणी को देखो।
ऐब

3
@Patrick: populateArray एक पुस्तकालय में है (और 12 साल पहले लिखा गया था) और आपने लिखा getNextRandomValue खुद (कल); इसलिए इसे सीधे नहीं कह सकते। एक लाइब्रेरी सॉर्ट फंक्शन के बारे में सोचें, जिसकी तुलना आप खुद कंपैक्टर को करते हैं।
ऐब

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

मुझे जिस चीज की जरूरत थी। यदि आपके उपयोगकर्ता वें कॉलबैक फ़ंक्शन में आवश्यक कस्टम डेटा (जैसे डिवाइस हैंडल) पास करना चाहते हैं, तो यूज़रडेटा भाग बहुत उपयोगी है।
uceumern

सत्यापन प्रश्न: क्या तारांकन चिह्न के साथ कॉलबैक टाइपफेड है क्योंकि यह फ़ंक्शन पते का एक संकेतक है? यदि तारांकन गायब है, तो क्या वह गलत होगा? यदि वह गलत है, तो जीथब पर सिस्को के लिबासर्टप लाइब्रेरी में दो लापता सितारे हैं: github.com/cisco/libsrtp/blob/… github.com/cisco/libsrtp/blob/…
twildeman

@ वुडमैन को चेतावनी के साथ एक मानक सी मोड में संकलन करके अपने स्वयं के प्रश्न का उत्तर देना मामूली लगता है। आप एक न्यूनतम परीक्षण कार्यक्रम भी लिख सकते हैं। कोड के रूप में उन में libsrtpकोई चेतावनी देता है। मुझे लगता है कि तब, जब इस प्रकार का एक फ़ंक्शन तर्क के रूप में प्रकट होता है, तो पॉइंटर-टू-फंक्शन को 'क्षय' करना आवश्यक है, ठीक उसी तरह जैसे एरेस क्षय को उनके पहले तत्वों को इंगित करता है, इसलिए अंत में ऐसा ही होता है किसी भी तरह से। यह है , दिलचस्प हालांकि, कि मैंने पाया इस तरह के typedefs की चर्चा इस पहलू पर भी नहीं नज़र करते हैं बल्कि प्रोटोटाइप या इसके साथ संकेत घोषित करने पर ध्यान केंद्रित,
underscore_d

मुझे नहीं पता कि यह क्या करता है, और यह सफलतापूर्वक संकलित होने में असमर्थ है। क्या कोई इसे विस्तृत तरीके से समझा सकता है या बाकी कोड को सफलतापूर्वक संकलन के लिए भर सकता है?
एंडी लिन

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 एक फ़ंक्शन है जिसे कॉल बैक के रूप में प्रिंटटाउनन्यूट्स के अंदर से "कॉल बैक" / निष्पादित किया जाता है क्योंकि यह चलता है।

इसलिए यदि आपके पास एक पायथ्रेड फ़ंक्शन जैसा कुछ है, तो आप इसके तात्कालिकता से लूप के अंदर चलाने के लिए एक और फ़ंक्शन असाइन कर सकते हैं।


6

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 ऑपरेशन शुरू किया जाता है और फिर जब यह पूरा हो जाता है, तो कॉलबैक लागू किया जाता है।

निम्न प्रोग्राम में हम टीसीपी कनेक्शन अनुरोधों को सुनने के लिए एक सॉकेट बनाते हैं और जब एक अनुरोध प्राप्त होता है, तो सुनने वाला फ़ंक्शन तब प्रदान किए गए कॉलबैक फ़ंक्शन को आमंत्रित करता है। telnetउपयोगिता या वेब ब्राउज़र का उपयोग करके किसी अन्य विंडो में कनेक्ट करने का प्रयास करते हुए एक साधारण विंडो में इसे चलाकर इस सरल एप्लिकेशन का उपयोग किया जा सकता है ।

मैंने अधिकांश WinSock कोड को उस उदाहरण से उठाया, जिसे Microsoft https://msdn.microsoft.com/en-us/library/windows/desktop/ms737526(v=vs.85).aspxaccept() पर फ़ंक्शन के साथ प्रदान करता है।

यह एप्लिकेशन listen()स्थानीय होस्ट 127.0.0.1 पर शुरू होता है , पोर्ट 8282 का उपयोग करके ताकि आप telnet 127.0.0.1 8282या तो उपयोग कर सकें http://127.0.0.1:8282/

यह नमूना एप्लिकेशन विजुअल स्टूडियो 2017 कम्युनिटी एडिशन के साथ कंसोल एप्लिकेशन के रूप में बनाया गया था और यह सॉकेट्स के Microsoft WinSock संस्करण का उपयोग कर रहा है। लिनक्स एप्लिकेशन के लिए WinSock फ़ंक्शन को लिनक्स विकल्पों के साथ बदलना होगा और 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 में एसिंक्रोनस कॉल-बैक के उपयोग का एक और ठोस उदाहरण एसिंक्रोनस सिग्नल और उनके सिग्नल हैंडलर हैं। यहाँ एक उत्कृष्ट विवरण दिया गया है कि कैसे लिनक्स में सिग्नल संचालकों को संसाधित किया जाता है [लिंक] ( stackoverflow.com/questions/6949025/… )।
drlolly

4

C में कॉलबैक आमतौर पर फ़ंक्शन पॉइंटर्स और एक संबद्ध डेटा पॉइंटर का उपयोग करके लागू किया जाता है। आप अपने फ़ंक्शन on_event()और डेटा पॉइंटर्स को एक फ़्रेमवर्क फ़ंक्शन watch_events()(उदाहरण के लिए) पास करते हैं। जब कोई ईवेंट होता है, तो आपके डेटा और कुछ ईवेंट-विशिष्ट डेटा के साथ आपके फ़ंक्शन को कॉल किया जाता है।

जीयूआई प्रोग्रामिंग में कॉलबैक का भी उपयोग किया जाता है। जीटीके + ट्यूटोरियल पर एक अच्छी अनुभाग है संकेतों और कॉलबैक के सिद्धांत


2

इस विकिपीडिया लेख का C में एक उदाहरण है।

एक अच्छा उदाहरण यह है कि नए मॉड्यूल अपाचे वेब सर्वर रजिस्टर को मुख्य एपाचे प्रक्रिया के साथ उन्हें फ़ंक्शन पॉइंटर्स पास करके लिखा जाता है ताकि उन कार्यों को वेब पेज अनुरोधों को संसाधित करने के लिए वापस बुलाया जाए।


0

आमतौर पर यह एक फ़ंक्शन पॉइंटर का उपयोग करके किया जा सकता है, यह एक विशेष चर है जो किसी फ़ंक्शन के मेमोरी स्थान को इंगित करता है। आप विशिष्ट तर्कों के साथ फ़ंक्शन को कॉल करने के लिए इसका उपयोग कर सकते हैं। तो शायद एक फ़ंक्शन होगा जो कॉलबैक फ़ंक्शन सेट करता है। यह एक फ़ंक्शन पॉइंटर को स्वीकार करेगा और फिर उस पते को कहीं स्टोर करेगा जहां इसका उपयोग किया जा सकता है। उसके बाद जब निर्दिष्ट घटना को ट्रिगर किया जाता है, तो यह उस फ़ंक्शन को कॉल करेगा।


0

उदाहरण के माध्यम से किसी विचार को समझना बहुत आसान है। C में अब तक कॉलबैक फ़ंक्शन के बारे में जो बताया गया है वह बहुत अच्छे उत्तर हैं, लेकिन शायद कोड का उपयोग साफ रखने और अपुष्ट करने के लिए सुविधा का उपयोग करने का सबसे बड़ा लाभ है।

उदाहरण

निम्नलिखित सी कोड त्वरित छँटाई को लागू करता है। नीचे दिए गए कोड में सबसे दिलचस्प रेखा यह है, जहां हम कॉलबैक फ़ंक्शन को कार्रवाई में देख सकते हैं:

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

तुलना_ 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);
}
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.