lib-native-messaging
lib-native-messaging copied to clipboard
📡 Communicate with browser extensions from native C applications with Native Messaging!
libnativemsg
A blazing fast, memory safe, Native Messaging host library for C
This facillitates communicating with firefox or chrome extensions from native applications.
Instead of manually reading and writing from stdin and stdout in your host program, you can simply use 2 simple functions to instantly get started with native messaging in C! With absolutely no sacrifice to performance or resource efficiency!
Wait, what's Native Messaging?
Simple, a way to establish communication between a browser extension and a native application. Check out the respective docs!

Installation
-
On Linux-
PRE
Before installing, note that by default the makefile will use
gcc. You may change this by usingexport CC = desired_compiler. For example you can change the compiler to clang by usingexport CC = clangThe install rule also installs the library and the header file in
/usr/local. If you'd like to install it globally, useexport INSTALL_DIR = /usrMakefile
Use
$ make buildto build the static library, this library is then put insidebuild/with the namelibnativemsg.aUse
$ make installto install this library under/usr/local/lib(by default) and also copy over the header file ininclude/to/usr/local/include(by default) (Note: usingsudomay be required)Use
$ make uninstallto remove the library and the header file from/usr/local(by default) (Note: usingsudomay be required)Use
$ make cleanto cleanup the built files in the repo directory, in case something goes wrong -
On Windows-
PRE
You'll need Visual Studio (2017 or higher) with clang tools for building the library
Solution (.sln)
Open
native-messaging.slnusing your VS and hitbuildin the solution context menu (right click on solution). Make sure your build config is set to Release and win32The output
nativemsg.libfile should be inRelease/Script(s)
Run the powershell script
install.ps1(in administrator mode) to install the packageBy default the install directory is set to
${env:ProgramFiles(x86)}\cpkgs. If you'd like to install it elsewhere, you can set the variable$INSTALL_DIRto your desired path prior to installation. For example, if I'd like to install the package inDesktop\nativemsg, I'd do$INSTALL_DIR = "${env:USERPROFILE}\Desktop\nativemsg"and then run the script as adminRun the powershell script
uninstall.ps1(in administrator mode) to uninstall the packageDuring installation, the installation directory is logged inside
install_location.txt. The uninstall script will read the path from this file.NOTE: Running powershell scripts require you to have script execution policy set to true. Read more about it here
NOTE: You've to run these scripts from the same directory as the scripts themselves
Test
If you'd like to test the library prior to installation-
-
On Linux-
Use
$ make testto build the library and also the test file included intest_dir/. This DOES NOT install the built libraryYou'll notice an output executable file in the current directory named
test. Link this file to your browser extension. -
On Windows-
Open
test_src\tester.slnusing VS, change build config to win32 and preferably debug but release works too. HitStart(akaBuild and Run). This DOES NOT install the built libraryNOTE: You've to build the actual library (not install) before using
tester.slnYou can find docs about how to link the native host here for firefox and here for chrome.
The test file also relies on connectionless messaging.
API - How to use
PRE
-
On Linux- Firstly, you'll need to include the header file-
#include<nativemsg.h>Finally, remember to link the library during compilation. Use the flag
-lnativemsgfor this -
On Windows- You'll have to explicitly tell visual studio where to look for header files and you'll also have to link the directory
For the header file, right click on your project and go to properties -> C/C++ -> Additional Include Directories -> Now browse to the location of your
$INSTALL_DIRand select theincludefolder insideNow you can include the header file in your source-
#include "nativemsg.h"For the library itself, again go to properties -> Linker -> Additional Dependencies -> Now browse to the location of your
$INSTALL_DIRagain and select thelibfolder insideAlmost done, we now have to link the library itself. To do this you can either include this preprocessor statement in your source file
#pragma comment(lib, "nativemsg")Or you can go to properties -> Linker -> Input -> Additional Dependencies -> Type in
nativemsg.lib
uint8_t* nativemsg_read(uint32_t* length)
Reads the message sent the by browser extension and returns a uint8_t pointer to it
The pointer should be freed using free()
param: length - address of a uint32_t variable, to store the length of returned buffer
Returns NULL on failure
The corresponding error is written to stderr
Usage: uint8_t* msg = nativemsg_read(&len);
Where len is a variable of type uint32_t
NOTE: The returned pointer does not include a null terminator
size_t nativemsg_write(const uint8_t* const buffer, uint32_t length)
Writes given message for the browser extension to read
@param: buffer - pointer to the array of bytes to be written
@param: length - length of the array - should not be more than (1024 * 1024)
Returns number of bytes written to stdout
The number of bytes written is always length + 4 on success
The 4 extra bytes being the int variable: length
Returns 0 on failure
The corresponding error is written to stderr
Usage: size_t count = nativemsg_write((uint8_t*)"{\"msg\":\"pong\"}", 14);
This should return 14 + 4 = 18 on success
NOTE: The string "{\"msg\":\"pong\"}" is actually 15 bytes, the null terminator in the end must not be written. Otherwise it'll crash the extension.
Examples - Native Host
Assume the sent message is the JSON - {text: "ping"}
-
Connectionless messaging
uint32_t len; // Read the message uint8_t* msg = NULL; msg = nativemsg_read(&len); if (msg == NULL) { exit(EXIT_FAILURE); } // Assign a null terminator in the end to make it usable as a string msg[len] = '\0'; if (!strcmp((char*)msg, "{\"text\":\"ping\"}")) { // Message was 'ping', wrappned inside a json // Valid free(msg); if (nativemsg_write((uint8_t*)"{\"msg\":\"pong\"}", 14) != 18) { // Wrong number of bytes written - not (14 + 4) exit(EXIT_FAILURE); } } -
Connectionful messaging
uint32_t len; // Read the message uint8_t* msg = NULL; while(1) { msg = nativemsg_read(&len); if (msg == NULL) { exit(EXIT_FAILURE); } // Assign a null terminator in the end to make it usable as a string msg[len] = '\0'; if (!strcmp((char*)msg, "{\"text\":\"ping\"}")) { // Message was 'ping', wrappned inside a json // Valid free(msg); if (nativemsg_write((uint8_t*)"{\"msg\":\"pong\"}", 14) != 18) { // Wrong number of bytes written - not (14 + 4) exit(EXIT_FAILURE); } } }
Both of the above examples, expect a json {text: "ping"} from the extension and send {msg: "pong"} to the extension.
Examples - Browser Extension
The below examples are meant from chrome but there's not a major difference between the firefox version and this one
They both also send the same json mentioned above: {text: "ping"} and expect the json: {msg: "pong"}
The native host app is named pingpong
-
Connectionless Messaging
var start; chrome.browserAction.onClicked.addListener(() => { console.log('Sending: ping') start = performance.now(); chrome.runtime.sendNativeMessage("pingpong", {text: "ping"}, onResponse); }); function onResponse(res) { let end = performance.now(); console.log(`Received: ${res.msg}, Took: ${end - start} ms`); } -
Connectionful Messaging
var start; var port = chrome.runtime.connectNative('pingpong'); port.onMessage.addListener(function(res) { let end = performance.now(); console.log(`Received: ${res.msg}, took: ${(end - start) * 1000} μs`); }); port.onDisconnect.addListener(function() { console.log("Disconnected"); }); chrome.browserAction.onClicked.addListener(() => { console.log("Sending: ping") start = performance.now(); port.postMessage({ text: "ping" }); });