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 = clang
The install rule also installs the library and the header file in
/usr/local
. If you'd like to install it globally, useexport INSTALL_DIR = /usr
Makefile
Use
$ make build
to build the static library, this library is then put insidebuild/
with the namelibnativemsg.a
Use
$ make install
to install this library under/usr/local/lib
(by default) and also copy over the header file ininclude/
to/usr/local/include
(by default) (Note: usingsudo
may be required)Use
$ make uninstall
to remove the library and the header file from/usr/local
(by default) (Note: usingsudo
may be required)Use
$ make clean
to 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.sln
using your VS and hitbuild
in the solution context menu (right click on solution). Make sure your build config is set to Release and win32The output
nativemsg.lib
file 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_DIR
to 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 test
to 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.sln
using 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.sln
You 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
-lnativemsg
for 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_DIR
and select theinclude
folder 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_DIR
again and select thelib
folder 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" }); });