programing

함수 포인터와 컨텍스트를 예상하는 C-콜백에 C++ 람다를 전달하려면 어떻게 해야 합니까?

iphone6s 2023. 6. 21. 22:25
반응형

함수 포인터와 컨텍스트를 예상하는 C-콜백에 C++ 람다를 전달하려면 어떻게 해야 합니까?

표준 함수-포인터+컨텍스트 패러다임을 사용하는 C-API에 콜백을 등록하려고 합니다.api는 다음과 같습니다.

void register_callback(void(*callback)(void *), void * context);

제가 정말 하고 싶은 것은 C++ 람다를 콜백으로 등록하는 것입니다.또한 람다가 변수를 캡처한 것(즉, 직선 상태 비저장으로 변환할 수 없음)을 원합니다.std::function)

람다를 콜백으로 등록하려면 어떤 종류의 어댑터 코드를 작성해야 합니까?

간단한 방법은 람다를 다음과 같은 위치에 고정하는 것입니다.std::function<void()>어딘가에 보관되어 있습니다.잠재적으로 힙에 할당되고 단지 에 의해 참조됩니다.void*콜백을 받는 엔티티에 등록되었습니다.콜백은 단순히 다음과 같은 기능이 됩니다.

extern "C" void invoke_function(void* ptr) {
    (*static_cast<std::function<void()>*>(ptr))();
}

참고:std::function<S>상태가 있는 함수 개체를 유지할 수 있습니다(예: 비어 있지 않은 캡처의 람다 함수).다음과 같은 콜백을 등록할 수 있습니다.

register_callback(&invoke_function,
  new std::function<void()>([=](){ ... }));

가장 효율적인 방법은voidify람다 직접.

#include <iostream>
#include <tuple>
#include <memory>

template<class...Args>
struct callback {
  void(*function)(void*, Args...)=nullptr;
  std::unique_ptr<void, void(*)(void*)> state;
};
template<typename... Args, typename Lambda>
callback<Args...> voidify( Lambda&& l ) {
  using Func = typename std::decay<Lambda>::type;
  std::unique_ptr<void, void(*)(void*)> data(
    new Func(std::forward<Lambda>(l)),
    +[](void* ptr){ delete (Func*)ptr; }
  );
  return {
    +[](void* v, Args... args)->void {
      Func* f = static_cast< Func* >(v);
      (*f)(std::forward<Args>(args)...);
    },
    std::move(data)
  };
}

void register_callback( void(*function)(void*), void * p ) {
  function(p); // to test
}
void test() {
  int x = 0;
  auto closure = [&]()->void { ++x; };
  auto voidified = voidify(closure);
  register_callback( voidified.function, voidified.state.get() );
  register_callback( voidified.function, voidified.state.get() );
  std::cout << x << "\n";
}
int main() {
  test();
}

여기서voidify람다 및 (선택적으로) 인수 목록을 사용하고 전통적인 C 스타일 콜백을 생성합니다.void*짝. 그void*소유자:unique_ptr리소스를 적절하게 정리할 수 있도록 특수 삭제 프로그램을 사용합니다.

이것의 장점은 다음과 같습니다.std::function솔루션은 효율성입니다. 런타임을 한 단계 제거했습니다.콜백이 유효한 수명 또한 명확합니다. 즉, 콜백이 유효한 수명은std::unique_ptr<void, void(*)(void*)>에 의해 반환된.voidify.

unique_ptr<T,D>할 수 있는move에 빠지다.shared_ptr<T>더 복잡한 삶을 원한다면요


위는 수명과 데이터를 혼합하고 소거와 유틸리티를 입력합니다.나눌 수 있습니다.

template<class Lambda, class...Args>
struct callback {
  void(*function)(void*, Args...)=nullptr;
  Lambda state;
};

template<typename... Args, typename Lambda>
callback<typename std::decay<Lambda>::type, Args...> voidify( Lambda&& l ) {
  using Func = typename std::decay<Lambda>::type;
  return {
    +[](void* v, Args... args)->void {
      Func* f = static_cast< Func* >(v);
      (*f)(std::forward<Args>(args)...);
    },
    std::forward<Lambda>(l)
  };
}

지금이다voidify할당하지 않습니다.콜백 기간 동안 포인터를 전달하여 voidify를 저장하기만 하면 됩니다.second당신과 마찬가지로void*의 옆에first함수 포인터.

이 구성을 스택 외부에 저장해야 하는 경우 람다를 다음으로 변환std::function도움이 될 수 있습니다.또는 위의 첫 번째 변형을 사용합니다.

void test() {
  int x = 0;
  auto closure = [&]()->void { ++x; };
  auto voidified = voidify(closure);
  register_callback( voidified.function, &voidified.state );
  register_callback( voidified.function, &voidified.state );
  std::cout << x << "\n";
}

람다 함수는 캡처 변수가 없는 한 C-콜백 함수와 호환됩니다.
새로운 방법으로 기존의 것에 새로운 것을 넣으라고 강요하는 것은 말이 되지 않습니다.
옛날 방식을 따르는 것은 어떻습니까?

typedef struct
{
  int cap_num;
} Context_t;

int cap_num = 7;

Context_t* param = new Context_t;
param->cap_num = cap_num;   // pass capture variable
register_callback([](void* context) -> void {
    Context_t* param = (Context_t*)context;
    std::cout << "cap_num=" << param->cap_num << std::endl;
}, param);

Lamda 함수를 C 함수 포인터로 사용하는 매우 간단한 방법은 다음과 같습니다.

auto fun=+[](){ //add code here }

예를 들어 이 답변을 참조하십시오.https://stackoverflow.com/a/56145419/513481

언급URL : https://stackoverflow.com/questions/20525977/how-can-i-pass-a-c-lambda-to-a-c-callback-that-expects-a-function-pointer-and

반응형