I have a desktop application written in C# I'd like to make scriptable on C#/VB.
Ideally, the user would open a side pane and write things like
foreach (var item in myApplication.Items)
item.DoSomething();
Having syntax highlighting and code completion would be awesome, but I could live without it.
I would not want to require users to have Visual Studio 2010 installed.
I am thinking about invoking the compiler, loading and running the output assembly.
Is there a better way?
Is Microsoft.CSharp the answer?
Have you thought about IronPython or IronRuby?
Use a scripting language. Tcl, LUA or even JavaScript comes to mind.
Using Tcl is really easy:
using System.Runtime.InteropServices;
using System;
namespace TclWrap {
public class TclAPI {
[DllImport("tcl84.DLL")]
public static extern IntPtr Tcl_CreateInterp();
[DllImport("tcl84.Dll")]
public static extern int Tcl_Eval(IntPtr interp,string skript);
[DllImport("tcl84.Dll")]
public static extern IntPtr Tcl_GetObjResult(IntPtr interp);
[DllImport("tcl84.Dll")]
public static extern string Tcl_GetStringFromObj(IntPtr tclObj,IntPtr length);
}
public class TclInterpreter {
private IntPtr interp;
public TclInterpreter() {
interp = TclAPI.Tcl_CreateInterp();
if (interp == IntPtr.Zero) {
throw new SystemException("can not initialize Tcl interpreter");
}
}
public int evalScript(string script) {
return TclAPI.Tcl_Eval(interp,script);
}
public string Result {
get {
IntPtr obj = TclAPI.Tcl_GetObjResult(interp);
if (obj == IntPtr.Zero) {
return "";
} else {
return TclAPI.Tcl_GetStringFromObj(obj,IntPtr.Zero);
}
}
}
}
}
Then use it like:
TclInterpreter interp = new TclInterpreter();
string result;
if (interp.evalScript("set a 3; {exp $a + 2}")) {
result = interp.Result;
}
I would use PowerShell or MEF. It really depends on what you mean by scritable and what type of application you have. The best part about PowerShell is it's directly hostable and directly designed to use .NET interfaces in a scripting manner.
I had the exact same problem and with a bit of googling and few modifications I solved it using Microsoft.CSharp.CSharpCodeProvider which allows the user to edit a C# template I present to them that exposes the complete Object Model of my application and they can even pass parameters from / and return result to the application itself.
The full C# solution can be downloaded from http://qurancode.com.
But here is the main code that does just that:
using System;
using System.Text;
using System.IO;
using System.Collections.Generic;
using System.Reflection;
using System.CodeDom.Compiler;
using Microsoft.CSharp;
using System.Security;
using Model; // this is my application Model with my own classes
public static class ScriptRunner
{
private static string s_scripts_directory = "Scripts";
static ScriptRunner()
{
if (!Directory.Exists(s_scripts_directory))
{
Directory.CreateDirectory(s_scripts_directory);
}
}
/// <summary>
/// Load a C# script fie
/// </summary>
/// <param name="filename">file to load</param>
/// <returns>file content</returns>
public static string LoadScript(string filename)
{
StringBuilder str = new StringBuilder();
string path = s_scripts_directory + "/" + filename;
if (File.Exists(filename))
{
using (StreamReader reader = File.OpenText(path))
{
string line = "";
while ((line = reader.ReadLine()) != null)
{
str.AppendLine(line);
}
}
}
return str.ToString();
}
/// <summary>
/// Compiles the source_code
/// </summary>
/// <param name="source_code">source_code must implements IScript interface</param>
/// <returns>compiled Assembly</returns>
public static CompilerResults CompileCode(string source_code)
{
CSharpCodeProvider provider = new CSharpCodeProvider();
CompilerParameters options = new CompilerParameters();
options.GenerateExecutable = false; // generate a Class Library assembly
options.GenerateInMemory = true; // so we don;t have to delete it from disk
Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
foreach (Assembly assembly in assemblies)
{
options.ReferencedAssemblies.Add(assembly.Location);
}
return provider.CompileAssemblyFromSource(options, source_code);
}
/// <summary>
/// Execute the IScriptRunner.Run method in the compiled_assembly
/// </summary>
/// <param name="compiled_assembly">compiled assembly</param>
/// <param name="args">method arguments</param>
/// <returns>object returned</returns>
public static object Run(Assembly compiled_assembly, object[] args, PermissionSet permission_set)
{
if (compiled_assembly != null)
{
// security is not implemented yet !NIY
// using Utilties.PrivateStorage was can save but not diaplay in Notepad
// plus the output is saved in C:\Users\<user>\AppData\Local\IsolatedStorage\...
// no contral over where to save make QuranCode unportable applicaton, which is a no no
//// restrict code security
//permission_set.PermitOnly();
foreach (Type type in compiled_assembly.GetExportedTypes())
{
foreach (Type interface_type in type.GetInterfaces())
{
if (interface_type == typeof(IScriptRunner))
{
ConstructorInfo constructor = type.GetConstructor(System.Type.EmptyTypes);
if ((constructor != null) && (constructor.IsPublic))
{
// construct object using default constructor
IScriptRunner obj = constructor.Invoke(null) as IScriptRunner;
if (obj != null)
{
return obj.Run(args);
}
else
{
throw new Exception("Invalid C# code!");
}
}
else
{
throw new Exception("No default constructor was found!");
}
}
else
{
throw new Exception("IScriptRunner is not implemented!");
}
}
}
// revert security restrictions
//CodeAccessPermission.RevertPermitOnly();
}
return null;
}
/// <summary>
/// Execute a public static method_name(args) in compiled_assembly
/// </summary>
/// <param name="compiled_assembly">compiled assembly</param>
/// <param name="methode_name">method to execute</param>
/// <param name="args">method arguments</param>
/// <returns>method execution result</returns>
public static object ExecuteStaticMethod(Assembly compiled_assembly, string methode_name, object[] args)
{
if (compiled_assembly != null)
{
foreach (Type type in compiled_assembly.GetTypes())
{
foreach (MethodInfo method in type.GetMethods())
{
if (method.Name == methode_name)
{
if ((method != null) && (method.IsPublic) && (method.IsStatic))
{
return method.Invoke(null, args);
}
else
{
throw new Exception("Cannot invoke method :" + methode_name);
}
}
}
}
}
return null;
}
/// <summary>
/// Execute a public method_name(args) in compiled_assembly
/// </summary>
/// <param name="compiled_assembly">compiled assembly</param>
/// <param name="methode_name">method to execute</param>
/// <param name="args">method arguments</param>
/// <returns>method execution result</returns>
public static object ExecuteInstanceMethod(Assembly compiled_assembly, string methode_name, object[] args)
{
if (compiled_assembly != null)
{
foreach (Type type in compiled_assembly.GetTypes())
{
foreach (MethodInfo method in type.GetMethods())
{
if (method.Name == methode_name)
{
if ((method != null) && (method.IsPublic))
{
object obj = Activator.CreateInstance(type, null);
return method.Invoke(obj, args);
}
else
{
throw new Exception("Cannot invoke method :" + methode_name);
}
}
}
}
}
return null;
}
}
I then defined a C# Interface to be implemented by the user code where they are free to put anythng they like inside their concrete Run method:
/// <summary>
/// Generic method runner takes any number and type of args and return any type
/// </summary>
public interface IScriptRunner
{
object Run(object[] args);
}
And here is the startup template the user can extends:
using System;
using System.Collections.Generic;
using System.Windows.Forms;
using System.Text;
using System.IO;
using Model;
public class MyScript : IScriptRunner
{
private string m_scripts_directory = "Scripts";
/// <summary>
/// Run implements IScriptRunner interface
/// to be invoked by QuranCode application
/// with Client, current Selection.Verses, and extra data
/// </summary>
/// <param name="args">any number and type of arguments</param>
/// <returns>return any type</returns>
public object Run(object[] args)
{
try
{
if (args.Length == 3) // ScriptMethod(Client, List<Verse>, string)
{
Client client = args[0] as Client;
List<Verse> verses = args[1] as List<Verse>;
string extra = args[2].ToString();
if ((client != null) && (verses != null))
{
return MyMethod(client, verses, extra);
}
}
return null;
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, Application.ProductName);
return null;
}
}
/// <summary>
/// Write your C# script insde this method.
/// Don't change its name or parameters
/// </summary>
/// <param name="client">Client object holding a reference to the currently selected Book object in TextMode (eg Simplified29)</param>
/// <param name="verses">Verses of the currently selected Chapter/Page/Station/Part/Group/Quarter/Bowing part of the Book</param>
/// <param name="extra">any user parameter in the TextBox next to the EXE button (ex Frequency, LettersToJump, DigitSum target, etc)</param>
/// <returns>true to disply back in QuranCode matching verses. false to keep script window open</returns>
private long MyMethod(Client client, List<Verse> verses, string extra)
{
if (client == null) return false;
if (verses == null) return false;
if (verses.Count == 0) return false;
int target;
if (extra == "")
{
target = 0;
}
else
{
if (!int.TryParse(extra, out target))
{
return false;
}
}
try
{
long total_value = 0L;
foreach (Verse verse in verses)
{
total_value += Client.CalculateValue(verse.Text);
}
return total_value;
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, Application.ProductName);
return 0L;
}
}
}
And this is how I call it from my MainForm.cs
#region Usage from MainForm
if (!ScriptTextBox.Visible)
{
ScriptTextBox.Text = ScriptRunner.LoadScript(#"Scripts\Template.cs");
ScriptTextBox.Visible = true;
}
else // if visible
{
string source_code = ScriptTextBox.Text;
if (source_code.Length > 0)
{
Assembly compiled_assembly = ScriptRunner.CompileCode(source_code);
if (compiled_assembly != null)
{
object[] args = new object[] { m_client, m_client.Selection.Verses, "19" };
object result = ScriptRunner.Run(compiled_assembly, args);
// process result here
}
}
ScriptTextBox.Visible = false;
}
#endregion
Still to do is the Syntax Highlighting and CodeCompletion though.
Good luck!
You will invoke the compiler anyway, because C# is a compiled language. The best way to do it can be checked in CSharpCodeProvider - класс.
You can use the following open source solution as an example: https://bitbucket.org/jlyonsmith/coderunner/wiki/Home
What language is your application written in? If C++, you might consider Google V8, an embeddable ECMAScript/JavaScript engine.
Related
I'm still really new to coding and trying to make my first WPF application. I have been trying to implement sqlite for the past like 3 days now.
I am using VS 2019. I have downloaded the SQLite Toolbox and am following the instructions here - https://github.com/ErikEJ/SqlCeToolbox/wiki/EF6-workflow-with-SQLite-DDEX-provider. I did a full installation. Was I supposed to install it in my project directory? Because now I just have a bunch of files and nothing seems to have changed in Studio. I tried using Install.exe, but it returned a "confirm option not enabled" error. Looking at a similar question, I tried putting the files in an external folder in my project, and then installed the System.Data.SQLite.EF6.dll to my GAC using the VS Dev Console. The toolbox doesn't see any changes, and does not recognize the dll and I'm having a hard time finding reliable information for my version. Thanks for any help getting pointed in the right direction!
I'm not sure why you mention toolbox as far as I'm aware you access the SQLite functionality programmatically.
The NuGet packages that I recently used for a Xamarin project (SQLite-net-pcl by Frank Krueger and supporting libraries) allowed me to use pretty simple object mapping without recourse to SQL like strings.
I use a lot of interfaces in my code, but here's my all access database class
:
public class AllAccessDataTableBaseSqLite<T> : IDataAccessRead<T>, IDataAccessWrite<T>,IDataAccessExpressionSearch<T>, IDataAccessDelete<T> where T: IDataRecord, new()
{
//Note that this static value will only apply to those classes based on the same generic type e.g. all DataTableBase<User> instances etc.
public static SQLiteAsyncConnection DBConnection;
/// <summary>
/// Lock object to prevent multi-thread interruption of code segment.
/// </summary>
public static readonly object CollisionLock = new object();
/// <summary>
/// Constructor
/// </summary>
public AllAccessDataTableBaseSqLite()
{
lock (CollisionLock)
{
if (DBConnection != null)
{
DBConnection.CreateTableAsync<T>().Wait();
return;
}
try
{
string directory;
if (DeviceInfo.Platform != DevicePlatform.Unknown)
{
directory = FileSystem.AppDataDirectory;
}
else
{
directory = "DataStore";
var directoryInfo = Directory.CreateDirectory(directory);
directory = directoryInfo.FullName;
}
var path = Path.Combine(directory, $"{typeof(T).Name}.db");
if (!File.Exists(path))
{
using var fileStream = File.Create(path);
fileStream.Close();
}
DBConnection = new SQLiteAsyncConnection(path);
DBConnection.CreateTableAsync<T>().Wait();
}
catch (Exception ex)
{
if (ex is UnauthorizedAccessException)
{
}
}
}
}
/// <summary>
/// Create the data table
/// </summary>
/// <returns></returns>
public async Task<CreateTableResult> CreateTableAsync()
{
if (DBConnection != null)
{
return await DBConnection.CreateTableAsync<T>();
}
return CreateTableResult.Migrated;
}
/// <summary>
/// Create a new record entry
/// </summary>
/// <param name="entity">Data entity to enter</param>
/// <param name="user">Current User information</param>
/// <returns>New entry record if successful</returns>
public async Task<T> CreateAsync(T entity, IUserRecord user)
{
if (entity == null)
{
return default(T);
}
if (DBConnection == null)
{
return default(T);
}
entity.CreatedDate = DateTime.UtcNow;
entity.CreatedByUserId = user.Id;
entity.Id = 0;
try
{
await DBConnection.InsertAsync(entity);
}
catch (SQLiteException e)
{
if (e.Message == "Constraint")
{
throw new InvalidConstraintException(e.Message, e.InnerException);
}
}
var result = entity;
return result;
}
/// <summary>
/// Update a collection of new entities of type T to the data table.
/// All entities should be present within the data table
/// </summary>
/// <param name="entityList">Entity collection</param>
/// <param name="user">user making the change</param>
/// <returns>ID of entities successfully updated or added</returns>
public async Task<int> UpdateAllAsync(IEnumerable<T> entityList, IUserRecord user)
{
var result = 0;
foreach (var t in entityList)
{
if (null != await UpdateAsync(t, user))
{
result++ ;
}
}
return result;
}
/// <summary>
/// Obtain the data record with the given Id
/// </summary>
/// <param name="id">Id value to select the record by</param>
/// <returns>A valid record if found otherwise null</returns>
public async Task<T> GetById(int id)
{
if (DBConnection == null)
{
return default(T);
}
return await DBConnection.Table<T>().Where(i => i.Id == id).FirstOrDefaultAsync();
}
/// <summary>
/// This function returns all database entries that are not marked deleted or changed
/// Warning: The data set may be very large
/// </summary>
/// <returns>A list of entries</returns>
public async Task<List<T>> GetAll()
{
if (DBConnection != null)
{
return await DBConnection.Table<T>().Where(x=>x.ChangedDate==default && x.DeletedDate==default)
.ToListAsync();
}
return new List<T>();
}
/// <inheritdoc />
public async Task<List<T>> GetAllHistoric() => await DBConnection.Table<T>().ToListAsync();
/// <summary>
/// This function is used to update the supplied record entry within the database.
/// If the supplied record does not have a non-zero value Id field it is assumed to be a
/// new record to be inserted into the database.
/// </summary>
/// <param name="entity">Record to update</param>
/// <param name="user">User performing the action</param>
/// <returns></returns>
public async Task<T> UpdateAsync(T entity, IUserRecord user)
{
if (DBConnection == null)
{
return default(T);
}
if (entity == null)
{
return default(T);
}
var newRecord = (T) ((entity) as BaseRecord<T>)?.Clone();
if (null == newRecord)
{
return default(T);
}
//if Id is zero assume that the record is new and to be added
if (newRecord.Id == 0)
{
if (user != null)
{
newRecord.CreatedByUserId = user.Id;
}
newRecord.CreatedDate = DateTime.UtcNow;
newRecord.Id = await DBConnection.InsertAsync(newRecord);
return newRecord;
}
// Id is not zero and thus a new record should be created linked to the old record.
var oldRecord = await GetById(newRecord.Id);
oldRecord.ChangedDate = DateTime.UtcNow;
if (user != null)
{
oldRecord.ChangedByUserId = user.Id;
}
try
{
var result = await DBConnection.UpdateAsync(oldRecord);
}
catch (Exception e)
{
Debug.WriteLine($"UpdateAsync {e.Message}");
}
newRecord.PreviousRecordId = oldRecord.Id;
newRecord.Id = 0;
return await CreateAsync(newRecord, user);
}
/// <inheritdoc />
public async Task<int> DeleteAsync(T entity)
{
if (DBConnection == null)
{
return -1;
}
return await DBConnection.DeleteAsync(entity);
}
/// <inheritdoc />
public async Task DeleteAll()
{
await DBConnection.DropTableAsync<T>();
await CreateTableAsync();
}
/// <inheritdoc />
public async Task<PagedResult<T>> GetAllPagedResult(int recordId, uint maxResults = 100)
{
if (DBConnection == null)
{
return null;
}
List<T> list;
if (maxResults == 0)
{
list = await GetAll();
}
else
{
list = await DBConnection.Table<T>().Where(x => (x.Id >= recordId && x.ChangedDate == default && x.DeletedDate == default)).ToListAsync();
if (list.Count() > maxResults)
{
list = list.GetRange(0, (int) maxResults);
}
}
return new PagedResult<T>(list, list.Count());
}
/// <inheritdoc />
public async Task<IEnumerable<T>> FindAsyncOrdered<TValue>(Expression<Func<T, bool>> predicate = null,
Expression<Func<T, TValue>> orderBy = null)
{
var query = DBConnection.Table<T>();
if (predicate != null)
{
query = query.Where(predicate);
}
if (orderBy != null)
{
query = query.OrderBy<TValue>(orderBy);
}
return await query.ToListAsync();
}
/// <inheritdoc />
public async Task<T> FindFirst(Expression<Func<T, bool>> predicate) => await DBConnection.FindAsync(predicate);
}
I'm using data classes based upon:
public interface IDataRecord
{
/// <summary>
/// Identifier for record
/// </summary>
int Id { get; set; }
/// <summary>
/// Link to previous version of record
/// </summary>
int PreviousRecordId { get; set; }
/// <summary>
/// User Identity that made the change
/// </summary>
int ChangedByUserId { get; set; }
/// <summary>
/// Date when the data record was last changed
/// </summary>
DateTime ChangedDate { get; set; }
/// <summary>
/// Identity of User that deleted the record
/// </summary>
int DeletedByUserId { get; set; }
/// <summary>
/// Date when the data record was deleted
/// </summary>
DateTime DeletedDate { get; set; }
/// <summary>
/// Identity of User that created the record
/// </summary>
int CreatedByUserId { get; set; }
/// <summary>
/// Date when the data record was added
/// </summary>
DateTime CreatedDate { get; set; }
object Clone();
}
Obviously you don't have to use this, but put simply for my application implementation each type of data record is stored in its own data file (thus 1 table per file) and this is created at the beginning in the constructor.
The SQLite db connection is created using the data file path.
Table is created using the dbconnection
edit
I've had a closer look at your code.
Points to note are:
You don't appear to be creating a table.
Access to non-app folders is restricted if you chose to create a UWP rather than base WPF project - be aware of folder access permissions when running apps especially in release mode.
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.
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();
}
}
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.
I am keep getting
System.Reflection.TargetInvocationException
and PresentationFramework.dll, additional info Exception has been thrown by the target of an invocation.
Can someone please help me out here?
Info:
Call Stack
PresentationFramework.dll!System.Windows.Markup.WpfXamlLoader.Load(System.Xaml.XamlReader xamlReader, System.Xaml.IXamlObjectWriterFactory writerFactory, bool skipJournaledProperties, object rootObject, System.Xaml.XamlObjectWriterSettings settings, System.Uri baseUri) Unknown
namespace PMD.Analysis.AnalysisViewModel
{
using PMD.Measurement.MeasurementModel;
using System.Windows.Data;
using PMD.Analysis.AnalysisModel;
using System;
using System.Collections.Generic;
using PMD.Measurement.MeasurementViewModel;
public class AnalysisViewModel : ViewModel
{
/// <summary>
/// New analysis command.
/// </summary>
private ICommand newAnalysis = null;
public PMD.Analysis.AnalysisViewModel.NewAnalysisViewModel m_NewAnalysisViewModel;
Measurement measurement = new Measurement();
private ICollectionView measurements = null;
/// <summary>
/// Measurement's search by title field.
/// </summary>
private string searchTitle;
/// <summary>
/// Measurement's search by title field.
/// </summary>
private string searchTester;
/// <summary>
/// Measurement's search by vehicle VIN field.
/// </summary>
private string searchVehicleVIN;
public MeasurementModel MeasurementModel
{
get;
set;
}
public enum SelectedState
{
// No Masurements.
Inactive,
// Masurements.
Active,
// Waiting for Masurements.
WaitingAnswer
};
public SelectedState CurrentSelectedState { get; set; }
public Analysis Analysis
{
get;
set;
}
public AnalysisViewModel()
{
Analysis = new Analysis();
measurements = new ListCollectionView(MeasurementModel.Measurements);
measurements.Filter = new Predicate<object>(SearchCallbackAnalysis);
}
~AnalysisViewModel()
{
}
/// <summary>
/// List of measurements that will be displayed in analysis view.
/// </summary>
public ICollectionView Measurements
{
get { return measurements; }
set { measurements = value; }
}
/// <summary>
/// Gets or sets new analysis command.
/// </summary>
public ICommand NewAnalysis
{
get
{
if (newAnalysis == null)
newAnalysis = new NewAnalysisCommand(this);
return newAnalysis;
}
}
public bool SearchCallbackAnalysis(object item)
{
bool isItemShowed = true;
if ((searchTitle != "") && (searchTitle != null))
isItemShowed &= (((Measurement)item).Title == searchTitle);
if ((searchVehicleVIN != "") && (searchVehicleVIN != null))
isItemShowed &= (((Measurement)item).Vehicle.VehicleVIN == searchVehicleVIN);
if ((SearchTester != "") && (SearchTester != null))
isItemShowed &= (((Measurement)item).Tester == SearchTester);
return isItemShowed;
}
/// <summary>
/// Gets or sets measurement's search by title field.
/// </summary>
public string SearchTitle
{
get
{
return searchTitle;
}
set
{
searchTitle = value;
Measurements.Refresh();
}
}
/// <summary>
/// Gets or sets measurement's search by tester name field.
/// </summary>
public string SearchTester
{
get
{
return searchTester;
}
set
{
searchTester = value;
Measurements.Refresh();
}
}
/// <summary>
/// Gets or sets measurement's search by vehicle VIN field.
/// </summary>
public string SearchVehicleVIN
{
get
{
return searchVehicleVIN;
}
set
{
searchVehicleVIN = value;
Measurements.Refresh();
}
}
}//end AnalysisViewModel
}//end namespace AnalysisViewModel
if i comment in constructor this line of code:
measurements.Filter = new Predicate<object>(SearchCallbackAnalysis);
Everything works fine but i need this line to search in the list.
Additional info:
xamlReader Cannot obtain value of local or argument 'xamlReader' as it is not available at this instruction pointer, possibly because it has been optimized away. System.Xaml.XamlReader
writerFactory Cannot obtain value of local or argument 'writerFactory' as it is not available at this instruction pointer, possibly because it has been optimized away. System.Xaml.IXamlObjectWriterFactory
skipJournaledProperties Cannot obtain value of local or argument 'skipJournaledProperties' as it is not available at this instruction pointer, possibly because it has been optimized away. bool
rootObject Cannot obtain value of local or argument 'rootObject' as it is not available at this instruction pointer, possibly because it has been optimized away. object
settings Cannot obtain value of local or argument 'settings' as it is not available at this instruction pointer, possibly because it has been optimized away. System.Xaml.XamlObjectWriterSettings
baseUri Cannot obtain value of local or argument 'baseUri' as it is not available at this instruction pointer, possibly because it has been optimized away. System.Uri
i have try:
public ICollectionView Measurements
{
get { return measurements; }
set { measurements = value;
measurements.Filter = new Predicate<object>(SearchCallbackAnalysis);
}
}
Now everything works fine. Thank you for try to help me.
I have been reading on how to compare a list with one annother. I have tried to implement the IEquatable interface. Here is what i have done so far:
/// <summary>
/// A object holder that contains a service and its current failcount
/// </summary>
public class ServiceHolder : IEquatable<ServiceHolder>
{
/// <summary>
/// Constructor
/// </summary>
/// <param name="service"></param>
public ServiceHolder(Service service)
{
Service = service;
CurrentFailCount = 0;
}
public Service Service { get; set; }
public UInt16 CurrentFailCount { get; set; }
/// <summary>
/// Public equal method
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
public override bool Equals(object obj)
{
if (obj == null)
{
return false;
}
ServiceHolder tmp = obj as ServiceHolder;
if (tmp == null)
{
return false;
}
else
{
return Equals(tmp);
}
}
/// <summary>
/// Checks the internal components compared to one annother
/// </summary>
/// <param name="serviceHolder"></param>
/// <returns>tru eif they are the same else false</returns>
public bool Equals(ServiceHolder serviceHolder)
{
if (serviceHolder == null)
{
return false;
}
if (this.Service.Id == serviceHolder.Service.Id)
{
if (this.Service.IpAddress == serviceHolder.Service.IpAddress)
{
if (this.Service.Port == serviceHolder.Service.Port)
{
if (this.Service.PollInterval == serviceHolder.Service.PollInterval)
{
if (this.Service.ServiceType == serviceHolder.Service.ServiceType)
{
if (this.Service.Location == serviceHolder.Service.Location)
{
if (this.Service.Name == this.Service.Name)
{
return true;
}
}
}
}
}
}
}
return false;
}
}
and this is where I use it:
private void CheckIfServicesHaveChangedEvent()
{
IList<ServiceHolder> tmp;
using (var db = new EFServiceRepository())
{
tmp = GetServiceHolders(db.GetAll());
}
if (tmp.Equals(Services))
{
StateChanged = true;
}
else
{
StateChanged = false;
}
}
Now when I debug and I put a break point in the equals function it never gets hit.
This leads me to think I have implemented it incorrectly or Im not calling it correctly?
If you want to compare the contents of two lists then the best method is SequenceEqual.
if (tmp.SequenceEquals(Services))
This will compare the contents of both lists using equality semantics on the values in the list. In this case the element type is ServiceHolder and as you've already defined equality semantics for this type it should work just fine
EDIT
OP commented that order of the collections shouldn't matter. For that scenario you can do the following
if (!tmp.Except(Services).Any())
You can compare lists without the order most easily with linq.
List<ServiceHolder> result = tmp.Except(Services).ToList();
I have a following code:
public class Temp<T, TMetadata>
{
[ImportMany]
private IEnumerable<Lazy<T, TMetadata>> plugins;
public Temp(string path)
{
AggregateCatalog aggregateCatalog = new AggregateCatalog();
aggregateCatalog.Catalogs.Add(new DirectoryCatalog(path));
CompositionContainer container = new CompositionContainer(aggregateCatalog);
container.ComposeParts(this);
}
public T GetPlugin(Predicate<TMetadata> predicate)
{
Lazy<T, TMetadata> pluginInfo;
try
{
pluginInfo = plugins.SingleOrDefault(p => predicate(p.Metadata));
}
catch
{
// throw some exception
}
if (pluginInfo == null)
{
// throw some exception
}
return Clone(pluginInfo.Value); // -> this produces errors
}
}
I have a single object of Temp and I call GetPlugin() from multiple threads. Sometimes I face strange composition errors, which I didn't find a way to reproduce. For example:
"System.InvalidOperationException: Stack empty.
at System.Collections.Generic.Stack`1.Pop()
at System.ComponentModel.Composition.Hosting.ImportEngine.TrySatisfyImports(PartManager partManager, ComposablePart part, Boolean shouldTrackImports)
at System.ComponentModel.Composition.Hosting.ImportEngine.SatisfyImports(ComposablePart part)
at System.ComponentModel.Composition.Hosting.CompositionServices.GetExportedValueFromComposedPart(ImportEngine engine, ComposablePart part, ExportDefinition definition)
at System.ComponentModel.Composition.Hosting.CatalogExportProvider.GetExportedValue(CatalogPart part, ExportDefinition export, Boolean isSharedPart)
at System.ComponentModel.Composition.ExportServices.GetCastedExportedValue[T](Export export)
at System.Lazy`1.CreateValue()
at System.Lazy`1.LazyInitValue()
at Temp`2.GetPlugin(Predicate`1 predicate)..."
What could be a reason and how to cure this code?
The CompositionContainer class has a little-known constructor which accepts an isThreadSafe parameter (which defaults to false for performance reasons). If you'll create your container with this value set to true, I believe your problem will be solved:
CompositionContainer container = new CompositionContainer(aggregateCatalog, true);
On a side note, unrelated to the original question, instead of calling Clone() on the plugin, you can use an export factory instead - this way you don't have to implement your own clone method, as MEF will create a new instance for you.
If you want to get a list of available Exports for a matching Import type, you don't need to use the (problematic) container.ComposeParts(this);
You can do something more like:
var pluginsAvailable = container.GetExports<T>().Select(y => y.Value).ToArray();
And that will give you an array of available instances, without all the threading issues that plague MEF.
I've been working on something like this today... please excuse the code dump:
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading;
namespace PluginWatcher
{
/// <summary>
/// Watch for changes to a plugin directory for a specific MEF Import type.
/// <para>Keeps a list of last seen exports and exposes a change event</para>
/// </summary>
/// <typeparam name="T">Plugin type. Plugins should contain classes implementing this type and decorated with [Export(typeof(...))]</typeparam>
public interface IPluginWatcher<T> : IDisposable
{
/// <summary>
/// Available Exports matching type <typeparamref name="T"/> have changed
/// </summary>
event EventHandler<PluginsChangedEventArgs<T>> PluginsChanged;
/// <summary>
/// Last known Exports matching type <typeparamref name="T"/>.
/// </summary>
IEnumerable<T> CurrentlyAvailable { get; }
}
/// <summary>
/// Event arguments relating to a change in available MEF Export types.
/// </summary>
public class PluginsChangedEventArgs<T>: EventArgs
{
/// <summary>
/// Last known Exports matching type <typeparamref name="T"/>.
/// </summary>
public IEnumerable<T> AvailablePlugins { get; set; }
}
/// <summary>
/// Watch for changes to a plugin directory for a specific MEF Import type.
/// <para>Keeps a list of last seen exports and exposes a change event</para>
/// </summary>
/// <typeparam name="T">Plugin type. Plugins should contain classes implementing this type and decorated with [Export(typeof(...))]</typeparam>
public class PluginWatcher<T> : IPluginWatcher<T>
{
private readonly object _compositionLock = new object();
private FileSystemWatcher _fsw;
private DirectoryCatalog _pluginCatalog;
private CompositionContainer _container;
private AssemblyCatalog _localCatalog;
private AggregateCatalog _catalog;
public event EventHandler<PluginsChangedEventArgs<T>> PluginsChanged;
protected virtual void OnPluginsChanged()
{
var handler = PluginsChanged;
if (handler != null) handler(this, new PluginsChangedEventArgs<T> { AvailablePlugins = CurrentlyAvailable });
}
public PluginWatcher(string pluginDirectory)
{
if (!Directory.Exists(pluginDirectory)) throw new Exception("Can't watch \"" + pluginDirectory + "\", might not exist or not enough permissions");
CurrentlyAvailable = new T[0];
_fsw = new FileSystemWatcher(pluginDirectory, "*.dll");
SetupFileWatcher();
try
{
_pluginCatalog = new DirectoryCatalog(pluginDirectory);
_localCatalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());
_catalog = new AggregateCatalog();
_catalog.Catalogs.Add(_localCatalog);
_catalog.Catalogs.Add(_pluginCatalog);
_container = new CompositionContainer(_catalog, false);
_container.ExportsChanged += ExportsChanged;
}
catch
{
Dispose(true);
throw;
}
ReadLoadedPlugins();
}
private void SetupFileWatcher()
{
_fsw.NotifyFilter = NotifyFilters.Attributes | NotifyFilters.CreationTime | NotifyFilters.FileName |
NotifyFilters.LastAccess | NotifyFilters.LastWrite | NotifyFilters.Size | NotifyFilters.Security;
_fsw.Changed += FileAddedOrRemoved;
_fsw.Created += FileAddedOrRemoved;
_fsw.Deleted += FileAddedOrRemoved;
_fsw.Renamed += FileRenamed;
_fsw.EnableRaisingEvents = true;
}
private void ExportsChanged(object sender, ExportsChangeEventArgs e)
{
lock (_compositionLock)
{
if (e.AddedExports.Any() || e.RemovedExports.Any()) ReadLoadedPlugins();
}
}
private void ReadLoadedPlugins()
{
CurrentlyAvailable = _container.GetExports<T>().Select(y => y.Value).ToArray();
OnPluginsChanged();
}
private void FileRenamed(object sender, RenamedEventArgs e)
{
RefreshPlugins();
}
void FileAddedOrRemoved(object sender, FileSystemEventArgs e)
{
RefreshPlugins();
}
private void RefreshPlugins()
{
try
{
var cat = _pluginCatalog;
if (cat == null) { return; }
lock (_compositionLock)
{
cat.Refresh();
}
}
catch (ChangeRejectedException rejex)
{
Console.WriteLine("Could not update plugins: " + rejex.Message);
}
}
public IEnumerable<T> CurrentlyAvailable { get; protected set; }
~PluginWatcher()
{
Dispose(true);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected void Dispose(bool disposing)
{
if (!disposing) return;
var fsw = Interlocked.Exchange(ref _fsw, null);
if (fsw != null) fsw.Dispose();
var plg = Interlocked.Exchange(ref _pluginCatalog, null);
if (plg != null) plg.Dispose();
var con = Interlocked.Exchange(ref _container, null);
if (con != null) con.Dispose();
var loc = Interlocked.Exchange(ref _localCatalog, null);
if (loc != null) loc.Dispose();
var cat = Interlocked.Exchange(ref _catalog, null);
if (cat != null) cat.Dispose();
}
}
}