C# native host with Chrome Native Messaging
Assuming the manifest is set up properly, here is a complete example for talking to a C# host using the "port" method:
using System;
using System.IO;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace NativeMessagingHost
{
class Program
{
public static void Main(string[] args)
{
JObject data;
while ((data = Read()) != null)
{
var processed = ProcessMessage(data);
Write(processed);
if (processed == "exit")
{
return;
}
}
}
public static string ProcessMessage(JObject data)
{
var message = data["text"].Value<string>();
switch (message)
{
case "test":
return "testing!";
case "exit":
return "exit";
default:
return "echo: " + message;
}
}
public static JObject Read()
{
var stdin = Console.OpenStandardInput();
var length = 0;
var lengthBytes = new byte[4];
stdin.Read(lengthBytes, 0, 4);
length = BitConverter.ToInt32(lengthBytes, 0);
var buffer = new char[length];
using (var reader = new StreamReader(stdin))
{
while (reader.Peek() >= 0)
{
reader.Read(buffer, 0, buffer.Length);
}
}
return (JObject)JsonConvert.DeserializeObject<JObject>(new string(buffer));
}
public static void Write(JToken data)
{
var json = new JObject();
json["data"] = data;
var bytes = System.Text.Encoding.UTF8.GetBytes(json.ToString(Formatting.None));
var stdout = Console.OpenStandardOutput();
stdout.WriteByte((byte)((bytes.Length >> 0) & 0xFF));
stdout.WriteByte((byte)((bytes.Length >> 8) & 0xFF));
stdout.WriteByte((byte)((bytes.Length >> 16) & 0xFF));
stdout.WriteByte((byte)((bytes.Length >> 24) & 0xFF));
stdout.Write(bytes, 0, bytes.Length);
stdout.Flush();
}
}
}
If you don't need to actively communicate with the host, using runtime.sendNativeMessage
will work fine. To prevent the host from hanging, simply remove the while
loop and do Read/Write once.
To test this, I used the example project provided by Google here: https://chromium.googlesource.com/chromium/src/+/master/chrome/common/extensions/docs/examples/api/nativeMessaging
Note: I'm using Json.NET to simplify the json serialization/de-serialization process.
I hope this is helpful to somebody!
C# host with Chrome Native Messaging Extension
Native Messaging works by starting a new application process, then communicating with it over STDIO as long as Chrome keeps the connection open. If you don't keep the connection open, you can't "reconnect" to an application. To keep the connection open, you'll probably need to open it from the event script.
See this question: How to get Chrome Native Messaging to Listen to application? It discusses some of this topic, including C#-specific ways to keep it a single instance.
Furthermore, consider using alternative solutions, such as web communication (for example, WebSockets) to a local port exposed by your native code, though it creates new security questions (how to make sure it's your extension talking to it?).
Native messaging from chrome extension to native host written in C#
In order to get my extension working, I followed the following steps:
- I deleted my registry key and then re-added it in my HKLM registry.
Although unnecessary,
host.exe
really only needed to receive one message from the extension, so I changed my previous "open port" messaging to a "no port" message, i.e.chrome.runtime.sendNativeMessage(...);
Importantly, when I changed the text I was sending from
{"text":data[0]}
to a simple string{"text":"Hello from Chrome!"}
, everything began working smoothly.data[0]
was a string of about 250KB, which should definitely be an acceptable message size according to the developer's site. However, I have created a different question for this problem, as I was able to solve the general message sending and receiving problem I was experiencing.
How to get Chrome Native Messaging to Listen to application?
Well I ended up going with a single instance application, and used code for a SingleInstance
I found somewhere (sorry went through so many sites looking for simplest one)
Ended up using this main class
/// <summary>
/// Holds a list of arguments given to an application at startup.
/// </summary>
public class ArgumentsReceivedEventArgs : EventArgs
{
public string[] Args { get; set; }
}
public class SingleInstance : IDisposable
{
private Mutex _mutex;
private readonly bool _ownsMutex;
private Guid _identifier;
/// <summary>
/// Enforces single instance for an application.
/// </summary>
/// <param name="identifier">An _identifier unique to this application.</param>
public SingleInstance(Guid identifier)
{
this._identifier = identifier;
_mutex = new Mutex(true, identifier.ToString(), out _ownsMutex);
}
/// <summary>
/// Indicates whether this is the first instance of this application.
/// </summary>
public bool IsFirstInstance
{ get { return _ownsMutex; } }
/// <summary>
/// Passes the given arguments to the first running instance of the application.
/// </summary>
/// <param name="arguments">The arguments to pass.</param>
/// <returns>Return true if the operation succeded, false otherwise.</returns>
public bool PassArgumentsToFirstInstance(string[] arguments)
{
if (IsFirstInstance)
throw new InvalidOperationException("This is the first instance.");
try
{
using (var client = new NamedPipeClientStream(_identifier.ToString()))
using (var writer = new StreamWriter(client))
{
client.Connect(200);
foreach (var argument in arguments)
writer.WriteLine(argument);
}
return true;
}
catch (TimeoutException)
{ } //Couldn't connect to server
catch (IOException)
{ } //Pipe was broken
return false;
}
/// <summary>
/// Listens for arguments being passed from successive instances of the applicaiton.
/// </summary>
public void ListenForArgumentsFromSuccessiveInstances()
{
if (!IsFirstInstance)
throw new InvalidOperationException("This is not the first instance.");
ThreadPool.QueueUserWorkItem(ListenForArguments);
}
/// <summary>
/// Listens for arguments on a named pipe.
/// </summary>
/// <param name="state">State object required by WaitCallback delegate.</param>
private void ListenForArguments(object state)
{
try
{
using (var server = new NamedPipeServerStream(_identifier.ToString()))
using (var reader = new StreamReader(server))
{
server.WaitForConnection();
var arguments = new List<string>();
while (server.IsConnected)
arguments.Add(reader.ReadLine());
ThreadPool.QueueUserWorkItem(CallOnArgumentsReceived, arguments.ToArray());
}
}
catch (IOException)
{ } //Pipe was broken
finally
{
ListenForArguments(null);
}
}
/// <summary>
/// Calls the OnArgumentsReceived method casting the state Object to String[].
/// </summary>
/// <param name="state">The arguments to pass.</param>
private void CallOnArgumentsReceived(object state)
{
OnArgumentsReceived((string[])state);
}
/// <summary>
/// Event raised when arguments are received from successive instances.
/// </summary>
public event EventHandler<ArgumentsReceivedEventArgs> ArgumentsReceived;
/// <summary>
/// Fires the ArgumentsReceived event.
/// </summary>
/// <param name="arguments">The arguments to pass with the ArgumentsReceivedEventArgs.</param>
private void OnArgumentsReceived(string[] arguments)
{
if (ArgumentsReceived != null)
ArgumentsReceived(this, new ArgumentsReceivedEventArgs() { Args = arguments });
}
#region IDisposable
private bool disposed = false;
protected virtual void Dispose(bool disposing)
{
if (!disposed)
{
if (_mutex != null && _ownsMutex)
{
_mutex.ReleaseMutex();
_mutex = null;
}
disposed = true;
}
}
~SingleInstance()
{
Dispose(false);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
#endregion
}
And my C# application mostly taken from (C# native host with Chrome Native Messaging):
class Program
{
const string MutexId = "ENTER YOUR GUID HERE, OR READ FROM APP";
public static void Main(string[] args)
{
using (var instance = new SingleInstance(new Guid(MutexId)))
{
if (instance.IsFirstInstance)
{
instance.ArgumentsReceived += Instance_ArgumentsReceived;
instance.ListenForArgumentsFromSuccessiveInstances();
DoMain(args);
}
else
{
instance.PassArgumentsToFirstInstance(args);
}
}
}
private static void Instance_ArgumentsReceived(object sender, ArgumentsReceivedEventArgs e)
{
TryProcessAccount(e.Args);
}
// This is the main part of the program I use, so I can call my exe with program.exe 123 42424 to have it open that specific account in chrome. Replace with whatever code you want to happen when you have multiple instances.
private static void TryProcessAccount(string[] args)
{
if (args == null || args.Length < 2 || args[0] == null || args[1] == null || args[0].Length != 3) { return; }
var openAccountString = GetOpenAccountString(args[0], args[1]);
Write(openAccountString);
}
private static void DoMain(string[] args)
{
TryProcessAccount(args);
JObject data = Read();
while ((data = Read()) != null)
{
if (data != null)
{
var processed = ProcessMessage(data);
Write(processed);
if (processed == "exit")
{
return;
}
}
}
}
public static string GetOpenAccountString(string id, string secondary)
{
return JsonConvert.SerializeObject(new
{
action = "open",
id = id,
secondary = secondary
});
}
public static string ProcessMessage(JObject data)
{
var message = data["message"].Value<string>();
switch (message)
{
case "test":
return "testing!";
case "exit":
return "exit";
case "open":
return GetOpenAccountString("123", "423232");
default:
return message;
}
}
public static JObject Read()
{
var stdin = Console.OpenStandardInput();
var length = 0;
var lengthBytes = new byte[4];
stdin.Read(lengthBytes, 0, 4);
length = BitConverter.ToInt32(lengthBytes, 0);
var buffer = new char[length];
using (var reader = new StreamReader(stdin))
{
while (reader.Peek() >= 0)
{
reader.Read(buffer, 0, buffer.Length);
}
}
return (JObject)JsonConvert.DeserializeObject<JObject>(new string(buffer))["data"];
}
public static void Write(JToken data)
{
var json = new JObject
{
["data"] = data
};
var bytes = System.Text.Encoding.UTF8.GetBytes(json.ToString(Formatting.None));
var stdout = Console.OpenStandardOutput();
stdout.WriteByte((byte)((bytes.Length >> 0) & 0xFF));
stdout.WriteByte((byte)((bytes.Length >> 8) & 0xFF));
stdout.WriteByte((byte)((bytes.Length >> 16) & 0xFF));
stdout.WriteByte((byte)((bytes.Length >> 24) & 0xFF));
stdout.Write(bytes, 0, bytes.Length);
stdout.Flush();
}
}
Related Topics
How to Retrieve the Loaderexception Property
C#7: Underscore ( _ ) & Star ( * ) in Out Variable
How to Detect If a Debugger Is Attached to Another Process from C#
How to Resolve Service for Type While Attempting to Activate
How to Take a Screenshot of a Wpf Control
How to Load Assembly at Runtime and Create Class Instance
How to Effectively Draw on Desktop in C#
A Dependent Property in a Referentialconstraint Is Mapped to a Store-Generated Column
How to Fix "The Connectionstring Property Has Not Been Initialized"
How to Use Shell32 Within a C# Application
Entity Framework 6 Code First Custom Functions
Keep Window on Top and Steal Focus in Winforms
Redirect Console.Writeline from Windows Application to a String
How to "Zip" or "Rotate" a Variable Number of Lists