Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Initialize a C++ class in C through `void *` and C++ `extern "C"` function calls

Tags:

The objective is to use OpenCV 3 in C. OpenCV had a C API, but it has been deprecated since a long time ago.

So what I did is an abstraction in C++ converting all pointers to class cv::Something into void * which I can't dereference in C, but can pass between C++ extern "C" functions which do the work.

To use this abstraction, I did some C function, which should read an image from a file and save it into a new file:

#include "foo.h"

#include "libalx/extra/cv.h"


int foo(const char *restrict src, const char *restrict dest)
{
    void    *img;
    int     status;

    status  = -1;
    if (alx_cv_alloc_img(&img))
        return  -1;
    if (alx_cv_init_img(img, 1, 1))     // Segfault is here
        goto out_free;
    if (alx_cv_imread(img, src))
        goto out_free;
    if (alx_cv_imwrite(img, dest))
        goto out_free;
    status  = 0;
out_free:
    alx_cv_free_img(img);
    return  status;
}

The files needed to create this abstraction are the following:


cv.h:

#pragma once    /* libalx/extra/cv.h */


#include <stddef.h>


__attribute__((nonnull))
int     alx_cv_alloc_img(void **restrict img);
__attribute__((nonnull))
void    alx_cv_free_img (void *restrict img);
__attribute__((nonnull))
int     alx_cv_init_img (void *restrict img, ptrdiff_t w, ptrdiff_t h);
__attribute__((nonnull))
int     alx_cv_imread   (void *restrict img, const char *restrict fname);
__attribute__((nonnull))
void    alx_cv_imwrite  (const void *restrict img, const char *restrict fname);

cv.hpp:

#pragma once    /* libalx/extra/cv.hpp */


#include <cstddef>

#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>


#define restrict    __restrict__


extern  "C"
{
[[gnu::nonnull]]
int     alx_cv_alloc_img(void **restrict img);
[[gnu::nonnull]]
void    alx_cv_free_img (void *restrict img);
[[gnu::nonnull]]
int     alx_cv_init_img (void *restrict img, ptrdiff_t w, ptrdiff_t h);
[[gnu::nonnull]]
int     alx_cv_imread   (void *restrict img, const char *restrict fname);
[[gnu::nonnull]]
void    alx_cv_imwrite  (const void *restrict img, const char *restrict fname);
}   /* extern "C" */


namespace alx {
namespace CV {
[[gnu::nonnull]]
int     alloc_img       (class cv::Mat **restrict img);
[[gnu::nonnull]]
void    free_img        (class cv::Mat *restrict img);
[[gnu::nonnull]]
int     init_img        (class cv::Mat *restrict rect,
                         ptrdiff_t w, ptrdiff_t h);
[[gnu::nonnull]]
int     imread          (class cv::Mat *restrict img,
                         const char *restrict fname);
[[gnu::nonnull]]
void    imwrite         (const class cv::Mat *restrict img,
                         const char *restrict fname);
}   /* namespace CV */
}   /* namespace alx */

cv.cpp:

#include "libalx/extra/cv.hpp"

#include <cstddef>

#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>

#include "libalx/base/stdlib/alloc/mallocs.hpp"


#define restrict    __restrict__


int     alx::CV::alloc_img  (class cv::Mat **restrict img)
{
    return  alx_mallocs(img, 1);
}

int     alx_cv_alloc_img    (void **restrict img)
{
    return  alx::CV::alloc_img((class cv::Mat **)img);
}

void    alx::CV::free_img   (class cv::Mat *restrict img)
{

    img->release();
    free(img);
}

void    alx_cv_free_img     (void *restrict img)
{
    alx::CV::free_img((class cv::Mat *)img);
}

int     alx::CV::init_img   (class cv::Mat *restrict img,
                             ptrdiff_t w, ptrdiff_t h)
{

    if (w < 1 || h < 1)
        return  1;
    *img = cv::Mat::zeros(cv::Size(w, h), CV_8UC3);     // Segfault is here

    return  0;
}

int     alx_cv_init_img     (void *restrict img, ptrdiff_t w, ptrdiff_t h)
{
    return  alx::CV::init_img((class cv::Mat *)img, w, h);
}

int     alx::CV::imread     (class cv::Mat *restrict img,
                             const char *restrict fname)
{

    *img    = cv::imread(fname, CV_LOAD_IMAGE_COLOR);
    if (img->empty())
        return  -1;
    return  0;
}

int     alx_cv_imread       (void *restrict img, const char *restrict fname)
{
    return  alx::CV::imread((class cv::Mat *)img, fname);
}

void    alx::CV::imwrite    (const class cv::Mat *restrict img,
                             const char *restrict fname)
{
    cv::imwrite(fname, *img);
}

void    alx_cv_imwrite      (const void *restrict img,
                             const char *restrict fname)
{
    alx::CV::imwrite((const class cv::Mat *)img, fname);
}

Here I use alx_mallocs which is an enclosure over malloc:


mallocs.hpp:

#pragma once    /* libalx/base/stdlib/alloc/mallocs.hpp */


#include <cstddef>


/*
 * [[gnu::nonnull]]
 * int  alx_mallocs(type **restrict p, ptrdiff_t nmemb);
 */
#define alx_mallocs(ptr, nmemb) (                           \
{                                                           \
    auto    ptr_    = (ptr);                                \
    void    *vp;                                            \
                                                            \
    vp      = alx_mallocs__(nmemb, sizeof(**ptr_));         \
    *ptr_   = static_cast<typeof(*ptr_)>(vp);               \
                                                            \
    !(*ptr_);                                               \
}                                                           \
)

extern  "C"
{
[[gnu::malloc]]
void    *alx_mallocs__(ptrdiff_t nmemb, size_t size);
}

mallocs.c:

#include "libalx/base/stdlib/alloc/mallocs.h"

#include <errno.h>
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>


void    *alx_mallocs__(ptrdiff_t nmemb, size_t size)
{

    if (nmemb < 0)
        goto ovf;
    if (nmemb > (PTRDIFF_MAX / (ptrdiff_t)size))
        goto ovf;
    return  malloc(size * nmemb);
ovf:
    errno   = EOVERFLOW;
    return  NULL;
}

However, it seems that allocating and trying to initialize the allocated memory isn't enough. My guess is that there are some constructors that aren't being called.

How can this be done so that it doesn't segfault?

like image 842
alx Avatar asked Jul 16 '19 11:07

alx


1 Answers

instead of trying to call the copy assignment operator with this

*img = cv::Mat::zeros(cv::Size(w, h), CV_8UC3);  

You can use placement-new to construct an object into already allocated memory

new (img) cv::Mat(cv::Size(w, h), CV_8UC3, 0.0);

However you should also make sure that you are honoring the alignment of a type when constructing it into previously allocated memory.

like image 71
PeterT Avatar answered Dec 19 '22 05:12

PeterT