2013-08-11 11 views
11

Powiedzmy, że jest to funkcja C do owinięcia:Jaki jest najlepszy sposób na zawinięcie wywołania zwrotnego C za pomocą interfejsu C++ 11?

void foo(int(__stdcall *callback)()); 

Dwa główne pułapki z callbacków wskaźnik funkcji C są:

  • Nie będąc w stanie przechowywać wyrażeń powiązań
  • Nie mogąc Przechowywanie przechwytywania lambdas

Chciałbym wiedzieć, jak najlepiej owijać takie funkcje, jak to zrobić. Pierwsza jest szczególnie użyteczna dla wywołania funkcji członkowskiej, a druga dla definicji śródliniowej, która wykorzystuje otaczające zmienne, ale nie są to jedyne zastosowania.

Inną właściwością tych konkretnych wskaźników funkcji jest to, że muszą używać konwencji wywoływania __stdcall. To, według mojej wiedzy, całkowicie eliminuje lambdas i jest nieco uciążliwe. Chciałbym również pozwolić na co najmniej __cdecl.

Jest to najlepsze, co mogę wymyślić bez rzeczy, które zaczynają się cofać do polegania na wsparciu wskaźników funkcji, które nie mają. Zwykle znajduje się w nagłówku. Oto następujący przykład na Coliru.

#include <functional> 

//C function in another header I have no control over 
extern "C" void foo(int(__stdcall *callback)()) { 
    callback(); 
} 

namespace detail { 
    std::function<int()> callback; //pretend extern and defined in cpp 

    //compatible with the API, but passes work to above variable 
    extern "C" int __stdcall proxyCallback() { //pretend defined in cpp 
     //possible additional processing 
     return callback(); 
    } 
} 

template<typename F> //takes anything 
void wrappedFoo(F f) { 
    detail::callback = f; 
    foo(detail::proxyCallback); //call C function with proxy 
} 

int main() { 
    wrappedFoo([&]() -> int { 
     return 5; 
    }); 
} 

Istnieje jednak poważna wada. To nie jest ponowne wprowadzenie. Jeśli zmienna zostanie ponownie przypisana przed użyciem, stara funkcja nigdy nie zostanie wywołana (nie uwzględniając problemów z wielowątkowością).

Jedną rzeczą, którą próbowałem, która zakończyła się podwojenie z powrotem na siebie było przechowywanie std::function jako członek danych i przy użyciu obiektów, więc każdy działałby na innej zmiennej, ale nie było sposobu, aby przekazać obiekt do proxy. Podjęcie obiektu jako parametru spowodowałoby niezgodność podpisu i jego powiązanie, uniemożliwiłoby zapisanie wyniku jako wskaźnika funkcji.

Jedną z moich pomysłów, ale nie bawiłem się z nią, jest wektor std::function. Uważam jednak, że jedynym bezpiecznym momentem, który można by z niego usunąć, byłoby oczyszczenie go, gdy nic z niego nie korzysta. Jednak każdy wpis jest najpierw dodawany w wrappedFoo, a następnie używany w proxyCallback. Zastanawiam się, czy licznik, który jest inkrementowany w poprzednim i zmniejszony w tym ostatnim, następnie sprawdził na zero, zanim oczyszczanie wektora będzie działało, ale to brzmi jak bardziej skomplikowane rozwiązanie, niż to konieczne.

Czy istnieje jakiś sposób, aby otoczyć funkcji C z zwrotnego wskaźnik funkcji taką wersję, że C++ owiniętą:

  • Umożliwia dowolny obiekt funkcyjny
  • pozwala na bardziej niż po prostu wywołanie Konwencja C oddzwonienia (jeśli to krytyczny, że to ten sam użytkownik może przechodzić w coś z prawej wywołującego konwencji)
  • Is wątku bezpieczny/wielowejściowy

Uwaga: oczywiste rozwiązanie, określone jako część odpowiedzi Mikaela Perssona, polega na wykorzystaniu parametru, który powinien istnieć. Jednak niestety nie jest to opcja ostateczna, głównie z powodu niekompetencji.Jakie możliwości istnieją dla tych funkcji, które nie mają tej opcji, są interesujące i stanowią główną drogę do bardzo przydatnej odpowiedzi.

