is there a portable way to get a pointer for struct with virtual table through a pointer to a member of it

0

in my io completion ports implementation I was delivering notifications through a switch statement on an opcode in a struct inheriting from OVERLAPPED structure expecting that the OVERLAPPED base is at offset 0 from the derived one

later I used a virtual method in the derived struct instead of an opcode with a switch , this made the design easier and more clear . I was still thinking that the OVERLAPPED structure is at offset 0 so I simply used something like this to issue an io operation :

WSASend(ns, &wsbuf, 1, &sent, 0, (LPWSAOVERLAPPED)(std::addressof(CTX)), nullptr);

where CTX is a struct of type BaseIoCtx which is :

BaseIoCtx : public OVERLAPPED
{

    void *fd;

    virtual void HandleIocpPacket(io::iocp::CompletionResult& IocpPacket) = 0;

    virtual ~BaseIoCtx() = default;
};

and in casting back in the io loop I used this code :

// BaseIoCtx inherits from OVERLAPPED
// result is an OVERLAPPED_ENTRY
BaseIoCtx *ctx = reinterpret_cast<BaseIoCtx*>(result.lpOverlapped);
ctx->HandleIocpPacket(result); // the virtual function that delivers the notification

I thought this should work well , and it worked well for more than month !

yesterday after doing some tests I got a strange crash at this line :

ctx->HandleIocpPacket(result); // the virtual function that delivers the notification

the debugger was saying : read access violation ... ctx-> was (nullptr, 0XFFFFFFFF, any strange address)

however I seed ctx itself was at a valid address in the visual studio debugger and contained the correct transferred length in the OVERLAPPED base but the __vfptr was nullptr or a very strange address like 0xFFFFFFF or 0xCCCCCCCCC so I set a breakpoint on the line before submitting the io request and saw that the __vfptr was pointing to a valid vtable and contained the two virtual functions of BaseIoCtx , so I figured that during the io request some windows internal function wrote at the __vfptr and corrupted it .

I was wondering because I though the base structure was at the start of the derived not the __vfptr but after some experiments with offsetof I found that __vfptr was at offset 0 instead !

yet I then changed the BaseIoCtx into this :

struct BaseIoCtx : public IoContext
{

    OVERLAPPED ov = { 0 };

    static BaseIoCtx * from_ov_ptr(OVERLAPPED *ov_ptr) noexcept
    {
        constexpr std::size_t ov_offset = offsetof(BaseIoCtx, ov);
        return reinterpret_cast<BaseIoCtx*>(reinterpret_cast<char*>(ov_ptr) - ov_offset);
    }

    void *fd;

    virtual void HandleIocpPacket(io::iocp::CompletionResult& IocpPacket) = 0;

    virtual ~BaseIoCtx() = default;
};

and in the the submitting I used :

WSASend(ns, &wsbuf, 1, &sent, 0, (LPWSAOVERLAPPED)(std::addressof(CTX.ov)), nullptr);

then in the io loop I got back the structure as :

BaseIoCtx * ctx = BaseIoCtx::from_ov_ptr(result.lpOverlapped);

and it works well . now the questions are : how did the first implementation worked for long time on the same compiler ! and is the new one considered a portable way in c++ rather than a hack and if it is a hack is there a better one ?

c++
asked on Stack Overflow Dec 14, 2019 by dev65

0 Answers

Nobody has answered this question yet.


User contributions licensed under CC BY-SA 3.0