Chrome native host in C++, cannot communicate with Chrome
Asked Answered
G

2

10

I am trying to implement a Chrome extension using runtime.connectNative and postMessage. I am following the Chrome documentation, downloaded the native messaging example, and changed the native app to using C++.

However, the native app cannot receive the message from the Chrome extension.

Meanwhile, when the native app using the printf function write message to chrome extension, the extension can not receive and the message just shown in the console.

Any ideas how to solve the problem?

Gaius answered 27/10, 2014 at 7:10 Comment(0)
T
27

You didn't provide a lot information about what you actually tried, so I will do my best to explain the steps needed to implement Chrome Extension, Native Messaging host and establish communication between them. (Please examine the following link to obtain more information about Chrome Native Messaging: Chrome Native Messaging How to.

CHROME EXTENSION

Firstly, we need to set up Chrome extension. As this will be very simple extension, we need only manifest.json file (please note this is extension's manifest file - native host will have its own manifest file as well) and background.js javascript implementation.

The following is sample manifest.json file:

{
  "name": "Test extension",
  "description": "Native messaging test",
   "permissions": [
                    "nativeMessaging",
                    "tabs",
                    "activeTab",
                    "background",
                    "http://*/", "https://*/"
                    ],
  "background": {
    "scripts": ["background.js"]
  },
  "version": "1.0",
  "minimum_chrome_version": "29",
  "manifest_version": 2
}

Important things here are that implementation will be provided in background.js, minimum supported Chrome version is 29 and HTTP and HTTPS are both supported.

Next, background.js file has the following content:

var port = chrome.runtime.connectNative('com.dolby.native_messaging_host');

port.onMessage.addListener(function(msg) {
  console.log(msg.text);
});

port.onDisconnect.addListener(function() {
  console.log("Disconnected");
});

port.postMessage({"text":"This is message from Chrome extension"});

The code itself is pretty self-explanatory - we try to connect to native host identified by com.dolby.native_messaging_host key (I will come to this in a minute). Then, we register a listener for onMessage event (this event is triggered when native host sends a message to the chrome extension). We also register a listener for disconnect event (for example when native host dies this event will be triggered). And finally, we send a message using postMessage method.

NATIVE MESSAGING HOST

Now, native host also has its own manifest.json file. Very simple manifest.json file for native host is as follows:

{
  "name": "com.dolby.native_messaging_host",
  "description": "Native messaging host",
  "path": "C:\\Users\\dbajg\\Desktop\\Native-messaging-host\\Debug\\Native-messaging-host.exe",
  "type": "stdio",
  "allowed_origins": [
    "chrome-extension://bjgnpdfhbcpjdfjoplajcmbleickphpg/"
  ]
}

Couple of interesting things here: name identifies the key under which this native host is registered. Path is full path to native host executable. Type of communication stdio means we are using standard input/output for communication (only type currently supported). And finally, allowed_origins specify which extensions can communicate with this native host - so you have to find out what is your extension's key!.

The next step is to register this Native Messaging host in registry (for Windows) and specify the location to its manifest file. The following screenshots explains how to this for Windows (examine provided link to find out how to do this in OSX and Linux):

Registry entry for native messaging host (Windows OS only)

After you've added registry entry for your native host the only remaining thing is to write your native host. The following C++ code implements simple native host that reads messages from the standard input and writes response to standard output (when you send #STOP# message the native host exits):

#include <iostream>
#include <string>

int main(){
    std::string oneLine = "";

    while (1){
        unsigned int length = 0;

        //read the first four bytes (=> Length)
        /*for (int i = 0; i < 4; i++)
        {
            int read_char = getchar();
            length += read_char * (int) pow(2.0, i*8);
            std::string s = std::to_string((long long)read_char) + "\n";
            fwrite(s.c_str(), sizeof(char), s.size(), f);
            fflush(f);
        }*/

        //Neat way!
        for (int i = 0; i < 4; i++)
        {
            unsigned int read_char = getchar();
            length = length | (read_char << i*8);
        }

        //read the json-message
        std::string msg = "";
        for (int i = 0; i < length; i++)
        {
            msg += getchar();
        }

        std::string message = "{\"text\":\"This is a response message\"}";
        // Collect the length of the message
        unsigned int len = message.length();

        // Now we can output our message
        if (msg == "{\"text\":\"#STOP#\"}"){
            message = "{\"text\":\"EXITING...\"}";
            len = message.length();

            std::cout   << char(len>>0)
                        << char(len>>8)
                        << char(len>>16)
                        << char(len>>24);

            std::cout << message;
            break;
        }
        
        // return stdin message
        len = length;
        std::cout   << char(len>>0)
                    << char(len>>8)
                    << char(len>>16)
                    << char(len>>24);

        std::cout << msg << std::flush;

        // return response message
        // std::cout    << char(len>>0)
        //          << char(len>>8)
        //          << char(len>>16)
        //          << char(len>>24);
        //  
        // std::cout << message << std::flush;
    }
    
    return 0;
}

Messages sent by extension to native host is formed in a way that first byte stores the number of bytes in the message. So the first thing native host must do is to read the first 4 bytes and calculate the size of the message. I explained how to do this in another post that can be found here:

How to calculate size of the message sent by chrome extension

Tadeo answered 27/10, 2014 at 8:55 Comment(10)
Thanks very much for your answer, I tried this method earlier and using a html page to launch the extension. However, the extension can not receive the html event, the html page is like this: function startApp() { var evt = document.createEvent("CustomEvent"); evt.initCustomEvent('myEvent', true, false, "Hello world"); document.dispatchEvent(evt); }Gaius
could you share me a sample of the html file which can launch the extension? thanks very much.Gaius
To be honest I have never used HTML to load an extension. I did it in the following way: navigate to chrome://extensions (you can type this in URL bar), choose "Load unpacked extension..." and then select the extension's folder (this is a folder that contains background.js and manifest.json for the extension). And I believe the extension is disabled by default so you have to enable it yourself.Tadeo
But when the native app is launched? or just when the extension is loaded, the native app is automatically loaded?Gaius
I used the chrome sample which launch the extension from chrome://apps, there is a button in the html page which can trigger the native app loaded. However, in my case, the native app is started. but can not see the data transmit. The app is always waiting in the getChar() sentence.Gaius
"When a messaging port is created using runtime.connectNative Chrome starts native messaging host process and keeps it running until the port is destroyed. On the other hand, when a message is sent using runtime.sendNativeMessage, without creating a messaging port, Chrome starts a new native messaging host process for each message." So, when extension calls connectNative the Native Messaging host is automatically started and it will be opened as long as port is opened.Tadeo
If native host is stuck in getChar() it means there are no bytes to read from standard input. So, your extension is probably not writing any data.Tadeo
thanks very much, it finally works. In the host manifest, I set a batch file to the app path and the native app is started in the batch file. it may be the reason. when I skip the batch file, it works.Gaius
Dear @Džanan When I click on connect, the exe file is running successfully, however when I send a message from chrome extension, nothing happens on the server side(mean exe file). Where is the problem?Demented
@Džanan thanks but there is a break absent for the while loop in C++ code. It does not exit and create memory overflowGeisha
A
6

For future Google people, here's how I do it:

C style

Reading

char bInLen[4];
read(0, bInLen, 4); // 0 is stdin
unsigned int inLen = *(unsigned int *)bInLen;
char *inMsg = (char *)malloc(inLen);
read(0, inMsg, inLen);
inMsg[inLen] = '\0';
...
free(inMsg);

Writing

char *outMsg = "{\"text\":\"This is a response message\"}";
unsigned int outLen = strlen(outMsg);
char *bOutLen = (char *)&outLen;
write(1, bOutLen, 4); // 1 is stdout
write(1, outMsg, outLen);
fflush(stdout);

C++ style

Reading

char bInLen[4];
cin.read(bInLen, 4);
unsigned int inLen = *reinterpret_cast<unsigned int *>(bInLen);
char *inMsg = new char[inLen];
cin.read(inMsg, inLen);
string inStr(inMsg); // if you have managed types, use them!
delete[] inMsg;

Writing

string outMsg = "{\"text\":\"This is a response message\"}";
unsigned int outLen = outMsg.length();
char *bOutLen = reinterpret_cast<char *>(&outLen);
cout.write(bOutLen, 4);
cout << outMsg << flush;
Authority answered 28/1, 2016 at 11:31 Comment(2)
using raw buffers in c++. Ugh.Blalock
inMsg[inLen] overflows.Jural

© 2022 - 2024 — McMap. All rights reserved.