Search code examples
c++google-chrome-extensionchrome-native-messaging

std::cin.read() fails to read stream


I'm implementing a native host for a browser extension. I designed my implementation around std::cin instead of C-style getchar()

The issue here is that std::cin not opened in binary mode and this has effects on Windows based hosts because Chrome browser don't work well with Windows style \r\n hence I have to read it in binary mode.

To read in binary mode, I have to use _setmode(_fileno(stdin), _O_BINARY);

My IDE can't find definition for _fileno and I found that the workaround is to use the following macro,

#if !defined(_fileno)
#define _fileno(__F) ((__F)->_file)
#endif

However, I'm not confident enough with this macro. I believe something is wrong, but I'm using the latest MinGW compiler and not sure why it's not defined.

Update: it seems the function is behind a __STRICT_ANSI__ and I have no idea how to disable it.

Whatever, the program compiles fine and the browser starts it, and when I send message from browser, the application able to read the length of message, and when it try to read the content, the std::cin.read() operation inserts nothing to the buffer vector and the message is not null terminated, but I don't think that causing the issue.

I also made an attempt to send a dummy message to browser without reading but it seems freezing the browser.

#include <iostream>
#include <cstdio>
#include <string>
#include <vector>

#ifdef __WIN32
#include <fcntl.h>
#include <io.h>
#endif

#if !defined(_fileno)
#define _fileno(__F) ((__F)->_file)
#endif

enum class Platforms {
    macOS = 1,
    Windows = 2,
    Linux = 3
};

Platforms platform;

#ifdef __APPLE__
    constexpr Platforms BuildOS = Platforms::macOS;
#elif __linux__
    constexpr Platforms BuildOS = Platforms::Linux;
#elif __WIN32
    constexpr Platforms BuildOS = Platforms::Windows;
#endif


void sendMessage(std::string message) {
    auto *data = message.data();
    auto size = uint32_t(message.size());

    std::cout.write(reinterpret_cast<char *>(&size), 4);
    std::cout.write(data, size);
    std::cout.flush();
}

int main() {
    if constexpr(BuildOS == Platforms::Windows) {
        // Chrome doesn't deal well with Windows style \r\n
        _setmode(_fileno(stdin), _O_BINARY);
        _setmode(_fileno(stdout), _O_BINARY);
    }

    while(true) {
        std::uint32_t messageLength;

        // First Four contains message legnth
        std::cin.read(reinterpret_cast<char*>(&messageLength), 4);

        if (std::cin.eof())
        {
            break;
        }

        std::vector<char> buffer;

        // Allocate ahead
        buffer.reserve(std::size_t(messageLength) + 1);

        std::cin.read(&buffer[0], messageLength);

        std::string message(buffer.data(), buffer.size());

        sendMessage("{type: 'Hello World'}");
    } 
}

Solution

  • Solution:

    buffer.reserve(std::size_t(messageLength) + 1);
    

    should be

    buffer.resize(std::size_t(messageLength) + 1);
    

    or we can presize the buffer during construction with

    std::vector<char> buffer(messageLength +1);
    

    Problem Explanation:

    buffer.reserve(std::size_t(messageLength) + 1);
    

    reserves capacity but doesn't change the size of the vector, so technically

    std::cin.read(&buffer[0], messageLength);`
    

    is illegal, and at

    std::string message(buffer.data(), buffer.size());` 
    

    buffer.size() is still 0.