web-dev-qa-db-ja.com

Cの「コールバック」とは何ですか、またどのように実装されていますか?

私が行った読書から、Core Audioはコールバックに大きく依存しています(そしてC++ですが、それは別の話です)。

私は、タスクを達成するために別の関数によって繰り返し呼び出される関数をセットアップするという概念を理解しています。それらがどのようにセットアップされ、実際にどのように機能するのかがわかりません。どんな例でも歓迎されます。

141
noizetoys

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関数は3番目のパラメーターとして関数ポインターを受け取り、それを呼び出して値を取得して配列に入力します。ランダムな値を返すコールバックgetNextRandomValueを作成し、populate_arrayにポインターを渡しました。 populate_arrayはコールバック関数を10回呼び出し、返された値を指定された配列の要素に割り当てます。

187
aib

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);
118
Russell Bryant

単純なコールバックプログラム。それがあなたの質問に答えることを願っています。

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

Cのコールバック関数は、別の関数内で使用するために割り当てられた関数パラメーター/変数と同等です。 Wiki Example

以下のコードでは、

#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関数のようなものがある場合、別の関数を割り当てて、そのインスタンス化からループ内で実行できます。

9
daemondave

Cのコールバックは通常、関数ポインターと関連データポインターを使用して実装されます。関数on_event()とデータポインターをフレームワーク関数watch_events()に渡します(たとえば)。イベントが発生すると、データとイベント固有のデータを使用して関数が呼び出されます。

コールバックは、GUIプログラミングでも使用されます。 GTK +チュートリアル には 信号とコールバックの理論 に関する素敵なセクションがあります。

4
John Millikin

この wikipediaの記事 にはCの例があります。

良い例は、Apache Webサーバーを強化するために書かれた新しいモジュールが関数ポインターを渡すことによってメインのApacheプロセスに登録し、それらの関数がWebページ要求を処理するために呼び出されることです。

3
Leonard

Cのコールバックは、他の関数がタスクを実行しているときに、ある時点で「コールバック」するために別の関数に提供される関数です。

コールバックが使用される2つの方法 :同期コールバックと非同期コールバックがあります。同期コールバックは、何らかのタスクを実行し、タスクが完了した状態で呼び出し元に戻る別の関数に提供されます。非同期コールバックは、タスクを開始し、タスクが完了していない可能性がある呼び出し元に戻る別の関数に提供されます。

通常、同期コールバックは、他の関数がタスクの一部のステップを委任する別の関数にデリゲートを提供するために使用されます。この委任の古典的な例は、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ブラウザーを使用して別のウィンドウで接続を試みながら、1つのウィンドウで実行することで実行できます。

https://msdn.Microsoft.com/en-us/library/windows/desktop/ms737526(v = vs.85)でMicrosoftがaccept()関数を使用して提供する例から、WinSockコードのほとんどを取り上げました。 aspx

このアプリケーションは、ポート8282を使用してローカルホスト127.0.0.1でlisten()を開始するため、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);
}
3

通常、これは関数ポインターを使用して行うことができます。関数ポインターは、関数のメモリ位置を指す特別な変数です。その後、これを使用して、特定の引数で関数を呼び出すことができます。そのため、おそらくコールバック関数を設定する関数があるでしょう。これは関数ポインターを受け入れ、そのアドレスを使用可能な場所に保存します。その後、指定されたイベントがトリガーされると、その関数が呼び出されます。

0
mdec

例を通してアイデアを理解する方がずっと簡単です。これまでCのコールバック関数について語られてきたことはすばらしい答えですが、おそらくこの機能を使用する最大の利点は、コードをすっきりさせて整理することです。

次のCコードはクイックソートを実装しています。以下のコードで最も興味深い行はこの1行で、コールバック関数の動作を確認できます。

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);
}
0
Asif