2011-02-03 18 views
6

मेरे पास एक सी लाइब्रेरी फ़ंक्शन है जो कॉलबैक के लिए फ़ंक्शन पॉइंटर की अपेक्षा करता है, और मैं एक C++ सदस्य फ़ंक्शन में पास करना चाहता हूं। सी ++ फ़ंक्शन एक सदस्य चर को संशोधित करता है, इसलिए मैं एक स्थिर मुक्त फ़ंक्शन का उपयोग नहीं कर सकता (जैसा कि कई समान पदों में सुझाया गया है)। मेरा प्रयास (नीचे दिखाया गया) एक कंपाइलर त्रुटि के साथ विफल रहता है।सी ++ वर्ग सदस्य फ़ंक्शन का उपयोग करना (सी स्थिर नहीं हो सकता है) सी कॉलबैक फ़ंक्शन

इस पोस्ट के सबसे करीब आता है कि मैं क्या जरूरत है:

Using a C++ class member function as a C callback function

मैं इस स्थिर कार्यों के बिना कर सकते हैं? धन्यवाद!


test.h

#ifndef TEST_H_ 
#define TEST_H_ 

#ifdef __cplusplus 
extern "C" { 
#endif 

typedef void (*handler_t)(int foo, void *bar); 

void set_handler(handler_t h); 

#ifdef __cplusplus 
} 
#endif 

#endif 

test.c

#include "test.h" 
#include <stdlib.h> 

static handler_t handler_ = NULL; 
void set_handler(handler_t h) { 
     handler_ = h; 
} 

void handle_event(int foo, void *bar) { 
     if (handler_ != NULL) handler_(foo, bar); 
} 

test.cpp

#include "test.h" 
#include <iostream> 
using namespace std; 

class Foo { 
public: 
     Foo() : ctr_(0) {}; 

     // handler needs to access non-static variable, so it can't be static 
     void handler(int foo, void *bar) { ++ctr_; } 

private: 
     int ctr_; 
}; 

int main(int argc, char **argv) { 
     // error: can't convert to "void (*)(int, void*)" 
     set_handler(&Foo::handler); 

     cout << "done" << endl; 
     return 0; 
} 

जीसीसी barf

$ gcc test.cpp test.c 
test.cpp: In function ‘int main(int, char**)’: 
test.cpp:18: error: cannot convert ‘void (Foo::*)(int, void*)’ to ‘void (*)(int, void*)’ for argument ‘1’ to ‘void set_handler(void (*)(int, void*))’ 
+0

क्या आप समझा सकते हैं कि हैंडलर फ़ंक्शन के पैरामीटर क्या हैं? क्या आप यह निर्दिष्ट करते हैं कि इनमें से कोई भी अलग सेटटर या कुछ भी है? –

+0

[यह एक समस्या क्यों है] (http://stackoverflow.com/questions/1707575/c-static-function-wrapper-that-routes-to-member-function/1707678#1707678) –

+0

आपके पास वास्तव में क्या पास हो जाता है 'हैंडलर' समारोह? क्या आप शायद उस कॉलबैक हैंडलर के हस्ताक्षर को बदल सकते हैं या आप उस पुस्तकालय द्वारा प्रदान की जाने वाली चीज़ों का उपयोग करने के लिए बाध्य हैं? – Xeo

उत्तर

8

कम से कम handler_t हस्ताक्षर के साथ यह संभव नहीं है।

आप सदस्य कॉल रैप करने के लिए अपने सीपीपी पर एक नि: शुल्क समारोह बना सकते हैं, आप Foo उदाहरण के लिए सूचक की जरूरत है:

void my_wrap(int foo, void* bar) { 
    Foo* some_foo_instance = ...; 
    some_foo_instance->handler(foo, bar); 
} 

int main(int argc, char **argv) { 
    set_handler(&my_wrap); 
} 

आप कुछ शून्य * जरूरत है एक हैंडलर के रूप में Foo उदाहरण पारित करने के लिए विशेषता:

// Header 
typedef void (*handler_t)(int foo, void *bar, void* arg1); 
void set_handler(handler_t h, void* arg1); 

// Impl. 
void set_handler(handler_t h, void* arg1) { 
     handler_ = h; 
     handler_arg1_ = arg1; 
} 

// cpp 
void my_wrap(int foo, void* bar, void* arg1) { 
    Foo* some_foo_instance = static_cast<Foo*>(arg1); 
    some_foo_instance->handler(foo, bar); 
} 

// main 
int main(int argc, char **argv) { 
    Foo some_concrete_instance; 
    set_handler(&my_wrap, static_cast<void*>(&some_concrete_instance)); 
} 
+0

उत्कृष्ट। धन्यवाद! –

1

आप एक मानचित्रण समारोह बनाने मान लीजिए:

Foo *inst = // some instance of Foo you're keeping around... 

void wrapper(int foo, void *bar){ 
    inst->handler(foo, bar); 
} 

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

+0

रुको, क्या आप यहां बिल्कुल कर रहे हैं? – Xeo

+1

@Xeo: जाहिर तौर पर सोते हुए, अधिक समझदार होने के लिए संपादित किया गया। –

+0

यह इस तरह दिखता है। :) पुराना संस्करण मेरे मानसिक "अजीब नींद-कोडित कोड" में संग्रहित है। – Xeo