+0

„* To nie jest re-uczestnik Jeśli zmienna jest przydzielona zanim zostanie użyty, stara funkcja nigdy nie będzie wywoływany (nie biorąc pod zagadnień konto wielowątkowości) . * "Jeśli tak się dzieje, to czy nie jesteś w trakcie ustawiania tego samego wywołania zwrotnego z * dwóch różnych wątków *? O ile system wywołujący, z którym się rejestrujesz, nie poradzi sobie z tym, nie ma znaczenia, czy twój system otoki może. Złamane jest zepsute, niezależnie od tego, czy jest w kodzie, czy w jego kodzie. –

+0

@NicolBolas, Cóż, zdecydowanie można. Wahałem się, aby wspomnieć, że to pytanie powstało z interfejsu Windows API 'CreateWindow', który powinien być dobrze wywoływany z dwóch wątków jednocześnie AFAIK, a także ma (bardziej ukrytą) opcję' void * '. Powodem wahania było to, że chciałem, aby to pytanie było nieco bardziej abstrakcyjne. Jednak warto o tym pomyśleć. – chris

+2

@NicolBolas Opisany przypadek występuje wtedy, gdy ta sama funkcja opakowania (do której wskazuje wskaźnik wywołania zwrotnego) jest używana do rejestrowania wielu wywołań zwrotnych (po wielu wywołaniach funkcji C API), które nie mogą działać poprawnie, jeśli masz tylko jeden obiekt funkcji globalnej (wskazując na aktualny funktor wywołania zwrotnego/lambda/cokolwiek). Nie jest to związane z wielowątkowością jako taką, chociaż wielowątkowość wprowadza jeszcze więcej problemów. –

Odpowiedz

4

Niestety, masz szczęście.

Istnieją sposoby generowania kodu w czasie wykonywania, na przykład można przeczytać na LLVM trampoline intrinsics, gdzie można wygenerować funkcję przekierowania, która przechowuje dodatkowy stan, bardzo podobny do lambd, ale zdefiniowane środowisko uruchomieniowe.

Niestety, żaden z nich nie jest standardem, a tym samym jesteś osierocony.


Najprostszym sposobem przekazania stanu jest ... rzeczywiste przekazanie stanu. Ah!

Dobrze zdefiniowane callbacki C zajmie dwa parametry:

  • Wskaźnik do funkcji połączenia zwrotnego sam
  • void*

Ten ostatni nie jest używany przez samego kodu i po prostu przekazywane do wywołania zwrotnego, gdy zostanie wywołane. W zależności od interfejsu wywołanie zwrotne jest odpowiedzialne za jego zniszczenie lub dostawca, a nawet trzecia funkcja "niszczenia" może zostać przekazana.

Dzięki takiemu interfejsowi można skutecznie przekazać stan w bezpiecznym dla wątków stylu & na poziomie C, a więc naturalnie zawrzeć to w C++ z tymi samymi właściwościami.

template <typename Result, typename... Args) 
Result wrapper(void* state, Args... args) { 
    using FuncWrapper = std::function<Result(Args...)>; 
    FuncWrapper& w = *reinterpret_cast<FuncWrapper*>(state); 
    return w(args...); 
} 

template <typename Result, typename... Args) 
auto make_wrapper(std::function<Result(Args...)>& func) 
    -> std::pair<Result (*)(Args...), void*> 
{ 
    void* state = reinterpret_cast<void*>(&func); 
    return std::make_pair(&wrapper<Result, Args...>, state); 
} 

Jeśli interfejs C nie przewiduje takich obiektów, można siekać się trochę, ale ostatecznie jesteś bardzo ograniczona. Jak już powiedziano, możliwe rozwiązanie polega na utrzymaniu państwa na zewnątrz, przy użyciu globali i starań, aby uniknąć rywalizacji.

wstępny szkic jest tutaj:.

// The FreeList, Store and Release functions are up to you, 
// you can use locks, atomics, whatever... 
template <size_t N, typename Result, typename... Args> 
class Callbacks { 
public: 
    using FunctionType = Result (*)(Args...); 
    using FuncWrapper = std::function<Result(Args...)>; 

