Click here to Skip to main content
15,881,938 members
Please Sign up or sign in to vote.
0.00/5 (No votes)
See more:
I'm writing some system code for a filesystem driver for an ESP32 based IoT gadget. My toolchain will always be gcc because the ESP32's developer framework requires GCC. The architecture is a known quantity. Portability is no concern. Function pointers are the same size as other function pointers and data pointers.

I've got a (googlable) struct called esp_vfs_t which somewhat simplified, looks like this
C++
typedef struct
{
    int flags;      /*!< ESP_VFS_FLAG_CONTEXT_PTR or ESP_VFS_FLAG_DEFAULT */
    union {
        ssize_t (*write_p)(void* p, int fd, const void * data, size_t size);                         /*!< Write with context pointer */
        ssize_t (*write)(int fd, const void * data, size_t size);                                    /*!< Write without context pointer */
    };
    union {
        off_t (*lseek_p)(void* p, int fd, off_t size, int mode);                                     /*!< Seek with context pointer */
        off_t (*lseek)(int fd, off_t size, int mode);                                                /*!< Seek without context pointer */
    };
    union {
        ssize_t (*read_p)(void* ctx, int fd, void * dst, size_t size);                               /*!< Read with context pointer */
        ssize_t (*read)(int fd, void * dst, size_t size);                                            /*!< Read without context pointer */
    };
    ...

You can see that each function pointer is a union, but I'm only interested in the functions that take void* as their first argument.

Basically I want to OO this by creating a pure abstract class you can derive from, and because I want zero additional overhead for this feature, I want to copy the vtbl directly into this esp_vfs_t structure such that each function pointer is filled with the corresponding function pointer in my vtbl.

The code that I have to do it is ugly, but not crashing. It's simply not calling my functions for reasons I haven't determined yet.

I'm trying to assert that my function pointers got copied where I expect, but C++ won't let me compare addresses i need to compare in order to make it work. This is because the first argument for each function in the structure is void*, while of course the first argument to my vtbl functions are the class instance ptr itself.

The following code compiles and runs "successfully" if i remove the code to attempt to do that assert, but for whatever reason my functions aren't getting called (could be an unrelated issue)

What I expect is a crash or my functions to be called.
Ideally what I'd like for now is to get the code below that asserts to compile:

C++
static bool mount(const char* mount_point,vfs_driver* driver) {
    if(nullptr==mount_point || nullptr==driver) {
        m_last_error=ESP_ERR_INVALID_ARG;
        return false;
    }
    esp_vfs_t vfs;
    memset(&vfs,0,sizeof(vfs));
    vfs.flags = ESP_VFS_FLAG_CONTEXT_PTR;
    // try to assert these values
    void* test1;
    void* test2;
    memcpy(&test1,vfs.fstat,sizeof(void*));
    memcpy(&test2,&esp32::vfs_driver::fstat,sizeof(void*));
    memcpy(&vfs.write_p,driver,sizeof(vfs_driver));
    assert(test1==test2);
    // the above won't compile
    esp_err_t res = esp_vfs_register(mount_point,&vfs,driver);
    if(ESP_OK!=res) {
        m_last_error = res;
        return false;
    }
    return true;
}


Here's the first bit of the abstract base class I've created
C++
class vfs_driver {
public:
    virtual ssize_t write(int fd, const void * data, size_t size)=0;
    virtual off_t lseek(int fd, off_t size, int mode)=0;
    virtual ssize_t read(int fd, void * dst, size_t size)=0;
    virtual ssize_t pread(int fd, void *dst, size_t size, off_t offset)=0;
    virtual ssize_t pwrite(int fd, const void *src, size_t size, off_t offset)=0;
    virtual int open(const char * path, int flags, int mode)=0;
    virtual int close(int fd)=0;
    virtual int fstat(int fd, struct stat * st)=0;
    virtual int fsync(int fd)=0;
...


What I have tried:

The code I've tried is presented as part of the question
Posted
Updated 3-Mar-21 10:10am
v4
Comments
Richard MacCutchan 3-Mar-21 12:37pm    
You create the two pointers test1 and test2, but they don't point to anything so how can you copy those structures to them? Also I am not sure that assert will not just compare the addresses of the two pointers, rather than whatever they are pointing to.
honey the codewitch 3-Mar-21 12:38pm    
Yeah i caught that a minute ago. it amazes me i let that go. I've updated the code. it still doesn't compile.
Richard MacCutchan 3-Mar-21 12:51pm    
I just tried a simple variation of what you have and it compiles fine. What error(s) do you see?
honey the codewitch 3-Mar-21 12:53pm    
src/vfs.hpp:152:31: error: invalid conversion from 'int (*)(int, stat*)' to 'const void*' [-fpermissive]
memcpy(&test1,vfs.fstat,sizeof(void*));

I can't set the permissive flag because gcc is run as part of a large, complicated toolchain and it doesn't like to be touched.

Have you tried reinterpret_cast? e.g.:
C++
#include <iostream>
#include <cassert>

int foo(int)
{
    return 1;
}

int main()
{
    int (*p1)(int) = &foo;
    void *p2 = reinterpret_cast<void*>(&foo);

    assert(p1 == p2);
}

I've tested this using g++ versions 4.8.2, 5.5.0, 6.5.0, 7.2.0, 8.2.0, 9.1.0, 10.2.0 and 11.0.0 (pre-release), with flags -Wall -Wextra -fpermissive. All compile without complaint, so I think it should work with your environment.
 
Share this answer
 
I dismissed that out of hand, without even trying it. It works though. Gosh don't I feel smart today. Thanks!
 
Share this answer
 
Comments
Richard MacCutchan 4-Mar-21 3:01am    
Spitfire beat me to it. Mainly because I was drinking and sleeping, but not both at the same time.

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900