最近一周做一个第三方c++库糊c wrapper的工作,干的太慢了,一周啊没搞定。我原本的计划是打算这一周就把这个活搞定,结果连10%都没做完。高估自己能力了。

前两天主要是搞定原来的库,去掉对其他库的依赖(锁用的还是pthread api,我给去掉了),剩下的时间,思考怎么把cpp导出c接口。。这也太闹心了。学习了一下rocksdb导出的实现。

结构体封装

类的字段全都要导出接口,天哪。

参数处理

首先说下我个人的暴力替换法

  • std::vector<T> 改成 T*

  • std::vector<std::string>(&) 直接改成 char**

  • std::vector<std::string>* 直接变成char *** 感觉已经招架不住了

返回值

  • 返回指针还是传入指针or传入buffer指针?buffer指针咋设置合适,or 传入指针在内部分配?不如直接返回指针

  • 返回的话管理内存就得交给调用方?

Rocksdb做法

char* rocksdb_get(
    rocksdb_t* db,
    const rocksdb_readoptions_t* options,
    const char* key, size_t keylen,
    size_t* vallen,
    char** errptr) {
  char* result = nullptr;
  std::string tmp;
  Status s = db->rep->Get(options->rep, Slice(key, keylen), &tmp);
  if (s.ok()) {
    *vallen = tmp.size();
    result = CopyString(tmp);
  } else {
    *vallen = 0;
    if (!s.IsNotFound()) {
      SaveError(errptr, s);
    }
  }
  return result;
}

返回指针,传入长度,这只是一个kv,如果是多个呢,如果是复杂的数据结构呢?

错误处理

Rocksdb 是怎么把错误导出的。传入错误指针,saveerror

static bool SaveError(char** errptr, const Status& s) {
  assert(errptr != nullptr);
  if (s.ok()) {
    return false;
  } else if (*errptr == nullptr) {
    *errptr = strdup(s.ToString().c_str());
  } else {
    // This is a bug if *errptr is not created by malloc()
    free(*errptr);
    *errptr = strdup(s.ToString().c_str());
  }
  return true;
}

char* err = NULL; 传入&err (为啥二级指针?) err本身是个字符串,传字符串地址。

编译问题

gcc编译c程序,链接c++的静态库,需要-lstdc++,不然会有连接错误。或者用g++编译,默认带stdc++ runtime 其实这背后有更恶心的问题,如果你链接一个静态库,这个静态库依赖其他静态库,这里头的依赖关系就很恶心了。 最近看pika,主程序依赖rocksdb和blackwidow,blackwidow也依赖rocksdb,链接都是静态。就很混乱。

一个示例

[root@host]# tree
|-- libp.so
|-- libstaticp.a
|-- p.cpp
|-- p.h
|-- p.o
|-- p_test.c
|-- ptest_d
`-- ptest_s

p.cpp

#include "p.h"
#include <iostream>
using namespace std;
extern "C"{
void print_int(int a){
    cout<<a<<endl;
}
}

p.h

#ifndef _P_
#define _P_
#ifdef __cplusplus 
extern "C" {
#endif
void print_int(int a);
#ifdef __cplusplus
}
#endif
#endif

p_test.c

#include "p.h"
int main(){
    print_int(111);
    return 0;
}
  • 需要导入当前目录到环境中,方便ld
    export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH
    
  • 编译动态库
    g++ p.cpp -fPIC -shared -o libp.so
    
  • 编译静态库
    g++ -c p.cpp
    ar cqs libstaticp.a p.o
    
  • 编译程序
    gcc -o ptest_d p_test.c -L. -lp					#ok
    g++ -o ptest_s p_test.c -L. -lstaticp			#ok
    gcc -o ptest_s p_test.c -L. -lstaticp -lstdc++	#ok
    gcc -o ptest_s p_test.c -L. -lstaticp			#not ok
    ./libstaticp.a(p.o): In function `print_int':
    p.cpp:(.text+0x11): undefined reference to `std::cout'
    p.cpp:(.text+0x16): undefined reference to `std::ostream::operator<<(int)'
    p.cpp:(.text+0x1b): undefined reference to `std::basic_ostream<char, std::char_traits<char> >& std::endl<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&)'
    p.cpp:(.text+0x23): undefined reference to `std::ostream::operator<<(std::ostream& (*)(std::ostream&))'
    ./libstaticp.a(p.o): In function `__static_initialization_and_destruction_0(int, int)':
    p.cpp:(.text+0x4c): undefined reference to `std::ios_base::Init::Init()'
    p.cpp:(.text+0x5b): undefined reference to `std::ios_base::Init::~Init()'
    collect2: error: ld returned 1 exit status
    

    reference

  • 参考这个 写的例子 https://blog.csdn.net/surgewong/article/details/39236707
  • 注意p.h中需要有__cplusplus marco guard, 因为extern “C” 不是c的内容,会报错 https://stackoverflow.com/questions/10307762/error-expected-before-string-constant
  • https://arne-mertz.de/2018/10/calling-cpp-code-from-c-with-extern-c/ 这个链接说了extern “C”在c++中的风格干净的用法,注意,在c中还是用不了。