回答:
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回呼び出し、返された値を指定された配列の要素に割り当てます。
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);
libsrtp
は警告を出しません。そのような型が関数の引数として現れる場合、配列が最初の要素へのポインターに減衰するのと同じように、関数へのポインターに「減衰」する必要があると思います。したがって、最後に同じことが起こります。どちらにしても。それはある私が見つけた、このような型定義の議論ではなく、この点ではないとしても一目を行うことで、プロトタイプやポインタ宣言に焦点を当てていること、しかし、面白い
単純なコールバックプログラム。それがあなたの質問に答えることを願っています。
#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のコールバック関数は、別の関数内で使用するために割り当てられた関数パラメーター/変数に相当します。Wikiの例
以下のコードでは、
#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のコールバックは、他の関数がそのタスクを実行しているときに、ある時点で「コールバック」するために別の関数に提供される関数です。
コールバックの使用方法には、同期コールバックと非同期コールバックの2つがあります。同期コールバックは、いくつかのタスクを実行し、タスクが完了した状態で呼び出し元に戻る別の関数に提供されます。非同期コールバックは、タスクを開始し、タスクが完了していない可能性がある呼び出し元に戻る別の関数に提供されます。
同期コールバックは通常、他の関数がタスクのいくつかのステップを委任する別の関数にデリゲートを提供するために使用されます。この委譲の典型的な例は、関数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
ユーティリティまたはWebブラウザを使用して別のウィンドウで接続を試行しながら、1つのウィンドウで実行することで実行できます。
ほとんどのWinSockコードを、Microsoftが提供するhttps://msdn.microsoft.com/en-us/library/windows/desktop/ms737526(v=vs.85).aspxのaccept()
機能の例から削除しました
このアプリケーションは、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の代替機能で置き換える必要があり、pthreads
代わりにWindowsスレッドライブラリを使用します。
#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のコールバックは通常、関数ポインターと関連するデータポインターを使用して実装されます。たとえば、関数on_event()
とデータポインターをフレームワーク関数に渡しますwatch_events()
。イベントが発生すると、データといくつかのイベント固有のデータを使用して関数が呼び出されます。
コールバックはGUIプログラミングでも使用されます。GTK +チュートリアルでは、上の素敵な部分がある信号とコールバックの理論を。
このウィキペディアの記事には、Cの例があります。
良い例は、Apache Webサーバーを強化するために作成された新しいモジュールが、関数ポインターを渡してメインのApacheプロセスに登録し、それらの関数がWebページ要求を処理するためにコールバックされることです。
例を通してアイデアを理解する方がはるかに簡単です。これまで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);
}