객체 지향 C ++ 코드 용 C 래퍼 API 개발


81

우리의 핵심 로직 (객체 지향 C ++로 작성)에 액세스하기 위해 기존 C ++ API를 래핑 할 C API 세트를 개발하려고합니다. 이것은 본질적으로 C ++ 로직을 다른 언어에서 사용할 수 있도록하는 글루 API입니다. 객체 지향 C ++ 주위에 C를 래핑하는 것과 관련된 개념을 소개하는 좋은 자습서, 서적 또는 모범 사례는 무엇입니까?


4
영감을 얻으려면 zeromq 소스를 확인하십시오. 라이브러리는 현재 C ++로 작성되었으며 C 바인딩이 있습니다. zeromq.org
Hassan Syed

답변:


70

이것은 손으로하기가 너무 어렵지 않지만 인터페이스의 크기에 따라 달라집니다. 내가 한 경우는 순수한 C 코드 내에서 C ++ 라이브러리를 사용할 수 있도록하는 것이었기 때문에 SWIG는별로 도움이되지 않았습니다. (글쎄, SWIG를 사용하여이 작업을 수행 할 수 있지만, 저는 SWIG 전문가가 아니므로 사소한 것 같지 않습니다.)

결국 우리가 한 것은 다음과 같습니다.

  1. 모든 객체는 C에서 불투명 핸들로 전달됩니다.
  2. 생성자와 소멸자는 순수 함수로 래핑됩니다.
  3. 멤버 함수는 순수 함수입니다.
  4. 다른 내장 기능은 가능한 경우 C에 해당합니다.

따라서 이와 같은 클래스 (C ++ 헤더)

class MyClass
{
  public:
  explicit MyClass( std::string & s );
  ~MyClass();
  int doSomething( int j );
}

다음과 같은 C 인터페이스에 매핑됩니다 (C 헤더).

struct HMyClass; // An opaque type that we'll use as a handle
typedef struct HMyClass HMyClass;
HMyClass * myStruct_create( const char * s );
void myStruct_destroy( HMyClass * v );
int myStruct_doSomething( HMyClass * v, int i );

인터페이스의 구현은 다음과 같습니다 (C ++ 소스).

#include "MyClass.h"

extern "C" 
{
  HMyClass * myStruct_create( const char * s )
  {
    return reinterpret_cast<HMyClass*>( new MyClass( s ) );
  }
  void myStruct_destroy( HMyClass * v )
  {
    delete reinterpret_cast<MyClass*>(v);
  }
  int myStruct_doSomething( HMyClass * v, int i )
  {
    return reinterpret_cast<MyClass*>(v)->doSomething(i);
  }
}

우리는 캐스팅이 필요하지 않도록 원래 클래스에서 불투명 핸들을 파생 시켰습니다 (현재의 컴파일러에서는 작동하지 않는 것 같습니다). C가 클래스를 지원하지 않기 때문에 핸들을 구조체로 만들어야합니다.

이것이 우리에게 기본적인 C 인터페이스를 제공합니다. 예외 처리를 통합 할 수있는 한 가지 방법을 보여주는 더 완전한 예제를 원한다면 github에서 내 코드를 시도해 볼 수 있습니다. https://gist.github.com/mikeando/5394166

이제 재미있는 부분은 필요한 모든 C ++ 라이브러리를 더 큰 라이브러리에 올바르게 연결하는 것입니다. gcc (또는 clang)의 경우 g ++를 사용하여 최종 링크 단계를 수행하는 것을 의미합니다.


11
반환 된 객체에 대해 void * 대신 익명 구조체와 같이 void가 아닌 다른 것을 사용하는 것이 좋습니다. 이것은 반환 된 핸들에 대해 일종의 유형 안전성을 제공 할 수 있습니다. 자세한 내용은 stackoverflow.com/questions/839765/… 를 확인하십시오 .
Laserallan 2010 년

3
나는 Laserallan에 동의하고 그에 따라 코드를 리팩토링했습니다
Michael Anderson

2
@Mike Weller extern "C"블록 내부의 신규 및 삭제는 괜찮습니다. extern "C"는 이름 맹 글링에만 영향을 미칩니다. C 컴파일러는 해당 파일을 보지 않고 헤더 만 볼 수 있습니다.
Michael Anderson

2
나는 또한 모든 것을 C로 컴파일하는 데 필요한 typedef를 놓쳤다. 이상한 typdef struct Foo Foo; "마구 자르기". 코드 업데이트
Michael Anderson

5
@MichaelAnderson, myStruct_destroymyStruct_doSomething함수 에 두 개의 오타가 있습니다 . 이어야 reinterpret_cast<MyClass*>(v)합니다.
firegurafiku

17

나는 Michael Anderson의 대답이 올바른 방향이라고 생각하지만 내 접근 방식은 다를 것입니다. 추가로 예외 사항에 대해 걱정해야합니다. 예외는 C ABI의 일부가 아니므로 C ++ 코드를 지나서 예외가 발생하도록 할 수 없습니다. 따라서 헤더는 다음과 같이 보일 것입니다.

#ifdef __cplusplus
extern "C"
{
#endif
    void * myStruct_create( const char * s );
    void myStruct_destroy( void * v );
    int myStruct_doSomething( void * v, int i );
#ifdef __cplusplus
}
#endif

래퍼의 .cpp 파일은 다음과 같습니다.

