diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..b1e20fbe --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,68 @@ +name: Continuous integration + +on: [push, pull_request] + +defaults: + run: + shell: bash + +jobs: + test: + strategy: + matrix: + os: [ubuntu-latest, macos-latest] + go: [1.16.x, 1.15.x, 1.14.x, 1.13.x] + name: Go ${{ matrix.go }} tests @ ${{ matrix.os }} + runs-on: ${{ matrix.os }} + steps: + - name: Install Linux dependencies + if: startsWith(matrix.os, 'ubuntu-') + run: | + sudo apt-get update + sudo apt-get install -yq librocksdb-dev + + - name: Install MacOS dependencies + if: startsWith(matrix.os, 'macos-') + run: | + brew update + brew install rocksdb + + - name: Install Golang ${{ matrix.go }} + id: install-golang + uses: actions/setup-go@v2 + with: + go-version: ${{ matrix.go }} + - run: go version + + - name: Checkout code + uses: actions/checkout@v2 + + - name: Cache Golang modules + uses: actions/cache@v2 + with: + path: ~/go/pkg/mod + key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} + restore-keys: | + ${{ runner.os }}-go- + + - name: Test v5 + if: startsWith(matrix.os, 'ubuntu-') + run: go test -v + + - name: Test v6.16 or later + if: startsWith(matrix.os, 'macos-') + run: go test -v -tags rocksdb_6_16 + + golangci: + name: lint + runs-on: ubuntu-latest + steps: + - name: Install Linux dependencies + run: | + sudo apt-get update + sudo apt-get install -yq librocksdb-dev + - uses: actions/checkout@v2 + - name: golangci-lint + uses: golangci/golangci-lint-action@v2 + with: + version: latest diff --git a/.gitignore b/.gitignore index 485dee64..3ce5adbb 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ .idea +vendor diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 00000000..2babb434 --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,56 @@ +# options for analysis running +run: + # default concurrency is a available CPU number + concurrency: 4 + + # timeout for analysis, e.g. 30s, 5m, default is 1m + timeout: 20m + + # exit code when at least one issue was found, default is 1 + issues-exit-code: 1 + + # include test files or not, default is true + tests: true + + # default is true. Enables skipping of directories: + # vendor$, third_party$, testdata$, examples$, Godeps$, builtin$ + skip-dirs-use-default: true + + # which dirs to skip: they won't be analyzed; + # can use regexp here: generated.*, regexp is applied on full path; + # default value is empty list, but next dirs are always skipped independently + # from this option's value: + # vendor$, third_party$, testdata$, examples$, Godeps$, builtin$ + # skip-dirs: + # - ^test.* + + # by default isn't set. If set we pass it to "go list -mod={option}". From "go help modules": + # If invoked with -mod=readonly, the go command is disallowed from the implicit + # automatic updating of go.mod described above. Instead, it fails when any changes + # to go.mod are needed. This setting is most useful to check that go.mod does + # not need updates, such as in a continuous integration and testing system. + # If invoked with -mod=vendor, the go command assumes that the vendor + # directory holds the correct copies of dependencies and ignores + # the dependency descriptions in go.mod. + modules-download-mode: readonly + + # which files to skip: they will be analyzed, but issues from them + # won't be reported. Default value is empty list, but there is + # no need to include all autogenerated files, we confidently recognize + # autogenerated files. If it's not please let us know. + # skip-files: + # - ".*\\.my\\.go$" + # - lib/bad.go + +# all available settings of specific linters +linters-settings: + govet: + enable: + - nilness + misspell: + # Correct spellings using locale preferences for US or UK. + # Default is to use a neutral variety of English. + # Setting locale to US will correct the British spelling of 'colour' to 'color'. + # locale: US + ignore-words: + - rela # This is for elf.SHT_RELA diff --git a/.travis.yml b/.travis.yml index 9b331467..8e27ad7c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,10 @@ dist: xenial language: go go: - - 1.12.x - 1.13.x + - 1.14.x + - 1.15.x + - 1.16.x - tip before_install: @@ -24,10 +26,10 @@ install: - sudo cp --preserve=links ./librocksdb.* /usr/lib/ - sudo cp -r ./include/rocksdb/ /usr/include/ - popd - - go get -t ./... + - go get -t -tags rocksdb_6_16 ./... script: - - go test -v ./ + - go test -v -tags rocksdb_6_16 ./ notifications: email: diff --git a/checkpoint_test.go b/checkpoint_test.go index 1ea10fdb..d90d47ed 100644 --- a/checkpoint_test.go +++ b/checkpoint_test.go @@ -41,8 +41,8 @@ func TestCheckpoint(t *testing.T) { opts := NewDefaultOptions() opts.SetCreateIfMissing(true) dbCheck, err = OpenDb(opts, dir) - defer dbCheck.Close() ensure.Nil(t, err) + defer dbCheck.Close() // test keys var value *Slice diff --git a/db.go b/db.go index 64735c61..5764f7c0 100755 --- a/db.go +++ b/db.go @@ -577,89 +577,6 @@ func (db *DB) DropColumnFamily(c *ColumnFamilyHandle) error { return nil } -// GetApproximateSizes returns the approximate number of bytes of file system -// space used by one or more key ranges. -// -// The keys counted will begin at Range.Start and end on the key before -// Range.Limit. -func (db *DB) GetApproximateSizes(ranges []Range) []uint64 { - sizes := make([]uint64, len(ranges)) - if len(ranges) == 0 { - return sizes - } - - cStarts := make([]*C.char, len(ranges)) - cLimits := make([]*C.char, len(ranges)) - cStartLens := make([]C.size_t, len(ranges)) - cLimitLens := make([]C.size_t, len(ranges)) - for i, r := range ranges { - cStarts[i] = (*C.char)(C.CBytes(r.Start)) - cStartLens[i] = C.size_t(len(r.Start)) - cLimits[i] = (*C.char)(C.CBytes(r.Limit)) - cLimitLens[i] = C.size_t(len(r.Limit)) - } - - defer func() { - for i := range ranges { - C.free(unsafe.Pointer(cStarts[i])) - C.free(unsafe.Pointer(cLimits[i])) - } - }() - - C.rocksdb_approximate_sizes( - db.c, - C.int(len(ranges)), - &cStarts[0], - &cStartLens[0], - &cLimits[0], - &cLimitLens[0], - (*C.uint64_t)(&sizes[0])) - - return sizes -} - -// GetApproximateSizesCF returns the approximate number of bytes of file system -// space used by one or more key ranges in the column family. -// -// The keys counted will begin at Range.Start and end on the key before -// Range.Limit. -func (db *DB) GetApproximateSizesCF(cf *ColumnFamilyHandle, ranges []Range) []uint64 { - sizes := make([]uint64, len(ranges)) - if len(ranges) == 0 { - return sizes - } - - cStarts := make([]*C.char, len(ranges)) - cLimits := make([]*C.char, len(ranges)) - cStartLens := make([]C.size_t, len(ranges)) - cLimitLens := make([]C.size_t, len(ranges)) - for i, r := range ranges { - cStarts[i] = (*C.char)(C.CBytes(r.Start)) - cStartLens[i] = C.size_t(len(r.Start)) - cLimits[i] = (*C.char)(C.CBytes(r.Limit)) - cLimitLens[i] = C.size_t(len(r.Limit)) - } - - defer func() { - for i := range ranges { - C.free(unsafe.Pointer(cStarts[i])) - C.free(unsafe.Pointer(cLimits[i])) - } - }() - - C.rocksdb_approximate_sizes_cf( - db.c, - cf.c, - C.int(len(ranges)), - &cStarts[0], - &cStartLens[0], - &cLimits[0], - &cLimitLens[0], - (*C.uint64_t)(&sizes[0])) - - return sizes -} - // SetOptions dynamically changes options through the SetOptions API. func (db *DB) SetOptions(keys, values []string) error { num_keys := len(keys) diff --git a/db_6.go b/db_6.go new file mode 100644 index 00000000..e599f93f --- /dev/null +++ b/db_6.go @@ -0,0 +1,91 @@ +// +build !rocksdb_6_16 + +package gorocksdb + +// #include +// #include "rocksdb/c.h" +import "C" +import "unsafe" + +// GetApproximateSizes returns the approximate number of bytes of file system +// space used by one or more key ranges. +// +// The keys counted will begin at Range.Start and end on the key before +// Range.Limit. +func (db *DB) GetApproximateSizes(ranges []Range) []uint64 { + sizes := make([]uint64, len(ranges)) + if len(ranges) == 0 { + return sizes + } + + cStarts := make([]*C.char, len(ranges)) + cLimits := make([]*C.char, len(ranges)) + cStartLens := make([]C.size_t, len(ranges)) + cLimitLens := make([]C.size_t, len(ranges)) + for i, r := range ranges { + cStarts[i] = (*C.char)(C.CBytes(r.Start)) + cStartLens[i] = C.size_t(len(r.Start)) + cLimits[i] = (*C.char)(C.CBytes(r.Limit)) + cLimitLens[i] = C.size_t(len(r.Limit)) + } + + defer func() { + for i := range ranges { + C.free(unsafe.Pointer(cStarts[i])) + C.free(unsafe.Pointer(cLimits[i])) + } + }() + + C.rocksdb_approximate_sizes( + db.c, + C.int(len(ranges)), + &cStarts[0], + &cStartLens[0], + &cLimits[0], + &cLimitLens[0], + (*C.uint64_t)(&sizes[0])) + + return sizes +} + +// GetApproximateSizesCF returns the approximate number of bytes of file system +// space used by one or more key ranges in the column family. +// +// The keys counted will begin at Range.Start and end on the key before +// Range.Limit. +func (db *DB) GetApproximateSizesCF(cf *ColumnFamilyHandle, ranges []Range) []uint64 { + sizes := make([]uint64, len(ranges)) + if len(ranges) == 0 { + return sizes + } + + cStarts := make([]*C.char, len(ranges)) + cLimits := make([]*C.char, len(ranges)) + cStartLens := make([]C.size_t, len(ranges)) + cLimitLens := make([]C.size_t, len(ranges)) + for i, r := range ranges { + cStarts[i] = (*C.char)(C.CBytes(r.Start)) + cStartLens[i] = C.size_t(len(r.Start)) + cLimits[i] = (*C.char)(C.CBytes(r.Limit)) + cLimitLens[i] = C.size_t(len(r.Limit)) + } + + defer func() { + for i := range ranges { + C.free(unsafe.Pointer(cStarts[i])) + C.free(unsafe.Pointer(cLimits[i])) + } + }() + + C.rocksdb_approximate_sizes_cf( + db.c, + cf.c, + C.int(len(ranges)), + &cStarts[0], + &cStartLens[0], + &cLimits[0], + &cLimitLens[0], + (*C.uint64_t)(&sizes[0])) + + return sizes +} diff --git a/db_6_16.go b/db_6_16.go new file mode 100644 index 00000000..756f9fcf --- /dev/null +++ b/db_6_16.go @@ -0,0 +1,108 @@ +// +build rocksdb_6_16 + +package gorocksdb + +// #include +// #include "rocksdb/c.h" +import "C" +import ( + "errors" + "unsafe" +) + +// GetApproximateSizes returns the approximate number of bytes of file system +// space used by one or more key ranges. +// +// The keys counted will begin at Range.Start and end on the key before +// Range.Limit. +func (db *DB) GetApproximateSizes(ranges []Range) (sizes []uint64, err error) { + var cErr *C.char + sizes = make([]uint64, len(ranges)) + if len(ranges) == 0 { + return + } + + cStarts := make([]*C.char, len(ranges)) + cLimits := make([]*C.char, len(ranges)) + cStartLens := make([]C.size_t, len(ranges)) + cLimitLens := make([]C.size_t, len(ranges)) + for i, r := range ranges { + cStarts[i] = (*C.char)(C.CBytes(r.Start)) + cStartLens[i] = C.size_t(len(r.Start)) + cLimits[i] = (*C.char)(C.CBytes(r.Limit)) + cLimitLens[i] = C.size_t(len(r.Limit)) + } + + defer func() { + for i := range ranges { + C.free(unsafe.Pointer(cStarts[i])) + C.free(unsafe.Pointer(cLimits[i])) + } + }() + + C.rocksdb_approximate_sizes( + db.c, + C.int(len(ranges)), + &cStarts[0], + &cStartLens[0], + &cLimits[0], + &cLimitLens[0], + (*C.uint64_t)(&sizes[0]), + &cErr, + ) + if cErr != nil { + defer C.rocksdb_free(unsafe.Pointer(cErr)) + err = errors.New(C.GoString(cErr)) + } + + return +} + +// GetApproximateSizesCF returns the approximate number of bytes of file system +// space used by one or more key ranges in the column family. +// +// The keys counted will begin at Range.Start and end on the key before +// Range.Limit. +func (db *DB) GetApproximateSizesCF(cf *ColumnFamilyHandle, ranges []Range) (sizes []uint64, err error) { + var cErr *C.char + sizes = make([]uint64, len(ranges)) + if len(ranges) == 0 { + return + } + + cStarts := make([]*C.char, len(ranges)) + cLimits := make([]*C.char, len(ranges)) + cStartLens := make([]C.size_t, len(ranges)) + cLimitLens := make([]C.size_t, len(ranges)) + for i, r := range ranges { + cStarts[i] = (*C.char)(C.CBytes(r.Start)) + cStartLens[i] = C.size_t(len(r.Start)) + cLimits[i] = (*C.char)(C.CBytes(r.Limit)) + cLimitLens[i] = C.size_t(len(r.Limit)) + } + + defer func() { + for i := range ranges { + C.free(unsafe.Pointer(cStarts[i])) + C.free(unsafe.Pointer(cLimits[i])) + } + }() + + C.rocksdb_approximate_sizes_cf( + db.c, + cf.c, + C.int(len(ranges)), + &cStarts[0], + &cStartLens[0], + &cLimits[0], + &cLimitLens[0], + (*C.uint64_t)(&sizes[0]), + &cErr, + ) + if cErr != nil { + defer C.rocksdb_free(unsafe.Pointer(cErr)) + err = errors.New(C.GoString(cErr)) + } + + return +} diff --git a/db_6_16_test.go b/db_6_16_test.go new file mode 100644 index 00000000..6deace37 --- /dev/null +++ b/db_6_16_test.go @@ -0,0 +1,54 @@ +// +build rocksdb_6_16 + +package gorocksdb + +import ( + "testing" + + "github.com/facebookgo/ensure" +) + +func TestDBGetApproximateSizes(t *testing.T) { + db := newTestDB(t, "TestDBGetApproximateSizes", nil) + defer db.Close() + + // no ranges + sizes, err := db.GetApproximateSizes(nil) + ensure.Nil(t, err) + ensure.DeepEqual(t, len(sizes), 0) + + // range will nil start and limit + sizes, err = db.GetApproximateSizes([]Range{{Start: nil, Limit: nil}}) + ensure.Nil(t, err) + ensure.DeepEqual(t, sizes, []uint64{0}) + + // valid range + sizes, err = db.GetApproximateSizes([]Range{{Start: []byte{0x00}, Limit: []byte{0xFF}}}) + ensure.Nil(t, err) + ensure.DeepEqual(t, sizes, []uint64{0}) +} + +func TestDBGetApproximateSizesCF(t *testing.T) { + db := newTestDB(t, "TestDBGetApproximateSizesCF", nil) + defer db.Close() + + o := NewDefaultOptions() + + cf, err := db.CreateColumnFamily(o, "other") + ensure.Nil(t, err) + + // no ranges + sizes, err := db.GetApproximateSizesCF(cf, nil) + ensure.Nil(t, err) + ensure.DeepEqual(t, len(sizes), 0) + + // range will nil start and limit + sizes, err = db.GetApproximateSizesCF(cf, []Range{{Start: nil, Limit: nil}}) + ensure.Nil(t, err) + ensure.DeepEqual(t, sizes, []uint64{0}) + + // valid range + sizes, err = db.GetApproximateSizesCF(cf, []Range{{Start: []byte{0x00}, Limit: []byte{0xFF}}}) + ensure.Nil(t, err) + ensure.DeepEqual(t, sizes, []uint64{0}) +} diff --git a/db_6_test.go b/db_6_test.go new file mode 100644 index 00000000..3c5ca62b --- /dev/null +++ b/db_6_test.go @@ -0,0 +1,48 @@ +// +build !rocksdb_6_16 + +package gorocksdb + +import ( + "testing" + + "github.com/facebookgo/ensure" +) + +func TestDBGetApproximateSizes(t *testing.T) { + db := newTestDB(t, "TestDBGetApproximateSizes", nil) + defer db.Close() + + // no ranges + sizes := db.GetApproximateSizes(nil) + ensure.DeepEqual(t, len(sizes), 0) + + // range will nil start and limit + sizes = db.GetApproximateSizes([]Range{{Start: nil, Limit: nil}}) + ensure.DeepEqual(t, sizes, []uint64{0}) + + // valid range + sizes = db.GetApproximateSizes([]Range{{Start: []byte{0x00}, Limit: []byte{0xFF}}}) + ensure.DeepEqual(t, sizes, []uint64{0}) +} + +func TestDBGetApproximateSizesCF(t *testing.T) { + db := newTestDB(t, "TestDBGetApproximateSizesCF", nil) + defer db.Close() + + o := NewDefaultOptions() + + cf, err := db.CreateColumnFamily(o, "other") + ensure.Nil(t, err) + + // no ranges + sizes := db.GetApproximateSizesCF(cf, nil) + ensure.DeepEqual(t, len(sizes), 0) + + // range will nil start and limit + sizes = db.GetApproximateSizesCF(cf, []Range{{Start: nil, Limit: nil}}) + ensure.DeepEqual(t, sizes, []uint64{0}) + + // valid range + sizes = db.GetApproximateSizesCF(cf, []Range{{Start: []byte{0x00}, Limit: []byte{0xFF}}}) + ensure.DeepEqual(t, sizes, []uint64{0}) +} diff --git a/db_test.go b/db_test.go index 4ccc7aa8..87696d57 100755 --- a/db_test.go +++ b/db_test.go @@ -204,42 +204,3 @@ func TestDBMultiGet(t *testing.T) { ensure.DeepEqual(t, values[2].Data(), givenVal2) ensure.DeepEqual(t, values[3].Data(), givenVal3) } - -func TestDBGetApproximateSizes(t *testing.T) { - db := newTestDB(t, "TestDBGetApproximateSizes", nil) - defer db.Close() - - // no ranges - sizes := db.GetApproximateSizes(nil) - ensure.DeepEqual(t, len(sizes), 0) - - // range will nil start and limit - sizes = db.GetApproximateSizes([]Range{{Start: nil, Limit: nil}}) - ensure.DeepEqual(t, sizes, []uint64{0}) - - // valid range - sizes = db.GetApproximateSizes([]Range{{Start: []byte{0x00}, Limit: []byte{0xFF}}}) - ensure.DeepEqual(t, sizes, []uint64{0}) -} - -func TestDBGetApproximateSizesCF(t *testing.T) { - db := newTestDB(t, "TestDBGetApproximateSizesCF", nil) - defer db.Close() - - o := NewDefaultOptions() - - cf, err := db.CreateColumnFamily(o, "other") - ensure.Nil(t, err) - - // no ranges - sizes := db.GetApproximateSizesCF(cf, nil) - ensure.DeepEqual(t, len(sizes), 0) - - // range will nil start and limit - sizes = db.GetApproximateSizesCF(cf, []Range{{Start: nil, Limit: nil}}) - ensure.DeepEqual(t, sizes, []uint64{0}) - - // valid range - sizes = db.GetApproximateSizesCF(cf, []Range{{Start: []byte{0x00}, Limit: []byte{0xFF}}}) - ensure.DeepEqual(t, sizes, []uint64{0}) -} diff --git a/go.mod b/go.mod new file mode 100644 index 00000000..b0912f25 --- /dev/null +++ b/go.mod @@ -0,0 +1,10 @@ +module github.com/tecbot/gorocksdb + +go 1.13 + +require ( + github.com/facebookgo/ensure v0.0.0-20200202191622-63f1cf65ac4c + github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 // indirect + github.com/facebookgo/subset v0.0.0-20200203212716-c811ad88dec4 // indirect + github.com/stretchr/testify v1.7.0 +) diff --git a/go.sum b/go.sum new file mode 100644 index 00000000..35ffeaf5 --- /dev/null +++ b/go.sum @@ -0,0 +1,16 @@ +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/facebookgo/ensure v0.0.0-20200202191622-63f1cf65ac4c h1:8ISkoahWXwZR41ois5lSJBSVw4D0OV19Ht/JSTzvSv0= +github.com/facebookgo/ensure v0.0.0-20200202191622-63f1cf65ac4c/go.mod h1:Yg+htXGokKKdzcwhuNDwVvN+uBxDGXJ7G/VN1d8fa64= +github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 h1:JWuenKqqX8nojtoVVWjGfOF9635RETekkoH6Cc9SX0A= +github.com/facebookgo/stack v0.0.0-20160209184415-751773369052/go.mod h1:UbMTZqLaRiH3MsBH8va0n7s1pQYcu3uTb8G4tygF4Zg= +github.com/facebookgo/subset v0.0.0-20200203212716-c811ad88dec4 h1:7HZCaLC5+BZpmbhCOZJ293Lz68O7PYrF2EzeiFMwCLk= +github.com/facebookgo/subset v0.0.0-20200203212716-c811ad88dec4/go.mod h1:5tD+neXqOorC30/tWg0LCSkrqj/AR6gu8yY8/fpw1q0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/slice_transform_test.go b/slice_transform_test.go index d60c7326..28664f1d 100644 --- a/slice_transform_test.go +++ b/slice_transform_test.go @@ -43,7 +43,7 @@ func TestNewNoopPrefixTransform(t *testing.T) { } type testSliceTransform struct { - initiated bool + // initiated bool } func (st *testSliceTransform) Name() string { return "gorocksdb.test" } diff --git a/util.go b/util.go index b6637ec3..f994d3f8 100644 --- a/util.go +++ b/util.go @@ -52,10 +52,10 @@ func cByteSlice(b []byte) *C.char { } // stringToChar returns *C.char from string. -func stringToChar(s string) *C.char { - ptrStr := (*reflect.StringHeader)(unsafe.Pointer(&s)) - return (*C.char)(unsafe.Pointer(ptrStr.Data)) -} +// func stringToChar(s string) *C.char { +// ptrStr := (*reflect.StringHeader)(unsafe.Pointer(&s)) +// return (*C.char)(unsafe.Pointer(ptrStr.Data)) +// } // charSlice converts a C array of *char to a []*C.char. func charSlice(data **C.char, len C.int) []*C.char {