Reputation: 6553
So I've have a working example of Chrome Native Messaging working with C# and it works great sending down to the application and then getting a response back. What I really need to do though, is to be able to call my native application and have it send information to the chrome extension (to load a website url).
The issue is, when I call my exe (console app) with arguments, chrome isn't listening. When I have chrome listening first, it starts up my application and I can no longer send it commands first and if I start it again chrome wasn't connected to that one, so nothing happens.
Ideally I want to call my application like:
nativeapplication.exe viewAccount ABCDEFG
and have my extension that's listening run its viewAccount
method with that argument of ABCDEFG
.
I have it fine working from chrome -> application -> chrome
, but I want to go other application (or command line with arguments) -> application -> chrome
.
Is the only way to do this to make my application act as a wcf service or similar, and have another application send it the data and then send the message to chrome? I would like to avoid having my application sit and read from a file or otherwise use resources while "idle". Is Single Instance with WCF the best option or am I missing something simple?
Note: Examples in languages other than C# are fine, as long as its the native application calling the extension, and not the other way around.
Upvotes: 4
Views: 3227
Reputation: 6553
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();
}
}
Upvotes: 3
Reputation: 77502
You're not missing anything simple: Native Messaging works only by launching a new process, it cannot attach to an existing one.
However, you can spin up an instance and keep it around with connectNative
instead of sendNativeMessage
for a single exchange. Then, that instance will have to listen to some external event happening.
At a very high level, this can indeed be achieved by a single-instance application. I do not have more concrete recommendations for C# though.
Upvotes: 2