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.
„* 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. –
@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
@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. –