    static std::pair<FunctionType, size_t> Generate(FuncWrapper&& func) { 
     // 1. Using the free-list, find the index in which to store "func" 
     size_t const index = Store(std::move(state)); 

     // 2. Select the appropriate "Call" function and return it 
     assert(index < N); 
     return std::make_pair(Select<0, N-1>(index), index); 
    } // Generate 

    static void Release(size_t); 

private: 
    static size_t FreeList[N]; 
    static FuncWrapper State[N]; 

    static size_t Store(FuncWrapper&& func); 

    template <size_t I, typename = typename std::enable_if<(I < N)>::type> 
    static Result Call(Args...&& args) { 
     return State[I](std::forward<Args>(args)...); 
    } // Call 

    template <size_t L, size_t H> 
    static FunctionType Select(size_t const index) { 
     static size_t const Middle = (L+H)/2; 

     if (L == H) { return Call<L>; } 

     return index <= Middle ? Select<L, Middle>(index) 
           : Select<Middle + 1, H>(index); 
    } 

}; // class Callbacks 

// Static initialization 
template <size_t N, typename Result, typename... Args> 
static size_t Callbacks<N, Result, Args...>::FreeList[N] = {}; 

template <size_t N, typename Result, typename... Args> 
static Callbacks<N, Result, Args...>::FuncWrapper Callbacks<N, Result, Args...>::State[N] = {}; 
+0

Uważaj na trampoliny, ponieważ czasami wymagają one stosów NX (i ich innych nazw handlowych, takich jak funkcja zagnieżdżona w GCC). NX-Stacks są czasem naruszeniem bezpiecznego kodowania/najlepszych praktyk, a kod, który ich używa, nie może przejść przez bramę bezpieczeństwa. W systemach Windows stanowią naruszenie zarówno bezpiecznego kodowania, jak i najlepszych praktyk. – jww

+0

@jww: To rzeczywiście byłby problem bezpieczeństwa, dzięki za wskazanie go. –

6

Ten problem ma dwa wyzwania: jeden łatwy i jeden prawie niemożliwy.

Pierwszym wyzwaniem jest transformacja typu statycznego (mapowanie) z dowolnej "rzeczy" wywoływalnej na prosty wskaźnik funkcji. Ten problem rozwiązano za pomocą prostego szablonu, nic wielkiego. Rozwiązuje to problem związany z konwencją wywoływania (po prostu zawijanie jednego rodzaju funkcji na inny). Jest to już rozwiązane przez szablon std::function (dlatego istnieje).

Głównym wyzwaniem jest enkapsulacja stanu wykonawczego do prostego wskaźnika funkcji, którego sygnatura nie pozwala na podanie wskaźnika "danych użytkownika" void* (tak jak normalnie miałby przyzwoity API C). Ten problem jest niezależny od języka (C, C++ 03, C++ 11) i jest prawie niemożliwy do rozwiązania.

Musisz zrozumieć zasadniczy fakt o każdym języku "ojczystym" (i większości innych). Kod jest naprawiany po kompilacji, a tylko dane zmieniają się w czasie wykonywania. Tak więc, nawet funkcja członkowska klasy, która wygląda tak, jakby była to jedna funkcja należąca do obiektu (stan wykonania), tak nie jest, kod jest naprawiony, zmienia się tylko tożsamość obiektu (wskaźnik this).

Innym podstawowym faktem jest to, że wszystkie stany zewnętrzne, których funkcja może używać, muszą być globalne lub przekazywane jako parametry. Jeśli wyeliminujesz to drugie, będziesz miał tylko stan globalny. Z definicji, jeśli działanie tej funkcji zależy od stanu globalnego, nie można jej ponownie wprowadzić.

