ما هو "رد الاتصال" في لغة C وكيف يتم تنفيذه؟

StackOverflow https://stackoverflow.com/questions/142789

  •  02-07-2019
  •  | 
  •  

سؤال

من القراءة التي قمت بها، يعتمد 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;
}

الوظيفة (*numberSource) داخل استدعاء الوظيفة PrintTwoNumbers هي وظيفة "لإعادة الاتصال"/التنفيذ من داخل PrintTwoNumbers كما يمليه الكود أثناء تشغيله.

لذلك، إذا كان لديك شيء مثل وظيفة pthread، فيمكنك تعيين وظيفة أخرى لتشغيلها داخل الحلقة من إنشاء مثيل لها.

عادةً ما يتم تنفيذ عمليات الاسترجاعات في لغة C باستخدام مؤشرات الوظائف ومؤشر البيانات المرتبط بها.قمت بتمرير وظيفتك on_event() ومؤشرات البيانات إلى وظيفة الإطار watch_events() (على سبيل المثال).عند وقوع حدث ما، يتم استدعاء وظيفتك باستخدام بياناتك وبعض البيانات الخاصة بالحدث.

تُستخدم عمليات الاسترجاعات أيضًا في برمجة واجهة المستخدم الرسومية.ال 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);
    }
}

يختلف رد الاتصال غير المتزامن في أنه عندما تعود الوظيفة التي تم استدعاؤها والتي نقدم لها رد اتصال، فقد لا تكتمل المهمة.يُستخدم هذا النوع من رد الاتصال غالبًا مع الإدخال/الإخراج غير المتزامن حيث تبدأ عملية الإدخال/الإخراج ثم عند اكتمالها، يتم استدعاء رد الاتصال.

في البرنامج التالي، نقوم بإنشاء مقبس للاستماع إلى طلبات اتصال TCP وعندما يتم تلقي طلب، تقوم الوظيفة التي تقوم بالاستماع باستدعاء وظيفة رد الاتصال المتوفرة.يمكن ممارسة هذا التطبيق البسيط عن طريق تشغيله في نافذة واحدة أثناء استخدام telnet الأداة المساعدة أو متصفح الويب لمحاولة الاتصال في نافذة أخرى.

لقد قمت برفع معظم كود WinSock من المثال الذي توفره Microsoft مع ملف accept() وظيفة في https://msdn.microsoft.com/en-us/library/windows/desktop/ms737526(v=vs.85).aspx

يبدأ هذا التطبيق أ listen() على المضيف المحلي، 127.0.0.1، باستخدام المنفذ 8282 حتى تتمكن من استخدام أي منهما 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);
}

عادةً ما يمكن القيام بذلك باستخدام مؤشر دالة، وهو متغير خاص يشير إلى موقع الذاكرة الخاص بالدالة.يمكنك بعد ذلك استخدام هذا لاستدعاء الوظيفة باستخدام وسائط محددة.لذلك من المحتمل أن تكون هناك وظيفة تقوم بتعيين وظيفة رد الاتصال.سيقبل هذا مؤشر وظيفة ثم يخزن هذا العنوان في مكان ما حيث يمكن استخدامه.بعد ذلك، عندما يتم تشغيل الحدث المحدد، فإنه سوف يستدعي تلك الوظيفة.

من الأسهل كثيرًا فهم الفكرة من خلال المثال.ما تم إخباره عن وظيفة رد الاتصال في لغة 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);
}
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top