Pav*_*hov 26 c c++ api shared-libraries
假设我们有一个像这样的类的C++库:
class TheClass {
public:
TheClass() { ... }
void magic() { ... }
private:
int x;
}
Run Code Online (Sandbox Code Playgroud)
此类的典型用法包括堆栈分配:
TheClass object;
object.magic();
Run Code Online (Sandbox Code Playgroud)
我们需要为这个类创建一个C包装器.最常见的方法如下:
struct TheClassH;
extern "C" struct TheClassH* create_the_class() {
return reinterpret_cast<struct TheClassH*>(new TheClass());
}
extern "C" void the_class_magic(struct TheClassH* self) {
reinterpret_cast<TheClass*>(self)->magic();
}
Run Code Online (Sandbox Code Playgroud)
但是,它需要堆分配,这对于这么小的类显然是不希望的.
我正在寻找一种允许从C代码中分配此类的堆栈的方法.这是我能想到的:
struct TheClassW {
char space[SIZEOF_THECLASS];
}
void create_the_class(struct TheClassW* self) {
TheClass* cpp_self = reinterpret_cast<TheClass*>(self);
new(cpp_self) TheClass();
}
void the_class_magic(struct TheClassW* self) {
TheClass* cpp_self = reinterpret_cast<TheClass*>(self);
cpp_self->magic();
}
Run Code Online (Sandbox Code Playgroud)
很难将类的实际内容放在struct的字段中.我们不能只包含C++标头,因为C不理解它,所以它需要我们编写兼容的C标头.这并不总是可行的.我认为C库并不需要关心结构的内容.
这个包装器的用法如下所示:
TheClassW object;
create_the_class(&object);
the_class_magic(&object);
Run Code Online (Sandbox Code Playgroud)
问题:
Ale*_*lex 12
您可以在alloca的组合中使用placement new来在堆栈上创建对象.对于Windows,有_malloca.这里的重要性是alloca和malloca相应地对齐内存,并且包装操作符可以轻松地暴露类的大小.请注意,在C代码中,当变量超出范围时,没有任何反应.特别是不要毁坏你的物体.sizeof
#include "the_class.h"
#include <alloca.h>
int main() {
void *me = alloca(sizeof_the_class());
create_the_class(me, 20);
if (me == NULL) {
return -1;
}
// be aware return early is dangerous do
the_class_magic(me);
int error = 0;
if (error) {
goto fail;
}
fail:
destroy_the_class(me);
}
Run Code Online (Sandbox Code Playgroud)
#ifndef THE_CLASS_H
#define THE_CLASS_H
#include <stddef.h>
#include <stdint.h>
#ifdef __cplusplus
class TheClass {
public:
TheClass(int me) : me_(me) {}
void magic();
int me_;
};
extern "C" {
#endif
size_t sizeof_the_class();
void *create_the_class(void* self, int arg);
void the_class_magic(void* self);
void destroy_the_class(void* self);
#ifdef __cplusplus
}
#endif //__cplusplus
#endif // THE_CLASS_H
Run Code Online (Sandbox Code Playgroud)
#include "the_class.h"
#include <iostream>
#include <new>
void TheClass::magic() {
std::cout << me_ << std::endl;
}
extern "C" {
size_t sizeof_the_class() {
return sizeof(TheClass);
}
void* create_the_class(void* self, int arg) {
TheClass* ptr = new(self) TheClass(arg);
return ptr;
}
void the_class_magic(void* self) {
TheClass *tc = reinterpret_cast<TheClass *>(self);
tc->magic();
}
void destroy_the_class(void* self) {
TheClass *tc = reinterpret_cast<TheClass *>(self);
tc->~TheClass();
}
}
Run Code Online (Sandbox Code Playgroud)
编辑:
您可以创建包装器宏以避免创建和初始化分离.你不能使用do { } while(0)样式宏,因为它会限制变量的范围.还有其他方法可以解决这个问题,但这在很大程度上取决于您如何处理代码库中的错误.概念证明如下:
#define CREATE_THE_CLASS(NAME, VAL, ERR) \
void *NAME = alloca(sizeof_the_class()); \
if (NAME == NULL) goto ERR; \
// example usage:
CREATE_THE_CLASS(me, 20, fail);
Run Code Online (Sandbox Code Playgroud)
这将gcc扩展为:
void *me = __builtin_alloca (sizeof_the_class()); if (me == __null) goto fail; create_the_class(me, (20));;
Run Code Online (Sandbox Code Playgroud)
存在对齐危险.但也许不在您的平台上.修复此问题可能需要特定于平台的代码或未标准化的C/C++互操作.
设计明智,有两种类型.在C中,它是struct TheClass;.在C++中,struct TheClass有一个正文.
做一个 struct TheClassBuff{char buff[SIZEOF_THECLASS];};
TheClass* create_the_class(struct TheClassBuff* self) {
return new(self) TheClass();
}
void the_class_magic(struct TheClass* self) {
self->magic();
}
void the_class_destroy(struct TheClass* self) {
self->~TheClass();
}
Run Code Online (Sandbox Code Playgroud)
C应该生成buff,然后从中创建一个句柄并使用它进行交互.现在通常这不是必需的,因为重新解释指向theclassbuff的指针将起作用,但我认为这是技术上未定义的行为.
这是另一种方法,根据具体应用的具体情况,可能接受也可能不接受.这里我们基本上从C代码中隐藏了TheClass实例的存在,并在包装函数中封装了TheClass的每个使用场景.如果此类方案的数量太大,这将变得难以管理,但否则可能是一种选择.
C包装器:
extern "C" void do_magic()
{
TheClass object;
object.magic();
}
Run Code Online (Sandbox Code Playgroud)
从C中简单地调用包装器.
2016年2月17日更新:
由于您需要具有有状态TheClass对象的解决方案,因此您可以遵循原始方法的基本思想,这在另一个答案中得到了进一步改进.这是该方法的另一个旋转,其中检查由C代码提供的内存占位符的大小以确保它足够大以容纳TheClass的实例.
我想说堆栈分配的TheClass实例的值在这里是有问题的,并且它是一个判断调用,具体取决于应用程序的细节,例如性能.您仍然必须调用解除分配函数,该函数又手动调用析构函数,因为TheClass可能会分配必须释放的资源.
但是,如果有一个堆栈分配的TheClass很重要,这是另一个草图.
要包装的C++代码以及包装器:
#include <new>
#include <cstring>
#include <cstdio>
using namespace std;
class TheClass {
public:
TheClass(int i) : x(i) { }
// cout doesn't work, had to use puts()
~TheClass() { puts("Deleting TheClass!"); }
int magic( const char * s, int i ) { return 123 * x + strlen(s) + i; }
private:
int x;
};
extern "C" TheClass * create_the_class( TheClass * self, size_t len )
{
// Ensure the memory buffer is large enough.
if (len < sizeof(TheClass)) return NULL;
return new(self) TheClass( 3 );
}
extern "C" int do_magic( TheClass * self, int l )
{
return self->magic( "abc", l );
}
extern "C" void delete_the_class( TheClass * self )
{
self->~TheClass(); // 'delete self;' won't work here
}
Run Code Online (Sandbox Code Playgroud)
C代码:
#include <stdio.h>
#define THE_CLASS_SIZE 10
/*
TheClass here is a different type than TheClass in the C++ code,
so it can be called anything else.
*/
typedef struct TheClass { char buf[THE_CLASS_SIZE]; } TheClass;
int do_magic(TheClass *, int);
TheClass * create_the_class(TheClass *, size_t);
void delete_the_class(TheClass * );
int main()
{
TheClass mem; /* Just a placeholder in memory for the C++ TheClass. */
TheClass * c = create_the_class( &mem, sizeof(TheClass) );
if (!c) /* Need to make sure the placeholder is large enough. */
{
puts("Failed to create TheClass, exiting.");
return 1;
}
printf("The magic result is %d\n", do_magic( c, 232 ));
delete_the_class( c );
return 0;
}
Run Code Online (Sandbox Code Playgroud)
这只是一个用于说明目的的人为例子.希望它是有帮助的.这种方法可能存在微妙的问题,因此在特定平台上进行测试非常重要.
一些额外的说明:
C代码中的THE_CLASS_SIZE TheClass就是要分配C++ 实例的内存缓冲区的大小; 只要缓冲区的大小足以容纳C++,我们就可以了TheClass
因为TheClass在C中只是一个内存占位符,我们也可以使用a void *,可能是typedef'd'作为包装函数中的参数类型而不是TheClass.我们会reinterpret_cast
在包装器代码中实现,这实际上会使代码更清晰:无论如何,
指向C的指针TheClass基本上都被重新解释为C++ TheClass.
TheClass*给实际上并不指向C++ TheClass
实例的包装器函数.解决此问题的一种方法是TheClass在C++代码中的某种数据结构中存储指向正确初始化的C++ 实例的指针,并返回可用于查找这些实例的C代码句柄.cout在C++包装器中使用s,我们需要在构建可执行文件时与C++标准库链接.例如,如果将C代码编译成main.oC++ lib.o,那么在Linux或Mac上我们就可以了gcc -o junk main.o lib.o -lstdc++.| 归档时间: |
|
| 查看次数: |
1316 次 |
| 最近记录: |