2016-07-12 3 views
2

C에서 C++ 라이브러리를 래핑하려고합니다. C++ 라이브러리는 데이터베이스 서버용 라이브러리입니다. 직렬화 된 데이터를 전달하기 위해 래퍼 클래스를 사용합니다. 나는 C에서 직접 클래스를 사용할 수 없습니다, 그래서 나는 다음과 같은 C 코드에서 사용할 수있는 구조체 정의 :스택에 불완전한 유형 할당

extern "C" { 
    typedef struct Hazelcast_Data_t Hazelcast_Data_t; 

    Hazelcast_Data_t *stringToData(char *str); 
    void freeData(Hazelcast_Data_t *d); 
} 
(이 내 C 래퍼의 클라이언트가 포함되어있는 래퍼) include/c-wrapper/c-wrapper.h에서

impl.pp

extern "C" struct Hazelcast_Data_t { 
    hazelcast::client::serialization::pimpl::Data data; // this is the C++ class 
}; 

Hazelcast_Data_t *stringToData(char *str) { 
    Data d = serializer.serialize(str); 

    Hazelcast_Data_t *dataStruct = new Hazelcast_Data_t(); 
    dataStruct->data = d; 

    return dataStruct; 
} 

... 

에서

지금이 내 C 라이브러리의 클라이언트는 typedef struct Hazelcast_Data_t Hazelcast_Data_t;을보고, 작동합니다. 문제는, 상기 타입은 스택에 할당 할 수없는, 같은 나는이 같은 API를 제공하고자하는 경우 :

// this is what I want to achieve, but Hazelcast_Data_t is an incomplete type 
#include <include/c-wrapper/c-wrapper.h> 

int main() { 
    char *str = "BLA"; 
    Hazelcast_Data_t d; 
    stringToData(str, &d); 
} 

컴파일러는 Hazelcast_Data_t가 불완전한 형태임을 오류가 발생합니다. 나는 여전히 Hazelcast_Data_t의 스택 할당 참조를 직렬화 함수에 전달할 수있는 API를 제공하려고하지만 Hazelcast_Data_t에 C++ 클래스에 대한 포인터가 있기 때문에 이는 거의 불가능한 것처럼 보입니다. 그러나 스택에 할당 된 참조를 전달하는 옵션을 사용하면 내 C 라이브러리의 클라이언트 코드를 크게 단순화 할 수 있습니다. new 구조를 해제 할 필요가 없습니다.

Hazelcast_Data_t 유형을 다시 정의하여 C로 사용할 수 있고 여전히 스택에 할당 할 수 있습니까?

+0

Hazelcast_Data_t에는 실제로 C++ 클래스에 대한 포인터가 포함되어 있습니까? 아니면 실제 C++ 구조체 또는 클래스 개체가 포함되어 있습니까? 실제로 포인터 일 경우 불완전한 유형 (예 : 시스템에 따라 항상 4 또는 8 바이트)에 대해서도 포인터 크기가 잘 알려져 있으므로 많은 문제가 발생하지 않아야합니다. C 컴파일러가 쉽게 이해할 수 있도록 포인터를 (void *)로 저장해야 할 수도 있지만, 그 밖에서는 왜 문제가되는지 알지 못합니다. –

+0

@JeremyFriesner Hazelcast_Data_t에는 실제 데이터가 들어 있습니다. 또한 void 포인터를 사용할 수 있다고 생각했지만'Hazelcast_Data_t data'를'void *'로 캐스팅 할 수 없습니다. 로컬 스택 변수이기 때문에 주소를 가져올 수 없습니다. – Max

+0

폐회 한 사람 : 제 질문에서 무엇이 질문되는지 명확하지 않은 이유를 이해하도록 도와주세요. 나는 여전히 C/C++ 프로그래밍에 익숙하지 않으며 잘못된 용어를 사용하여 내 문제를 해결할 수도 있습니다. "명확하지 않은"이유와 함께 닫는 투표는 전혀 도움이되지 않습니다. – Max

답변

3

구조체가 만들어 질 때 C가 포함 된 개체에 대해 C++ 생성자를 호출하지 않고 구조체가 벗어날 때 C++ 소멸자를 호출하지 않으므로이 작업을 수행하는 대부분의 해킹은 정의되지 않은 동작을 호출합니다. 범위. 제대로 작동하려면 구조체가 올바른 크기의 버퍼를 포함하고 init 함수에서 해당 버퍼에 새로 포함되어야하며 완료되면 해당 버퍼에서 소멸자를 호출해야합니다. 당신이 wrapper_init 모든 정의되지 않은 behvaiour의 땅으로 이동 전화를 분실 한 경우

struct wrapper { 
    char buffer[SIZE_OF_CXX_CLASS]; 
} 

void wrapper_init() { 
    new (buffer) Wrapped(); 
} 