Tak więc, aby móc utworzyć (sort-of-) ponownie wejściową funkcję *, która jest wywoływana za pomocą zwykłego wskaźnika funkcji i która zawiera dowolny ogólny obiekt (state-ful) funkcji (bind'ed calls, lambdas, czy cokolwiek innego), będziesz potrzebował unikalnego fragmentu kodu (nie danych) dla każdego połączenia. Innymi słowy, musisz wygenerować kod w czasie wykonywania i dostarczyć wskaźnik do tego kodu (wskaźnik funkcji wywołania zwrotnego) do funkcji C. Stąd pochodzi "prawie niemożliwe". Nie jest to możliwe przez żadne standardowe mechanizmy C++, jestem tego w 100% pewien, ponieważ gdyby było to możliwe w C++, możliwe byłoby również odbijanie w czasie wykonywania (i nie jest to możliwe).

Teoretycznie może to być łatwe. Wszystko czego potrzebujesz to kawałek skompilowanego "szablonu" kodu (nie szablonu w sensie C++), który możesz skopiować, wstawić wskaźnik do swojego stanu (lub obiektu funkcji) jako rodzaj zakodowanej zmiennej lokalnej, a następnie umieść to kodu do pewnej dynamicznie przydzielonej pamięci (z pewnym zliczeniem referencyjnym lub czymkolwiek, aby zapewnić, że istnieje tak długo, jak jest potrzebna). Ale zrobienie tego jest wyraźnie bardzo trudne i bardzo "hackowe". I szczerze mówiąc, jest to znacznie wyższe niż mój poziom umiejętności, więc nie byłbym w stanie poinstruować cię, jak dokładnie możesz to zrobić.

W praktyce realistyczną opcją jest nawet nie próbować tego zrobić. Twoje rozwiązanie z globalną (zewnętrzną) zmienną, której używasz do przekazania stanu (obiektu funkcji) idzie w dobrym kierunku, jeśli chodzi o kompromis. Możesz mieć coś w rodzaju puli funkcji, z których każda ma własny globalny obiekt funkcji do wywoływania, i śledzić, która funkcja jest obecnie używana jako wywołanie zwrotne, i przydzielać nieużywane, gdy jest to potrzebne. Jeśli zabraknie ci ograniczonej liczby funkcji, będziesz musiał rzucić wyjątek (lub dowolne zgłaszanie błędów, które wolisz). Schemat ten byłby zasadniczo równoważny powyższemu rozwiązaniu "w teorii", ale z ograniczoną liczbą współbieżnych wywołań zwrotnych. Istnieją inne rozwiązania w podobnym duchu, ale zależy to od charakteru konkretnej aplikacji.

Przykro mi, że ta odpowiedź nie daje dobrego rozwiązania, ale czasami po prostu nie ma żadnych srebrnych kul.

Inną opcją jest uniknięcie używania interfejsu API C zaprojektowanego przez buffoons, którzy nigdy nie słyszeli o nieuniknionym i niezwykle przydatnym parametrze void* user_data.

* "sortowane" ponownie, ponieważ wciąż odnosi się do stanu "globalnego", ale jest ponownie wprowadzone w tym sensie, że różne wywołania zwrotne (które wymagają innego stanu) nie zakłócają się nawzajem, jest twoim pierwotnym problemem.

+0

Muszę powiedzieć, że pomysł na basen jest interesujący. Mam kilka decyzji, które należy podjąć, aby się poddać, ale przynajmniej najrozsądniejsze przykłady mają parametry "void *", co stanowi dość rozsądną odpowiedź.Dla tych, którzy tego nie robią, nie wszystkie z nich będą obsługiwać wywoływanie z dwóch wątków naraz, więc zawęzimy listę jeszcze raz. Od tego momentu nadszedł czas na decyzje. – chris

+1

Mam dwa problemy z tą odpowiedzią. Po pierwsze, może to po prostu ja, ale nie rozumiem, dlaczego generowanie kodu runtime będzie konieczne. Jeśli hakujemy, dlaczego nie włamujemy się tak, że wprowadzamy wskaźnik do samego stanu (który jest tylko danymi)? Inną sprawą jest to, że samo generowanie kodu runtime nie jest "prawie niemożliwe". Gdyby tak było, stworzenie kompilatora JIT dla interpretera języka programowania również byłoby prawie niemożliwe; jednak mamy takie rzeczy jak LLVM (!), Nitro, JVM itp. –

+0

+1 tylko dla 'Inną opcją jest unikanie używania API C zaprojektowanego przez buffoons, którzy nigdy nie słyszeli o nieuniknionym i niezwykle przydatnym parametrze void * user_data . " Poszedłbym z "kretynami". Dowolny programista lib, który projektuje taki kod wywołania zwrotnego bez takiego parametru dostępnego w oddzwanianiu, powinien zostać zwolniony natychmiast (lub wystrzelony, jeśli dostępna jest drużyna). –

1

Jak wspomniano wcześniej, wskaźnik funkcji C nie zawiera żadnego stanu, więc funkcja wywołania zwrotnego wywoływana bez argumentów może uzyskać dostęp tylko do stanu globalnego. Dlatego taka "bezstanowa" funkcja zwrotna może być używana tylko w jednym kontekście, w którym kontekst jest przechowywany w zmiennej globalnej. Następnie zadeklaruj różne wywołania zwrotne dla różnych kontekstów.

Jeśli liczba żądanych wywołań zwrotnych zmienia się dynamicznie (na przykład w GUI, gdzie każde otwarte okno przez użytkownika wymaga nowego wywołania zwrotnego, aby obsłużyć dane wejściowe do tego okna), należy wstępnie zdefiniować dużą pulę prostego stanu mniej wywołań zwrotnych, które odwzorowują stanowe wywołanie zwrotne. W języku C można to zrobić w następujący sposób:

struct cbdata { void (*f)(void *); void *arg; } cb[10000]; 
void cb0000(void) { (*cb[0].f)(cb[0].arg); } 
void cb0001(void) { (*cb[1].f)(cb[1].arg); } 
... 
void cb9999(void) { (*cb[9999].f)(cb[99999].arg); } 
void (*cbfs[10000])(void) = 
    { cb0000, cb0001, ... cb9999 }; 

Następnie użyj jakiegoś modułu wyższego poziomu, aby zachować listę dostępnych połączeń zwrotnych.

Z GCC (ale nie z G ++, więc poniższe musi być w pliku C, a nie C++), możesz tworzyć nowe funkcje oddzwaniania nawet w locie, używając niezbyt dobrze znanego GCC funkcja, funkcje zagnieżdżone:

void makecallback(void *state, void (*cb)(void *), void (*cont)(void *, void (*)())) 
{ 
    void mycallback() { cb(state); } 
    cont(state, mycallback); 
} 

W tym przypadku GCC tworzy dla Ciebie kod niezbędnego generowania kodu. Minusem jest to, że ogranicza cię ona do kolekcji kompilatora GNU i że bit NX nie może być już użyty na stosie, ponieważ nawet twój kod będzie wymagał nowego kodu na stosie.

makecallback() jest wywoływana z kodu wysokiego poziomu w celu utworzenia nowej anonimowej funkcji zwrotnej z enkapsulowanym stanem. Jeśli ta nowa funkcja zostanie wywołana, wywoła ona stanową funkcję zwrotną cb ze stanem arg. Nowa funkcja anonimowego wywołania zwrotnego jest użyteczna, tak długo, jak makecallback() nie zwraca. Dlatego makecallback() zwraca kontrolę do kodu wywołującego, wywołując przekazaną funkcję "cont". W tym przykładzie założono, że rzeczywista funkcja wywołania zwrotnego cb() i normalna funkcja kontynuacji cont() używają tego samego stanu, "stanu". Możliwe jest również użycie dwóch różnych wskaźników pustych, aby przejść do obu stanów.

Funkcja "cont" może powrócić tylko (i POWINIEN POWRÓCIĆ również w celu uniknięcia wycieków pamięci), gdy wywołanie zwrotne nie jest już wymagane. Jeśli twoja aplikacja jest wielowątkowa i wymaga różnych wywołań zwrotnych, głównie dla różnych wątków, powinieneś mieć możliwość, aby każdy wątek podczas uruchamiania przydzielał wymagane wywołania zwrotne za pomocą makecallback().

Jeśli jednak twoja aplikacja jest wielowątkowa, i jeśli masz (lub możesz ustanowić) ścisłą relację oddzwaniania do wątku, możesz użyć zmiennych lokalnych z wątku, aby przejść wymagany stan. Oczywiście to zadziała, jeśli twoja biblioteka wywoła wywołanie zwrotne w odpowiednim wątku.

Powiązane problemy