void * myStruct_create( const char * s ) {
    MyStruct * ms = NULL;
    try { /* The constructor for std::string may throw */
        ms = new MyStruct(s);
    } catch (...) {}
    return static_cast<void*>( ms );
}

void myStruct_destroy( void * v ) {
    MyStruct * ms = static_cast<MyStruct*>(v);
    delete ms;
}

int myStruct_doSomething( void * v, int i ) {
    MyStruct * ms = static_cast<MyStruct*>(v);
    int ret_value = -1; /* Assuming that a negative value means error */
    try {
        ret_value = ms->doSomething(i);
    } catch (...) {}
    return ret_value;
}

Even better: If you know that all you need as a single instance of MyStruct, don't take the risk of dealing with void pointers being passed to your API. Do something like this instead:

static MyStruct * _ms = NULL;

int myStruct_create( const char * s ) {
    int ret_value = -1; /* error */
    try { /* The constructor for std::string may throw */
        _ms = new MyStruct(s);
        ret_value = 0; /* success */
    } catch (...) {}
    return ret_value;
}

void myStruct_destroy() {
    if (_ms != NULL) {
        delete _ms;
    }
}

int myStruct_doSomething( int i ) {
    int ret_value = -1; /* Assuming that a negative value means error */
    if (_ms != NULL) {
        try {
            ret_value = _ms->doSomething(i);
        } catch (...) {}
    }
    return ret_value;
}

This API is a lot safer.

But, as Michael mentioned, linking may get pretty tricky.

Hope this helps


2
For more about exception handling for this case take a look at the following thread: stackoverflow.com/questions/847279/…
Laserallan

2
When I know my C++ library will have also have a C API, I encapsulate an API error code int inside my exception base class. It is easier to know at the throwing site what the exact error condition is and provide a very-specific error code. The try-catch "wrappers" in the outer C API functions simply need to retrieve the error code and return it to the caller. For other standard library exceptions, refer to Laserallan's link.
Emile Cormier

2
catch(...){ } is pure unadulterated evil. My only regret is that I can only down vote once.
Terry Mahaffey

2
@Terry Mahaffey I absolutely agree with you that it is evil. The best is to do what Emile has suggested. But if you must guarantee that the wrapped code will never throw, you have no other choice but to put a catch (...) at the bottom of all the other catches identified. This is the case because the library you are wrapping may be poorly documented. There are no C++ constructs that you can use in order to enforce that only a set of exceptions may be thrown. What is the lesser of two evils? catch (...) or risking a run-time crash when the wrapped code attempts to throw to the C caller?
figurassa

1
catch(...) { std::terminate(); } is acceptable. catch(...){ } is a potential security hole
Terry Mahaffey

10

It is not hard to expose C++ code to C, just use the Facade design pattern

I am assuming your C++ code is built into a library, all you need to do is make one C module in your C++ library as a Facade to your library along with a pure C header file. The C module will call the relevant C++ functions

Once you do that your C applications and library will have full access to the C api you exposed.

for example, here is a sample Facade module

#include <libInterface.h>
#include <objectedOrientedCppStuff.h>

int doObjectOrientedStuff(int *arg1, int arg2, char *arg3) {
      Object obj = ObjectFactory->makeCppObj(arg3); // doing object oriented stuff here
      obj->doStuff(arg2);
      return obj->doMoreStuff(arg1);
   }

you then expose this C function as your API and you can use it freely as a C lib with out worrying about

// file name "libIntrface.h"
extern int doObjectOrientedStuff(int *, int, char*);

Obviously this is a contrived example but this is the easiest way to expos a C++ library to C


Hi @hhafez do you have a simple hello world example? One with strings?
Jason Foglia

for a non cpp guy this is lovely
Nicklas Avén

6

I would think you may be able to get some ideas on direction and/or possibly utilize directly SWIG. I would think that going over a few of the examples would at least give you an idea of what kinds of things to consider when wrapping one API into another. The exercise could be beneficial.

SWIG is a software development tool that connects programs written in C and C++ with a variety of high-level programming languages. SWIG is used with different types of languages including common scripting languages such as Perl, PHP, Python, Tcl and Ruby. The list of supported languages also includes non-scripting languages such as C#, Common Lisp (CLISP, Allegro CL, CFFI, UFFI), Java, Lua, Modula-3, OCAML, Octave and R. Also several interpreted and compiled Scheme implementations (Guile, MzScheme, Chicken) are supported. SWIG is most commonly used to create high-level interpreted or compiled programming environments, user interfaces, and as a tool for testing and prototyping C/C++ software. SWIG can also export its parse tree in the form of XML and Lisp s-expressions. SWIG may be freely used, distributed, and modified for commercial and non-commercial use.


2
SWIG is just over kill, if all he wants to do is to make a C++ library be available from C.
hhafez

1
Thats an opinion and contains no real useful feedback. SWIG would help if the original code is: Changing rapidly, There are no C++ resources to maintain it and only C resources available and if the developer wants to automate the C API generation. These are common and certainly valid reasons to use SWIG.
user1363990

5

Just replace the concept of an object with a void * (often referred to as an opaque type in C oriented libraries) and reuse everything you know from C++.


2

I think using SWIG is the best answer... not only it avoid reinventing wheel but it is reliable and also promote a continuity in development rather than one shooting the problem.

High frequency problems need to be addressed by a long term solution.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.