void wrapper_destroy() { 
    ((Wrapper*)buffer)->~Wrapper(); 
} 

{ 
    struct wrapper wrapped; 
    wrapper_init(&wrapped); 
    // ... use it ... 
    wrapper_destroy(&wrapped); 
} 

- (...이 경우는 예외 처리 및 번역을 추가 할 필요가 전혀 없다고 가정하면 발생)이 코드는 다음과 같습니다 의미한다. wrapper_destroy에 전화하는 것을 잊어 버리면 나는 UB도 받게된다고 생각합니다.

그러나 이것은 호출자가 init 함수를 호출하도록 강제하기 때문에 포인터를 사용하는 것보다 거의 이득이 없습니다. 나는 포인터가 아니라 구조체를 사용하는 것이 API 사용자에게 초기화가 사소한 것이어야하며 파괴가 불필요하다고 주장하기까지했다. 나는. API를 사용자로 나는 이것이 내가 관용구를 평소 스틱 힙에 할당 것 (당신처럼) 가능하지 않은 경우에

{ 
    struct wrapper wrapped = WRAPPER_INIT; //Trivial initialisaton macro 
    // .. use it .. 
    // No need to do anything it is a trivial object. 
} 

을 할 수 있기를 기대

{ 
    struct wrapper* wrapped = wrapper_create(); 
    // ... use it ... 
    wrapper_destroy(wrapped); 
} 
+0

결국 당신에게 동의합니다. 나는 많은 힙 할당을 피할 수 있다고 생각했다. 라이브러리가 데이터베이스와 상호 작용하기 위해 사용 되었기 때문에, 많은 메모리를 할당/해제하는 많은 작성/파괴가 있었다고 상상할 수있다. 그러나 결국 API가 안전하고 유용하게 사용되기를 바랍니다. – Max

+1

이러한 오브젝트의 할당이 나중에 병목 현상이되는 것을 발견하면 풀을 사용하는 것과 같은 다른 할당 스키마를 사용하여 대부분의 비용을 'wrapper_create'에서 최적화 할 수 있습니다. –

+0

아주 좋은 통찰력, 나는 동의한다. 지금 간단한 버전을 빌드하고 실제로 문제가 발생하는지 확인합니다. 고마워 마이클, 때로는 프로젝트에서 일하는 것이 약간의 아이디어에 얽매이지 않는 것을 어렵게 만듭니다. – Max

1

클라이언트가 스택에 할당 할 공간의 양을 알 수 있도록 헤더 파일에 구조체의 정의를 제공해야합니다. 그러나 이것은 extern "C"에 의해 노출 될 수없는 C++ 클래스의 기본 표현이 까다로워지면 까다로워집니다.

솔루션은 실제 클래스가 아닌 C++ 클래스에 대한 포인터입니다. 포인터가 같은 크기이기 때문에 C++에 대한 지식이 없어도 C 클라이언트에서 포인터가 작동합니다. 따라서 헤더

typedef struct Hazelcast_Data_t { 
     void *data 
} Hazelcast_Data_t 

에서 그리고 C에서

는이 포인터를 통해 C++ 클래스에 액세스 static_cast를 사용할 수있는 파일 ++.

+0

이것은 원래 불완전한 유형에 대한 포인터를 전달하는 것과 다르지 않습니다. –

0

C++ 유형을 포함 할만큼 큰 배열을 포함하는 래퍼 구조체를 만듭니다. 새로운 C++ 유형을 배치하십시오.

SIZEOF_HAZELCAST_T 및 ALIGNOF_HAZELCAST_T가 적절하게 정의 된 C 헤더 파일을 생성하는 작은 C++ 실행 파일을 빌드해야 할 것입니다.

+0

나는이 방법에 대해 확신하지 못한다. 비록 그것을하고 싶지는 않지만 'SIZEOF_HAZELCAST_T와 ALIGNOF_HAZELCAST_T를 적절히 사용하여 C 헤더 파일을 생성하는 작은 C++ 실행 파일을 빌드해야 할 것이다. 그런 실행 파일은 어떻게 생겼을 까? 보기 당 클래스는 비교적 쉽게 보입니다. 그러나 "크기"를 얻는 방법을 알지 못합니다. https://github.com/hazelcast/hazelcast-cpp-client/blob/master/hazelcast/include/hazelcast/client/ serialization/pimpl/Data.h – Max

+0

그러나 Michael Anderson이 지적했듯이, 나는 destroy 함수가 필요하다. 그렇지 않으면 CPP 클래스의 desructor가 호출되지 않는다. – Max

+1

클래스가 소멸자를 필요로한다면, 사용자는 어떤 방식 으로든 정리 기능을 호출해야하기 때문에이 모든 생각을 잊을 수 있습니다. –

관련 문제