1

यह वाक्य:

मैं एक सी librar है y समारोह

यह आप कर सकते हैं नहीं पास यह किसी भी सी ++ वस्तु का मतलब है।
यदि आप जिस लाइब्रेरी का उपयोग कर रहे हैं वह एक सी लाइब्रेरी है, तो उसे सी ++ के बारे में पता नहीं है, इसलिए यह सी ++ की किसी भी चीज का उपयोग नहीं कर सकता है, यह केवल सी सामान का उपयोग कर सकता है।

आप इसे आपके कोड में एक नि: शुल्क फ़ंक्शन कॉल करें।
अब आपका फ्री फ़ंक्शन किसी ऑब्जेक्ट पर एक विधि को कॉल कर सकता है (यही कारण है कि सी कॉलबैक में शून्य * पैरामीटर होता है (ताकि आप कॉलबैक में संदर्भ पास कर सकें))।

+0

आप सही हैं, लेकिन ऐसा लगता है कि 'set_handler' फ़ंक्शन' शून्य * 'पैरामीटर नहीं लेता है, इसलिए मैं सोच रहा हूं कि वह संदर्भ कैसे सेट करेगा। – Xeo

0

यदि आपके पास हैंडलर परिभाषित करने के तरीके पर नियंत्रण है, तो मैं फ़ंक्शन पॉइंटर्स के बजाय Boost function objects का उपयोग करने की सलाह देता हूं।

यदि आपको फ़ंक्शन पॉइंटर्स का उपयोग करना है, तो एक अतिरिक्त शून्य के साथ हैंडलर_टी को परिभाषित करें * जिसका मान हैंडलर के साथ पास किया गया है, the gotchas Martin York linked in a comment के लिए देखें।

typedef void (*handler_t)(int foo, void *bar, void *data); 

static handler_t handler_ = NULL; 
static void* handler_data_ = NULL; 
void set_handler(handler_t h, void *d = NULL) { 
    handler_ = h; 
    handler_data = d; 
} 

void handle_event(int foo, void *bar) { 
    if (handler_ != NULL) handler_(foo, bar, handler_data_); 
} 

void foo_handler(int foo, void *bar, void *data) { 
    Foo *fooObj = static_cast<Foo*>(data); 
    fooObj->handler(foo, bar); 
} 

// in main 
    set_handler(foo_handler, &some_foo_object); 
+0

इस पोस्ट में मार्टिन की टिप्पणी के आधार पर, मैं बूस्ट फ़ंक्शन ऑब्जेक्ट को मेरी सी लाइब्रेरी में पास नहीं कर पाऊंगा (या क्या मुझे कुछ याद आ रहा है?)। –

+0

आपने एक टिप्पणी में कहा, "हाँ, मैं हैंडलर_टी के हस्ताक्षर को बदल सकता हूं"। यदि आप ऐसा कर सकते हैं, जिसमें कोड का उपयोग करने और हैंडलर_टी को कॉल करने में शामिल है, तो आप कॉलबैक तंत्र को कुछ सी ++ पर आधारित क्यों नहीं बदल सकते हैं? –

+0

ऐसा नहीं है कि मैं * नहीं कर सकता; मैं बस समझ नहीं पा रहा हूं कि बूस्ट फ़ंक्शन ऑब्जेक्ट्स और सी लाइब्रेरी के साथ आप जो सुझाव देते हैं उसे कैसे करें। लाइब्रेरी विरासत सी कोड की 500 के + लाइनें है, इसलिए सी ++ में बदलना नॉनट्रिविअल है। –

1

यहाँ इस समस्या को हल करने के लिए एक ugly hack I invented awhile ago है:

#include <boost/function.hpp> 
#include <boost/bind.hpp> 

using ::boost::function; 
using ::boost::bind; 

typedef int (*callback_t)(const char *, int); 

typedef function<int(const char *, int)> MyFTWFunction; 

template <MyFTWFunction *callback> 
class callback_binder { 
public: 
    static int callbackThunk(const char *s, int i) { 
     return (*callback)(s, i); 
    } 
}; 

extern void register_callback(callback_t f); 

int random_func(const char *s, int i) 
{ 
    if (s && *s) { 
     return i; 
    } else { 
     return -1; 
    } 
} 

MyFTWFunction myfunc; 

class FooClass { 
public: 
    virtual int callme(const char *s, int x) { return 0; }; 
}; 

int main(int argc, const char *argv[]) 
{ 
    FooClass foo; 
    myfunc = bind(&FooClass::callme, &foo, _1, _2); 
    register_callback(&callback_binder<&myfunc>::callbackThunk); 
    return 0; 
} 

यह शायद TR1 से सामान का उपयोग करें और बूस्ट पर निर्भरता को निकालना तय किया जा सकता है तो फिर तुम कुछ इस तरह की है।

और, ज़ाहिर है, myfunc एक वैश्विक चर है। यह एक वैश्विक चर होना चाहिए। आपके पास एक अलग वैरिएबल प्रति अलग-अलग संभावित ऑब्जेक्ट होना चाहिए जिसमें आप वापस कॉल करना चाहते हैं। ओटीओएच, आप जितना चाहें उतने ग्लोबल्स प्राप्त कर सकते हैं।

यहां मुख्य मुद्दा यह है कि आप जो बाधाओं के भीतर चाहते हैं वह करना असंभव है। उस ऑब्जेक्ट के पॉइंटर जिसे आप वापस कॉल करना चाहते हैं उसे कहीं से आना है। कुछ भाषाओं में (उदाहरण के लिए पायथन की तरह) आप ऑन-द-फ्लाई पर फ़ंक्शन बना सकते हैं जिसमें ऑब्जेक्ट पॉइंटर की अपनी प्रति है। यह सी ++ में नहीं किया जा सकता है। संकलन समय पर सभी कार्यों को पूरी तरह से मौजूद होना चाहिए। आप रन टाइम पर नए फ़ंक्शन इंस्टेंस नहीं बना सकते हैं।

सी ++ 0x के साथ, आप रनटाइम पर लैम्ब्डा फ़ंक्शंस के साथ फ़ंक्शनटाइम बना सकते हैं। लेकिन इन कार्यों में एक अनिर्दिष्ट प्रकार है और वहां कोई रास्ता नहीं है जिसे आप कभी भी सी समारोह में पास कर सकते हैं और इसे काम कर सकते हैं। लैम्ब्डा अभिव्यक्तियों को टेम्पलेट पैरामीटर के रूप में आपूर्ति करने के लिए उपयोग किया जाता है और उन्हें किसी अन्य चीज़ के लिए उपयोग करना बहुत मुश्किल होता है क्योंकि उनके पते को नहीं लिया जा सकता है, और यहां तक ​​कि अगर आप वास्तव में यह नहीं जानते कि सूचक किस प्रकार इंगित कर रहा है।

मैं अत्यधिक इसका उपयोग नहीं करने की सलाह देता हूं। छोटे void * अधिकतर कॉलबैक इंटरफेस आपको यह निर्दिष्ट करने की अनुमति देते हैं कि डेटा के साथ आपको वापस सौंप दिया जाता है, किसी वस्तु के ऑब्जेक्ट पॉइंटर को पकड़ने के लिए होता है। यदि संभव हो, तो आपको इसके बजाय ऐसा करना चाहिए।

+0

मन एक उदाहरण दे रहा है जहां यह कक्षा सदस्य समारोह को लपेटता है? – Xeo

+0

@Xeo - ओह। * ग्रिन * हो गया। यदि आप परिचित हैं ':: boost :: function' और ':: boost :: bind', तो मुझे यह सुनिश्चित करना चाहिए था कि यह वहां था। – Omnifarious

+0

+1 आपके "बदसूरत हैक" की लंबाई का वर्णन करने के लिए +1 और उसके बाद इसका उपयोग न करने की सिफारिश करें :) –

2

बड़ा सवाल यह है कि विभिन्न ऑब्जेक्ट्स पर कॉल विधियों को कॉल करने के लिए आपको कितनी बार set_handler को कॉल करने की आवश्यकता होती है। यदि यह जवाब है, तो आप कुछ इस तरह कर सकते हैं:

#include <boost/function.hpp> 

class HandlerContext 
{ 
    static boost::function<void (int, void*)> s_func 

    static void forward(int foo, void* bar) 
    { 
     s_func(foo, bar); 
    } 

public: 
    static void set(boost::function<int, void*> const& f) 
    { 
     s_func = f; 
     set_handler(&HandlerContext::forward); 
    } 
}; 

अगर जवाब "एक से अधिक बार", आप कई अग्रेषण कार्यों कि उनके कार्य एक सरणी से बाहर वस्तुओं प्राप्त हो सकता है। आपको इस मामले में स्लॉट्स को पूर्वनिर्धारित करने की आवश्यकता होगी, क्योंकि उपयोग में कार्य से संकेत मिलेगा कि कौन सी कॉलबैक बनाना है।

+0

वीएस 2012 पर std :: फ़ंक्शन का उपयोग करके एक आकर्षण की तरह काम किया –

संबंधित मुद्दे