I have custom CMSModuleLoader code in web application project for Kentico CMS 8.2
I added this code in Old_App_Code\CMS folder which added a function in Global.asax file with the following code
private static void EnsureDynamicModules(object sender, EventArgs e)
{
ModuleEntryManager.EnsureModule<CMSModuleLoader>();
var discovery = new ModuleDiscovery();
var assembly = typeof(CMSModuleLoader).Assembly;
foreach (var module in discovery.GetModules(assembly))
{
ModuleEntryManager.EnsureModule(module);
}
}
This code is throwing error message:
The type 'CMSModuleLoader' cannot be used as type parameter 'T' in the generic type or method 'CMS.Core.ModuleEntryManager.EnsureModule()'. There is no implicit reference conversion from 'CMSModuleLoader' to 'CMS.Core.ModuleEntry'
The code is listed below
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Threading;
using System.Web;
using Aon.Exchange.Core;
using Aon.Exchange.Core.Exceptions;
using Aon.Exchange.Facade.Interfaces;
using Aon.Exchange.Domain.Entities;
using Aon.Exchange.Web.DependencyResolution;
using Common.Logging;
using System.Diagnostics;
using Aon.Exchange.Core.Extensions;
using Aon.Exchange.Core.Common.Logging;
using System.Collections.Specialized;
using CMS.OutputFilter;
using CMS.Membership;
using CMS.Base;
using CMS.Helpers;
/// <summary>
/// Module loader class, ensures initialization of other modules through this partial class
/// </summary>
public partial class CMSModuleLoader : CMSModuleLoaderBase
{
/// <summary>
/// Constructor
/// </summary>
public CMSModuleLoader()
: base("CMSModuleLoader")
{
}
private class CustomSecurityEventsAttribute : CMS.Base.CMSLoaderAttribute
{
/// <summary>
/// Called automatically when the application starts
/// </summary>
public override void Init()
{
SecurityEvents.Authenticate.Execute += new EventHandler<AuthenticationEventArgs>(Authenticate_Execute);
}
/// <summary>
/// called on every kentico authenticate attempt
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Authenticate_Execute(object sender, AuthenticationEventArgs args)
{
if (args.User != null) //the authenticate was successful
{
try
{
var accountFacade = WebContainer.Instance.Container.GetInstance<IAccountFacade>();
accountFacade.ReconcileOnLogin(args.UserName);
}
catch (Exception e)
{
var logger = LogManager.GetCurrentClassLogger();
var ex = new Exception("IAccountFacade.ReconcileOnLogin method throw an error communicating with dynamics, the issue is not resolvable from Kentico thus regardless of the permission level of the current user, the exception will be bubbled up and the user will be shown error details or the custom error page.", e);
logger.Fatal(x => x("The current exception is caused by dynamics/data problems and the user will not be allowed to login. A system admin with access to dynamics is required to resolve the problem.", e));
throw ex;
}
ResetPasswordAttempts(args.User);
}
}
/// <summary>
/// Resets the number of password attempts for a user. We have to pass the userinfo object thru the layers because
/// doing a get, update, change from to the same userinfo object that we have here DOES NOT WORK. Luckily we can avoid giving all the layers
/// reference to the kentico dlls by passing the userinfo as an object and unpacking it in the repository.
/// </summary>
private void ResetPasswordAttempts(UserInfo user)
{
try
{
var accountFacade = WebContainer.Instance.Container.GetInstance<IAccountFacade>();
accountFacade.ResetPasswordAttempts(user);
}
catch (KenticoConfigurationException e)
{
var logger = LogManager.GetCurrentClassLogger();
logger.Warn(x => x("An exception was thrown during the IAccountFacade.ResetPasswordAttempts method.", e));
if (user.IsGlobalAdministrator)
{
logger.Error(x => x("IAccountFacade.ResetPasswordAttempts method throw an error, however because the user is logged in as a Kentico Global Admin, the system will ignore the error because the cause might be Kentico configuration and the Gloabl Admin who is logging in could fix the issue.", e));
}
else
{
var ex = new Exception("IAccountFacade.ResetPasswordAttempts method throw an error and the current user does not have the permissions to fix any potential Kentico configuration problems. The current user will not be allowed into the system.", e);
logger.Fatal(x => x("The current user does not have the permissions to login with the current system configuration errors; requires a user with Global Admin rights in kentico", e));
throw ex;
}
}
}
}
private class CustomErrorEventsAttribute : CMS.Base.CMSLoaderAttribute
{
/// <summary>
/// Called automatically when the application starts
/// </summary>
public override void Init()
{
SystemEvents.Exception.Execute += new EventHandler<SystemEventArgs>(UnhandledException_Execute);
}
private void UnhandledException_Execute(object sender, SystemEventArgs e)
{
try
{
//log exception
var logger = LogManager.GetCurrentClassLogger();
logger.Error(x => x("Unhandled Exception caught by Kentico Global Exception handler", e.Exception));
}
catch
{
//swallow exceptions created when handling an exception.
}
var routeToCustomErrorPage = bool.Parse(ConfigurationManager.AppSettings[AppSettingKeys.FriendlyErrorRedirect_Enabled]);
var request = HttpContext.Current.Request;
var response = HttpContext.Current.Response;
//if web.config setting says so, and this isn't an api request, route to friendly error page
if (routeToCustomErrorPage && !request.Url.AbsolutePath.Contains("/api/"))
{
response.Redirect("~/CMSTemplates/AonExchange/FriendlyErrorPage.htm");
}
}
}
private class CustomSessionEventsAttribute : CMS.Base.CMSLoaderAttribute
{
/// <summary>
/// Called automatically when the application starts
/// </summary>
public override void Init()
{
//CMSSessionEvents.Start.Before += new EventHandler<EventArgs>(OnBeginSession); -- Kentico 7
SessionEvents.UpdateSession.Before += new EventHandler<CMSEventArgs>(OnBeginSession);
}
private void OnBeginSession(object sender, EventArgs e)
{
var userSessionFacade = WebContainer.Instance.Container.GetInstance<IUserSessionFacade>();
if (userSessionFacade.IsAuthenticated())
{
var log = LogManager.GetCurrentClassLogger();
try
{
log.Info(x => x("Http request was authenticated, but a new session was created. Logging the user out and responding with an http redirect response."));
var accountFacade = WebContainer.Instance.Container.GetInstance<IAccountFacade>();
// This call to redirect will function with both normal page requests and ajax because of the SuppressFormsAuthenticationRedirectModule
accountFacade.Logout();
}
catch (Exception ex)
{
log.Error(x => x("An exception occurred while logging the user out because of a new session", ex));
}
}
}
}
private class CustomRequestEventsAttribute : CMS.Base.CMSLoaderAttribute
{
/// <summary>
/// Called automatically when the application starts
/// </summary>
public override void Init()
{
//CMSRequestEvents.Begin.Before += new EventHandler<EventArgs>(OnBeforeBeginRequest); -- Kentico 7
//CMSRequestEvents.Begin.After += new EventHandler<EventArgs>(OnBeginRequest); -- Kentico 7
//CMSRequestEvents.End.Before += new EventHandler<EventArgs>(OnEndRequest); -- Kentico 7
RequestEvents.Prepare.Execute += new EventHandler<EventArgs>(OnBeforeBeginRequest);
RequestEvents.Begin.Execute += new EventHandler<EventArgs>(OnBeginRequest);
RequestEvents.End.Execute += new EventHandler<EventArgs>(OnEndRequest);
}
private void OnBeforeBeginRequest(object sender, EventArgs e)
{
if ((HttpContext.Current != null) && (HttpContext.Current.Request != null))
{
// Loads the request headers as a collection.
NameValueCollection headers = HttpContext.Current.Request.Headers;
// Gets the value from the FRONT-END-HTTPS header.
string forwardedSSL = headers.Get("FRONT-END-HTTPS");
CMS.Helpers.RequestContext.IsSSL = false;
// Checks if the original request used HTTPS.
if (!string.IsNullOrEmpty(forwardedSSL) && forwardedSSL.Equals("on", StringComparison.InvariantCultureIgnoreCase))
{
var log = LogManager.GetCurrentClassLogger();
log.Info(x => x("Https offloading is enabled."));
CMS.Helpers.RequestContext.IsSSL = true;
}
}
}
private void OnBeginRequest(object sender, EventArgs e)
{
if (HttpContext.Current != null)
{
HttpContext.Current.Items.Add("RequestId", Guid.NewGuid().ToString());
}
if (bool.Parse(ConfigurationManager.AppSettings[AppSettingKeys.MethodTrace_Enabled]))
{
var stopwatch = WebContainer.Instance.Container.GetInstance<Stopwatch>();
var log = LogManager.GetCurrentClassLogger();
log.Trace(x => x("Starting request for {0}", HttpContext.Current.Request.UrlOriginal().ToString()));
stopwatch.Start();
}
}
private void OnEndRequest(object sender, EventArgs e)
{
if (bool.Parse(ConfigurationManager.AppSettings[AppSettingKeys.MethodTrace_Enabled]))
{
var stopwatch = WebContainer.Instance.Container.GetInstance<Stopwatch>();
var log = LogManager.GetCurrentClassLogger();
stopwatch.Stop();
if (log.IsTraceEnabled)
log.Trace(new MethodTimingMessage(stopwatch.ElapsedMilliseconds, 0, "CMSLoader.OnEndRequest", string.Format("{0} execution time in milliseconds for {1}. ::Total Page Request::", stopwatch.ElapsedMilliseconds, HttpContext.Current.Request.UrlOriginal().ToString())));
}
if (HttpContext.Current != null)
{
HttpContext.Current.Items.Remove("RequestId");
}
}
}
/// <summary>
/// Attribute class that ensures the loading of new relic JavaScript insertion
/// </summary>
private class CustomNewRelicSubstitutionLoader : CMS.Base.CMSLoaderAttribute
{
/// <summary>
/// Called automatically when the application starts
/// </summary>
public override void Init()
{
//OutputFilter.OnResolveSubstitution += ResolveCustomSubstitutions;
}
/// <summary>
/// Called to substitute the ~newrelic~ token from the kentico head element into the new relic code to instrument browser data.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void ResolveCustomSubstitutions(object sender, SubstitutionEventArgs e)
{
if (!e.Match)
{
switch (e.Expression.ToLowerInvariant())
{
case "newrelic":
e.Result = NewRelic.Api.Agent.NewRelic.GetBrowserTimingHeader();
e.Match = true;
break;
}
}
}
}
}
You need to mark CMSModuleLoader with your custom attribute CustomSecurityEventsAttribute, like this:
[CustomSecurityEvents]
public partial class CMSModuleLoader : CMSModuleLoaderBase
Related
So, I was creating a windows service and i was following this video to setup the windows service.
https://www.youtube.com/watch?v=tAF9krJu3cs
So, I have a Model, I get the Model in the controller so I can build what I want to send, then, I call that Controller on the Service. After that, I created the Installer. Problem: I have 3 and i was able to create them adding: [RunInstaller(true)] in the beggining of every service. After that, I was saying that they were not manual and "prepraring" them for the errors, starting them, and, after that I started them. But, I turned on one of them, the other turns off, and so on. I was able to turn the 3 on after a while (maybe this was a bug) but they all stopped working again..... My code:
ProjectInstaller:
enter image description here
ProjectInstaller.Designer.cs :
{
partial class ProjectInstaller
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Component Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.serviceProcessInstaller1 = new System.ServiceProcess.ServiceProcessInstaller();
this.AccountService = new System.ServiceProcess.ServiceInstaller();
this.FinInfoPrevYearService = new System.ServiceProcess.ServiceInstaller();
this.GetFinancialInfoService = new System.ServiceProcess.ServiceInstaller();
//
// serviceProcessInstaller1
//
this.serviceProcessInstaller1.Account = System.ServiceProcess.ServiceAccount.LocalService;
this.serviceProcessInstaller1.Password = null;
this.serviceProcessInstaller1.Username = null;
this.serviceProcessInstaller1.AfterInstall += new System.Configuration.Install.InstallEventHandler(this.serviceProcessInstaller1_AfterInstall);
//
// AccountService
//
this.AccountService.ServiceName = "AccountService";
this.AccountService.AfterInstall += new System.Configuration.Install.InstallEventHandler(this.AccountService_AfterInstall);
//
// FinInfoPrevYearService
//
this.FinInfoPrevYearService.ServiceName = "FinInfoPrevYearService";
this.FinInfoPrevYearService.AfterInstall += new System.Configuration.Install.InstallEventHandler(this.FinInfoPrevYearService_AfterInstall);
//
// GetFinancialInfoService
//
this.GetFinancialInfoService.ServiceName = "GetFinancialInfoService";
this.GetFinancialInfoService.AfterInstall += new System.Configuration.Install.InstallEventHandler(this.GetFinancialInfoService_AfterInstall);
//
// ProjectInstaller
//
this.Installers.AddRange(new System.Configuration.Install.Installer[] {
this.serviceProcessInstaller1,
this.AccountService,
this.FinInfoPrevYearService,
this.GetFinancialInfoService});
}
#endregion
private System.ServiceProcess.ServiceProcessInstaller serviceProcessInstaller1;
private System.ServiceProcess.ServiceInstaller AccountService;
private System.ServiceProcess.ServiceInstaller FinInfoPrevYearService;
private System.ServiceProcess.ServiceInstaller GetFinancialInfoService;
}
}
ProjectInstaller.cs:
{
[RunInstaller(true)]
public partial class ProjectInstaller : System.Configuration.Install.Installer
{
public ProjectInstaller()
{
InitializeComponent();
}
private void serviceProcessInstaller1_AfterInstall(object sender, InstallEventArgs e)
{
}
private void AccountService_AfterInstall(object sender, InstallEventArgs e)
{
}
private void GetFinancialInfoService_AfterInstall(object sender, InstallEventArgs e)
{
}
private void FinInfoPrevYearService_AfterInstall(object sender, InstallEventArgs e)
{
}
}
}
One of the services:
enter image description here
Please help me guys.. I dont know what to do anymore...
I am trying to create a game in Unity3D which connects to bluetooth low energy device that supports the heart rate service and collects the HR data. I have created a WPF library that has been tested on a console application to connect to a BLE device and read the data which works perfectly. However, I do not know how to use this in Unity.
I transferred the library to Unity and had to disable it for the editor as the code uses Windows namespaces which are not supported in the editor. My problem now is how do I debug the code in Unity to check if the library code is working when I run the game. I tried wrapping the code for calling the library namespace and the functions from the library in #if NETFX_CORE, #if ENABLE_WINMD_SUPPORT, #if WINDOWS_UWP and many more but, never got any of the debug logs that I wrote.
Is there any possible solution to this?
Any help would be appreciated, thank you!
This is the code for the bluetooth LE connection library:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Windows.Devices.Bluetooth;
using Windows.Devices.Bluetooth.Advertisement;
using Windows.Devices.Bluetooth.GenericAttributeProfile;
using Windows.Devices.Enumeration;
using Windows.Storage.Streams;
namespace BLEHR
{
/// <summary>
/// Wraps and makes use if the <see cref="BluetoothLeAdvertisementWatcher"/>
/// for easier consumption
///
/// </summary>
public class BLEAdvertisementWatcher
{
#region Private Members
/// <summary>
/// The underlying bluetooth watcher class
/// </summary>
private readonly BluetoothLEAdvertisementWatcher mWatcher;
/// <summary>
/// a list of discovered devices
/// </summary>
private readonly Dictionary<string, BLEDevice> mDiscoveredDevices = new Dictionary<string, BLEDevice>();
/// <summary>
/// The details about Gatt services
/// </summary>
private readonly GattServiceIDs mGattServiceIds;
/// <summary>
/// a thread lock object for this class
/// </summary>
private readonly object mThreadLock = new object();
#endregion
#region Public Properties
/// <summary>
/// indicates is this watcher is listening for advertisements
/// </summary>
public bool Listening => mWatcher.Status == BluetoothLEAdvertisementWatcherStatus.Started;
/// <summary>
/// a list of discovered devices
/// </summary>
public IReadOnlyCollection<BLEDevice> DiscoveredDevices
{
get
{
//Clena up any Timeouts
CleanupTimeouts();
//Practice Thread safety
lock (mThreadLock)
{
//Convert to readonly list
return mDiscoveredDevices.Values.ToList().AsReadOnly();
}
}
}
/// <summary>
/// The timeout in seconds that a device is removed from the <see cref="DiscoveredDevices"/>
/// list if it is not re-advertised within this time
/// </summary>
public int TimeoutRemoval { get; set; } = 20;
public int HRValue { get; set; }
#endregion
#region Constructor
/// <summary>
/// The default constructor
/// </summary>
public BLEAdvertisementWatcher(GattServiceIDs gattIds)
{
//Null guard
mGattServiceIds = gattIds ?? throw new ArgumentNullException(nameof(gattIds));
//Create bluetooth listener
mWatcher = new BluetoothLEAdvertisementWatcher
{
ScanningMode = BluetoothLEScanningMode.Active
};
//Listen out for new advertisements
mWatcher.Received += WatcherAdvertisementReceivedAsync;
//Listen out for when the watcher stops listening
mWatcher.Stopped += (watcher, e) =>
{
//Inform listeners
StoppedListening();
};
}
#endregion
#region Private Methods
/// <summary>
/// Listens out for watcher advertisements
/// </summary>
/// <param name="sender"> The Watcher </param>
/// <param name="args">The Arguments </param>
private async void WatcherAdvertisementReceivedAsync(BluetoothLEAdvertisementWatcher sender, BluetoothLEAdvertisementReceivedEventArgs args)
{
//cleanup timeouts
CleanupTimeouts();
//Get BLE device info
var device = await GetBLEDeviceAsync(args.BluetoothAddress, args.Timestamp, args.RawSignalStrengthInDBm);
//Null guard
if(device == null)
{
return;
}
//is new discovery?
var newDiscovery = false;
var existingName = default(string);
//Lock your doors
lock (mThreadLock)
{
//Check if this is a new discovery
newDiscovery= !mDiscoveredDevices.ContainsKey(device.DeviceID);
//If this is not new...
if (!newDiscovery)
{
//Store the old name
existingName = mDiscoveredDevices[device.DeviceID].Name;
}
}
//Name changed?
var nameChanged =
//if it already exists
!newDiscovery &&
//And is not a blank name
!string.IsNullOrEmpty(device.Name) &&
//And the name is different
existingName != device.Name;
lock (mThreadLock)
{
//add/update the device in the dictionary
mDiscoveredDevices[device.DeviceID] = device;
}
//Inform listeners
DeviceDiscovered(device);
//if new discovery...
if (newDiscovery)
{
//Inform listeners
NewDeviceDiscovered(device);
}
}
/// <summary>
/// Connects to the BLE device and extracts more information from the
/// <seealso cref="https://learn.microsoft.com/en-us/uwp/api/windows.devices.bluetooth.bluetoothledevice"/>
/// </summary>
/// <param name="address">The BT address of the device to connect to</param>
/// <param name="broadcastTime">The time the broadcast message was received</param>
/// <param name="rssi">The signal strength in dB</param>
/// <returns></returns>
private async Task<BLEDevice> GetBLEDeviceAsync(ulong address, DateTimeOffset broadcastTime, short rssi)
{
//Get bluetooth device info
var device = await BluetoothLEDevice.FromBluetoothAddressAsync(address).AsTask();
//Null guard
if(device == null)
{
return null;
}
//Get GATT services that are available
var gatt = await device.GetGattServicesAsync().AsTask();
//if we have any services..
if(gatt.Status == GattCommunicationStatus.Success)
{
//loop each gatt service
foreach(var service in gatt.Services)
{
//This ID contains the GATT profile assigned number we want!
//TODO: Get more info and connect
var gattProfileId = service.Uuid;
}
}
//Return the new device information
return new BLEDevice
(
//Device ID
deviceID: device.DeviceId,
//Bluetooth address
address: device.BluetoothAddress,
//Device name
name: device.Name,
//Broadcast time
broadcastTime: broadcastTime,
//Signal strength
rssi: rssi,
//Is connected
connected: device.ConnectionStatus== BluetoothConnectionStatus.Connected,
//Can Pair?
canPair: device.DeviceInformation.Pairing.CanPair,
//Is Paired?
isPaired: device.DeviceInformation.Pairing.IsPaired
);
}
/// <summary>
/// Prune any timed out devices that we have not heard off
/// </summary>
private void CleanupTimeouts()
{
lock (mThreadLock)
{
//The date in time that if less than means a device has timed out
var threshold = DateTime.UtcNow - TimeSpan.FromSeconds(TimeoutRemoval);
//any devices that have not sent a new broadcast within the time
mDiscoveredDevices.Where(f => f.Value.BroadcastTime < threshold).ToList().ForEach(device =>
{
//remove device
mDiscoveredDevices.Remove(device.Key);
//Inform listeners
DeviceTimeout(device.Value);
});
}
}
#endregion
#region Public events
/// <summary>
/// Fired when the bluetooth watcher stops listening
/// </summary>
public event Action StoppedListening = () => { };
/// <summary>
/// Fired when the bluetooth watcher start listening
/// </summary>
public event Action StartedListening = () => { };
/// <summary>
/// fired when a new device is discovered
/// </summary>
public event Action<BLEDevice> NewDeviceDiscovered = (device) => {};
/// <summary>
/// fired when a device is discovered
/// </summary>
public event Action<BLEDevice> DeviceDiscovered = (device) => { };
/// <summary>
/// Fired when a device is removed for timing out
/// </summary>
public event Action<BLEDevice> DeviceTimeout = (device) => { };
#endregion
#region Public Methods
/// <summary>
/// Starts listening for advertisements
/// </summary>
public void StartListening()
{
lock (mThreadLock)
{
//if already listening...
if (Listening)
{
//DO nothing more
return;
}
//Start the underlying watcher
mWatcher.Start();
}
//inform listeners
StartedListening();
}
/// <summary>
/// Stops listening for advertisements
/// </summary>
public void StopListening()
{
lock (mThreadLock)
{
//if we are not currently listening
if (!Listening)
{
//Do nothing more
return;
}
//Stop listening
mWatcher.Stop();
//clear any devices
mDiscoveredDevices.Clear();
}
}
/// <summary>
/// Attempts to pair to a BLE device, by ID
/// </summary>
/// <param name="deviceID"> The BLE device ID</param>
/// <returns></returns>
public async Task PairToDeviceAsync(string deviceID)
{
//Get bluetooth device info
var device = await BluetoothLEDevice.FromIdAsync(deviceID).AsTask();
//Null guard
if (device == null)
{
//TODO: localize
throw new ArgumentNullException("");
}
//if we are already paired...
if (device.DeviceInformation.Pairing.IsPaired)
{
//un-pair the device
await device.DeviceInformation.Pairing.UnpairAsync().AsTask();
return;
}
//Try and pair to the device
device.DeviceInformation.Pairing.Custom.PairingRequested += (sender, args) =>
{
//Accept all attempts
args.Accept(); // <-- could enter a pin in here to accept
};
var result = await device.DeviceInformation.Pairing.Custom.PairAsync(DevicePairingKinds.ConfirmOnly).AsTask();
//Get GATT services that are available
var gatt = await device.GetGattServicesAsync().AsTask();
GattDeviceService serviceReq = null;
GattCharacteristic characteristicReq = null;
//if we have any services..
if (gatt.Status == GattCommunicationStatus.Success)
{
//loop each gatt service
foreach (var service in gatt.Services)
{
if (service.Uuid == GattServiceUuids.HeartRate)
{
serviceReq = service;
}
//This ID contains the GATT profile assigned number we want!
//TODO: Get more info and connect
var gattProfileId = service.Uuid;
}
var charResults = await serviceReq.GetCharacteristicsAsync().AsTask();
if(charResults.Status == GattCommunicationStatus.Success)
{
foreach (var chars in charResults.Characteristics)
{
if(chars.Uuid == GattCharacteristicUuids.HeartRateMeasurement)
{
characteristicReq = chars;
}
}
GattCharacteristicProperties properties = characteristicReq.CharacteristicProperties;
if (properties.HasFlag(GattCharacteristicProperties.Read))
{
GattReadResult readVal = await characteristicReq.ReadValueAsync().AsTask();
if(readVal.Status == GattCommunicationStatus.Success)
{
var reader = DataReader.FromBuffer(readVal.Value);
byte[] input = new byte[reader.UnconsumedBufferLength];
reader.ReadBytes(input);
HRValue = BitConverter.ToInt32(input, 0);
}
}
}
}
////Log the result
//if(result.Status == DevicePairingResultStatus.Paired)
//{
// Console.WriteLine("Pairing successful");
//}
//else
//{
// Console.WriteLine($"Pairing failed: {result.Status}");
//}
}
#endregion
}
}
And here is the code I am trying in Unity:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
#if NETFX_CORE
using BLEHR
#endif
public class HRTest : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
#if NETFX_CORE
var watcher = new BLEAdvertisementWatcher(new GattServiceIDs());
Debug.Log("Working?");
#endif
}
// Update is called once per frame
void Update()
{
#if WINDOWS_UWP
Debug.Log("Connecting");
#endif
}
}
Debugging with UWP/.Net-Core-only features in Unity seems like an infeasible workflow to me because of the long edit-compile-run cycles. The Unity Editor runs with .Net-Framework and thus can't directly access .Net-Core features. For non-Unity projects there is the possibility to access UWP-API from .Net-Framework via wrappers provided by Windows. But that doesn't work for Unity because of some Unity-specifics1.
BUT there is the possibility to wrap the UWP-calls in a C++ winrt dll and copy the dll to Unity. This way the UWP-API can be also used inside the Unity Editor (e.g. when pushing the play button). My use case also requires BLE and I uploaded my wrapper in this repo. You're free to use it as a starting point if you want.
One last thing: With .Net version 5 which is also announced for Unity, I guess this should become obsolete as version 5 is said to merge .Net-Framework and .Net-Core.
[1] Much information for this answer came from this blog-post by Mike Taulty.
I have created an object which is used to control a piece of test equipment (oscilloscope) which communicates using the Visa library. This object (scope object) works fine, but there is one method I created to query the scope for a waveform average, which takes a while to execute (around a second or so), and blocks execution of the UI while it is acquiring the data.
To get around this problem, I initially tried creating a task object and using this task object to execute the function that is querying the scope for the data; but I found that something in the Visa driver object itself was apparently still executing on the main thread (and thus slowing down my UI).
I then did one more test and created a new thread, and had this thread call a function. Inside this function, I initialized the scope object, setup the operating parameters, and then called the long-running function. This time, my UI was as responive as normal with no slowdowns.
So now, It seems that I need to actually initialize the scope object inside a new thread in order to get it to truly run asynchronously. But now I have a new challenge. I need to access the objects properties and methods from the main thread to set things up, query status info, etc. Is there a clean way to effectively read and write to a class's properties and methods from another thread? Or are there any existing libraries available to make this simpler?
My current idea is to create a wrapper class for the scope object and then have this wrapper class initialize the scope object in a new thread. But I'm not sure of the best way to access the object's members efficiently. Or better yet, is there a better approach to this problem?
EDIT: Below is some more information and code for the test program I wrote. The UI is a just a simple form with Acquire and Connect buttons, and two labels (one for showing the measurements and the other shows a number that gets incremented as I click on the "Click" button:
Here's the code for the Scope Object I created:
using System;
using Ivi.Scope.Interop;
using Tektronix.Tkdpo2k3k4k.Interop;
using System.Diagnostics;
namespace Test_App
{
public class DPO4034
{
#region [NOTES] Installing TekVisa Drivers for DPO4034
/*
1. Download and install the TekVisa Connectivity Software from here:
https://www.tek.com/oscilloscope/tds7054-software/tekvisa-connectivity-software-v411
2. Check under Start -> All Programs -> TekVisa and see if the "Open Choice Installation Manager" shortcut works.If not, then update all shortcuts to point to the correct base folder for the TekVISA files, which is "C:\Program Files\IVI Foundation\VISA\".
3. Download the DPO4000 series IVI driver from here:
https://www.tek.com/oscilloscope/dpo4054-software/dpo2000-dpo3000-dpo4000-ivi-driver
4. After running the unzip utility, open the unzipped folder and goto x64 -> Shared Components, and run the IviCleanupUtility_2.0.0.exe utility to make sure no shared IVI components exist.
5. Run the IviSharedComponents64_2.1.1.exe file to install shared components.
6. Go up one folder and open the IVI Driver Folder and run the Tkdpo2k3k4k-x64.msi installer to install the scope IVI driver.
7. In the VS project, add references to the following COM components:
• IviDriverLib
• IviScopeLib
• Tkdpo2k3k4kLib
8. Right Click on each of the three references in the Solution Explorer and select Properties in the menu. When the properties window appears, set the "Embed Interop Types" property to False.
*/
#endregion
#region Class Variables
Tkdpo2k3k4kClass driver; // IVI Driver representing the DPO4034
IIviScope scope; // IVI Scope object representing the DPO4034
#endregion
#region Class Constructors
public DPO4034()
{
this.driver = new Tkdpo2k3k4kClass();
this.scope = (IIviScope)driver;
}
~DPO4034()
{
this.Disconnect();
}
#endregion
#region Public Properties
/// <summary>
/// Returns true if the scope is connected (initialized)
/// </summary>
public bool Connected
{
get
{
return this.driver.IIviDriver_Initialized;
}
}
#endregion
#region Public Methods
/// <summary>
/// Initializes the connection to the scope
/// <paramref name="reset"/>Resets the scope after connecting if set to true</param>
/// </summary>
/// <returns>True if the function succeeds</returns>
public bool Connect(bool reset = false)
{
try
{
if (!this.Connected)
{
this.Disconnect();
}
this.driver.Initialize("TCPIP::10.10.0.200::INSTR", true, reset, "Simulate=false, DriverSetup= Model=DPO4034");
return true;
}
catch (Exception ex)
{
PrintError(ex, "Connect");
return false;
}
}
/// <summary>
/// Closes the connection to the scope
/// </summary>
/// <returns>True if the function succeeds</returns>
public bool Disconnect()
{
try
{
if (this.Connected)
{
this.driver.Close();
}
return true;
}
catch (Exception ex)
{
PrintError(ex, "Disconnect");
return false;
}
}
/// <summary>
/// Reads the average value of the waveform on the selected channel
/// </summary>
/// <param name="channel">1-4 for channels 1 to 4</param>
/// <returns>The measured average value</returns>
public double ReadWaveformAverage(int channel)
{
if (this.Connected)
{
try
{
double value = 0;
this.scope.Measurements.Item["CH" + channel.ToString()].FetchWaveformMeasurement(IviScopeMeasurementEnum.IviScopeMeasurementVoltageAverage, ref value);
return value;
}
catch (Exception ex)
{
PrintError(ex, "ReadWaveformAverage");
return 0;
}
}
else
{
PrintError("Oscilloscope not connected", "ReadWaveformAverage");
return 0;
}
}
#endregion
#region Private Methods
/// <summary>
/// Prints an error message to the debug console
/// </summary>
/// <param name="err">Error object</param>
/// <param name="source">Source of the error</param>
private void PrintError(Exception err, string source = "") //, bool showMessageBox = false)
{
Debug.Print($"Error: {err.Message}");
Debug.Print($"Source: {source}");
}
/// <summary>
/// Prints an error message to the debug console
/// </summary>
/// <param name="err">Error object</param>
/// <param name="source">Source of the error</param>
private void PrintError(string error, string source = "")
{
Debug.Print($"Error: {error}");
Debug.Print($"Source: {source}");
}
#endregion
}
}
Here's the code for the version of the form that uses an async function and tasks to directly call the acquisition functions:
using System;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace Test_App
{
public partial class Form1 : Form
{
byte number = 0;
public Form1()
{
InitializeComponent();
}
private void cmdAcquire_Click(object sender, EventArgs e)
{
takeMeasurements();
}
async void takeMeasurements()
{
try
{
// Create new instance of the scope object and connect to it
DPO4034 Scope = new DPO4034();
Scope.Connect();
// Update status
PrintStatus(Scope.Connected ? "Connected" : "Error");
// Loop continuously and print the samples to the status label
while (Scope.Connected)
{
double inputVoltage = await Task.Run(() => Scope.ReadWaveformAverage(1));
double inputCurrent = await Task.Run(() => Scope.ReadWaveformAverage(2));
double outputVoltage = await Task.Run(() => Scope.ReadWaveformAverage(3));
PrintStatus($"CH1: {inputVoltage}\n" +
$"CH2: {inputCurrent}\n" +
$"CH3: {outputVoltage}\n");
}
}
catch (Exception)
{
PrintStatus("Error");
}
}
private void cmdIncrement(object sender, EventArgs e)
{
// This is just something for me to make the interface do to see
// how responsive it is
lblNumber.Text = number.ToString();
number++;
}
// Prints status text to the label on the form
private void PrintStatus(string text)
{
Status.Text = text;
}
}
}
and here's the code for the version of the form that uses a separate thread to run the scope object:
using System;
using System.Threading;
using System.Windows.Forms;
namespace Test_App
{
public partial class Form1 : Form
{
Thread t;
byte number = 0;
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
t = new Thread(new ThreadStart(takeMeasurements));
}
private void cmdAcquire_Click(object sender, EventArgs e)
{
t.Start();
}
// Function to create scope object and take acquisitions
void takeMeasurements()
{
try
{
// Create new instance of the scope object and connect to it
DPO4034 Scope = new DPO4034();
Scope.Connect();
// Update status
PrintStatus(Scope.Connected ? "Connected" : "Error");
// Loop continuously and print the samples to the status label
while (Scope.Connected)
{
double inputVoltage = Scope.ReadWaveformAverage(1);
double inputCurrent = Scope.ReadWaveformAverage(2);
double outputVoltage = Scope.ReadWaveformAverage(3);
PrintStatus($"CH1: {inputVoltage}\n" +
$"CH2: {inputCurrent}\n" +
$"CH3: {outputVoltage}\n");
}
}
catch (Exception)
{
PrintStatus("Error");
}
}
private void cmdIncrement(object sender, EventArgs e)
{
// This is just something for me to make the interface do to see
// how responsive it is
lblNumber.Text = number.ToString();
number++;
}
// Prints status text to the label on the form
private void PrintStatus(string text)
{
if (!this.IsDisposed)
{
this.BeginInvoke((MethodInvoker)delegate
{
Status.Text = text;
});
}
else
{
t.Abort();
}
}
}
}
I hope this gives some more insight into what I'm trying to accomplish. Thank you all for your comments and I look forward to your feedback.
EDIT2: Just to be more clear, the method I would prefer to use (if possible) is the one using tasks. In the current program, the Scope object is initialized at the top of the form on the main thread and accessed by multiple objects within the program.
For anyone interested, I finally found a solution to the problem I was having with the GUI freezing when executing the ReadWaveformData() function.
The answer was to create a new thread inside of the Scope class that would call an Initialization function to initialize the internal scope and driver objects. Then the thread would do nothing but sit and host the instances until the ReadWavveformData() function is called inside a task. Here's the modified DPO4034 class:
using System;
using Ivi.Scope.Interop;
using Tektronix.Tkdpo2k3k4k.Interop;
using System.Diagnostics;
using System.Threading;
namespace Test_App
{
public class DPO4034
{
#region [NOTES] Installing TekVisa Drivers for DPO4034
/*
1. Download and install the TekVisa Connectivity Software from here:
https://www.tek.com/oscilloscope/tds7054-software/tekvisa-connectivity-software-v411
2. Check under Start -> All Programs -> TekVisa and see if the "Open Choice Installation Manager" shortcut works.If not, then update all shortcuts to point to the correct base folder for the TekVISA files, which is "C:\Program Files\IVI Foundation\VISA\".
3. Download the DPO4000 series IVI driver from here:
https://www.tek.com/oscilloscope/dpo4054-software/dpo2000-dpo3000-dpo4000-ivi-driver
4. After running the unzip utility, open the unzipped folder and goto x64 -> Shared Components, and run the IviCleanupUtility_2.0.0.exe utility to make sure no shared IVI components exist.
5. Run the IviSharedComponents64_2.1.1.exe file to install shared components.
6. Go up one folder and open the IVI Driver Folder and run the Tkdpo2k3k4k-x64.msi installer to install the scope IVI driver.
7. In the VS project, add references to the following COM components:
• IviDriverLib
• IviScopeLib
• Tkdpo2k3k4kLib
8. Right Click on each of the three references in the Solution Explorer and select Properties in the menu. When the properties window appears, set the "Embed Interop Types" property to False.
*/
#endregion
#region Class Variables
Tkdpo2k3k4kClass driver; // IVI Driver representing the DPO4034
IIviScope scope; // IVI Scope object representing the DPO4034
Thread t; // Thread to initialize the scope objects in to ensure that they async method calls do not run on the main thread
#endregion
#region Class Constructors
public DPO4034()
{
t = new Thread(new ThreadStart(Initialize));
t.Start();
// Wait for scope object to be initialized in the thread
while (this.scope == null);
}
~DPO4034()
{
this.Disconnect();
t.Abort();
}
#endregion
#region Public Properties
/// <summary>
/// Returns true if the scope is connected (initialized)
/// </summary>
public bool Connected
{
get
{
return this.driver.IIviDriver_Initialized;
}
}
#endregion
#region Public Methods
/// <summary>
/// Initializes the connection to the scope
/// <paramref name="reset"/>Resets the scope after connecting if set to true</param>
/// </summary>
/// <returns>True if the function succeeds</returns>
public bool Connect(bool reset = false)
{
try
{
if (!this.Connected)
{
this.Disconnect();
}
this.driver.Initialize("TCPIP::10.10.0.200::INSTR", true, reset, "Simulate=false, DriverSetup= Model=DPO4034");
return true;
}
catch (Exception ex)
{
PrintError(ex, "Connect");
return false;
}
}
/// <summary>
/// Closes the connection to the scope
/// </summary>
/// <returns>True if the function succeeds</returns>
public bool Disconnect()
{
try
{
if (this.Connected)
{
this.driver.Close();
}
return true;
}
catch (Exception ex)
{
PrintError(ex, "Disconnect");
return false;
}
}
/// <summary>
/// Reads the average value of the waveform on the selected channel
/// </summary>
/// <param name="channel">1-4 for channels 1 to 4</param>
/// <returns>The measured average value</returns>
public double ReadWaveformAverage(int channel)
{
if (this.Connected)
{
try
{
double value = 0;
this.scope.Measurements.Item["CH" + channel.ToString()].FetchWaveformMeasurement(IviScopeMeasurementEnum.IviScopeMeasurementVoltageAverage, ref value);
return value;
}
catch (Exception ex)
{
PrintError(ex, "ReadWaveformAverage");
return 0;
}
}
else
{
PrintError("Oscilloscope not connected", "ReadWaveformAverage");
return 0;
}
}
#endregion
#region Private Methods
private void Initialize()
{
this.driver = new Tkdpo2k3k4kClass();
this.scope = (IIviScope)driver;
// Does nothing but allow the objects to exist on the separate thread
while (true)
{
Thread.Sleep(int.MaxValue);
}
}
/// <summary>
/// Prints an error message to the debug console
/// </summary>
/// <param name="err">Error object</param>
/// <param name="source">Source of the error</param>
private void PrintError(Exception err, string source = "") //, bool showMessageBox = false)
{
Debug.Print($"Error: {err.Message}");
Debug.Print($"Source: {source}");
}
/// <summary>
/// Prints an error message to the debug console
/// </summary>
/// <param name="err">Error object</param>
/// <param name="source">Source of the error</param>
private void PrintError(string error, string source = "")
{
Debug.Print($"Error: {error}");
Debug.Print($"Source: {source}");
}
#endregion
}
}
If this is paired up with the version of the TestApp that uses the async tasks to execute the ReadWaveformData() function, then things run smoothly and I don't need to completely rewrite the scope class to get it to work in my program. Hope this is helpful for anyone else who may run into a similar challenge.
When I run the following code :
static void Main(string[] args)
{
if (!System.Diagnostics.EventLog.SourceExists("eventSource"))
{
System.Diagnostics.EventLog.CreateEventSource("eventSource", "");
}
System.Diagnostics.EventLog eventLog = new System.Diagnostics.EventLog();
eventLog.Source = "eventSource";
eventLog.Log = "";
for (int i = 0; i < 10000; i++)
{
Thread.Sleep(100);
eventLog.WriteEntry("test", EventLogEntryType.Information);
//I also tried the static method, the program still leaks
//System.Diagnostics.EventLog.WriteEntry("eventSource", "test", EventLogEntryType.Information);
}
Console.ReadKey();
}
The memory usage starts around 1MB, but rise up very quickly and doesn't stop. Why ?
The code I'm using to install windows service with a designated event log:
[RunInstaller(true)]
public partial class SampleServiceInstaller : System.Configuration.Install.Installer
{
private string SampleServiceName = string.Empty;
private string SampleLogName = string.Empty;
public SampleServiceInstaller()
{
this.SampleServiceName = "SampleService";
this.SampleLogName = this.SampleServiceName + "Log";
ServiceProcessInstaller serviceProcessInstaller = new ServiceProcessInstaller();
serviceProcessInstaller.Password = null;
serviceProcessInstaller.Username = null;
serviceProcessInstaller.Account = ServiceAccount.LocalSystem;
ServiceInstaller serviceInstaller = new ServiceInstaller();
//This must be identical to the WindowsService.ServiceBase name
// set in the constructor of WindowsService.cs
serviceInstaller.ServiceName = this.SampleServiceName;
serviceInstaller.DisplayName = this.SampleServiceName;
serviceInstaller.StartType = ServiceStartMode.Automatic;
serviceInstaller.Description = "Sample Windows Service";
// kill the default event log installer
serviceInstaller.Installers.Clear();
// Create Event Source and Event Log
// This recreates the log every time the service is reinstaled (old messages are lost)
if (EventLog.SourceExists(this.SampleServiceName)) EventLog.DeleteEventSource(this.SampleServiceName);
System.Diagnostics.EventLogInstaller logInstaller = new System.Diagnostics.EventLogInstaller();
logInstaller.Source = this.SampleServiceName; // use same as ServiceName
logInstaller.Log = this.SampleLogName; //can't be the same as service name
// Add all installers
this.Installers.AddRange(new Installer[] {
serviceProcessInstaller, serviceInstaller, logInstaller
});
}
public override void Install(System.Collections.IDictionary savedState)
{
base.Install(savedState);
}
protected override void OnBeforeUninstall(System.Collections.IDictionary savedState)
{
base.OnBeforeUninstall(savedState);
}
service code:
public partial class SampleService : ServiceBase
{
/// <summary>
/// Designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Event log for the service
/// </summary>
EventLog serviceLog;
/// <summary>
/// Public Constructor for WindowsService.
/// - Initialization code here.
/// </summary>
public SampleService()
{
InitializeComponent();
}
/// <summary>
/// The Main Thread: list of services to run.
/// </summary>
static void Main()
{
ServiceBase[] ServicesToRun;
ServicesToRun = new ServiceBase[] { new SampleService() };
ServiceBase.Run(ServicesToRun);
}
/// <summary>
/// Startup code
/// </summary>
/// <param name="args"></param>
protected override void OnStart(string[] args)
{
base.OnStart(args);
//
// run your own start code here
//
serviceLog.WriteEntry("My service started", EventLogEntryType.Information, 0);
}
/// <summary>
/// Stop code
/// </summary>
protected override void OnStop()
{
//
// run your own stop code here
//
serviceLog.WriteEntry("My service stopped", EventLogEntryType.Information, 0);
base.OnStop();
}
/// <summary>
/// Cleanup code
/// </summary>
/// <param name="disposing"></param>
protected override void Dispose(bool disposing)
{
//
// do disposing here
//
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
/// <summary>
/// Pause code
/// </summary>
protected override void OnPause()
{
base.OnPause();
//
// code to run if service pauses
//
}
/// <summary>
/// Continue code
/// </summary>
protected override void OnContinue()
{
base.OnContinue();
//
// code tu run when service continues after being paused
//
}
/// <summary>
/// Called when the System is shutting down
/// - when special handling
/// of code that deals with a system shutdown, such
/// as saving special data before shutdown is needed.
/// </summary>
protected override void OnShutdown()
{
//
// code tu run when system is shut down
//
base.OnShutdown();
}
/// <summary>
/// If sending a command to the service is needed
/// without the need for Remoting or Sockets,
/// this method is used to do custom methods.
/// int command = 128; //Some Arbitrary number between 128 & 256
/// ServiceController sc = new ServiceController("NameOfService");
/// sc.ExecuteCommand(command);
/// </summary>
/// <param name="command">Arbitrary Integer between 128 & 256</param>
protected override void OnCustomCommand(int command)
{
base.OnCustomCommand(command);
//
// handle custom code here
//
}
/// <summary>
/// Useful for detecting power status changes,
/// such as going into Suspend mode or Low Battery for laptops.
/// </summary>
/// <param name="powerStatus">The Power Broadcast Status
/// (BatteryLow, Suspend, etc.)</param>
protected override bool OnPowerEvent(PowerBroadcastStatus powerStatus)
{
//
// handle power events here
//
return base.OnPowerEvent(powerStatus);
}
/// <summary>
/// To handle a change event
/// from a Terminal Server session.
/// Useful if determining
/// when a user logs in remotely or logs off,
/// or when someone logs into the console is needed.
/// </summary>
/// <param name="changeDescription">The Session Change
/// Event that occured.</param>
protected override void OnSessionChange(SessionChangeDescription changeDescription)
{
//
// handle session change here
//
base.OnSessionChange(changeDescription);
}
private void InitializeComponent()
{
components = new System.ComponentModel.Container();
// first 8 letters should be unique
this.ServiceName = "SampleService";
// if you want to log service event to log registered while installing the service
string newLogName = this.ServiceName + "Log";
string newSourceName = this.ServiceName;
if (!EventLog.SourceExists(newSourceName))
{
EventLog.CreateEventSource(newSourceName, newLogName);
}
serviceLog = new EventLog();
serviceLog.Source = newSourceName;
serviceLog.Log = newLogName;
// Causes log to be disposed when the service is disposed
components.Add(serviceLog);
// Flags set whether or not to handle that specific type of event.
this.CanHandlePowerEvent = true;
this.CanHandleSessionChangeEvent = true;
this.CanPauseAndContinue = true;
this.CanShutdown = true;
this.CanStop = true;
}
}
I have an application that uses a mutex to stop multiple instances running at the same time, and to accept command line inputs in to the running instance.
I have a function in the app that asks the user if they want to restart as admin when required. For example the features they have enabled might require admin rights.
The mutex class looks like this:
namespace SingleInstanceClassLibrary
{
/// <summary>
/// Enforces single instance for an application.
/// </summary>
public class SingleInstance : IDisposable
{
private Mutex mutex = null;
private Boolean ownsMutex = false;
private Guid identifier = Guid.Empty;
/// <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 Boolean 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 Boolean PassArgumentsToFirstInstance(String[] arguments)
{
if (IsFirstInstance)
throw new InvalidOperationException("This is the first instance.");
try
{
using (NamedPipeClientStream client = new NamedPipeClientStream(identifier.ToString()))
using (StreamWriter writer = new StreamWriter(client))
{
client.Connect(200);
foreach (String 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(new WaitCallback(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 (NamedPipeServerStream server = new NamedPipeServerStream(identifier.ToString()))
using (StreamReader reader = new StreamReader(server))
{
server.WaitForConnection();
List<String> arguments = new List<String>();
while (server.IsConnected)
arguments.Add(reader.ReadLine());
ThreadPool.QueueUserWorkItem(new WaitCallback(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 Boolean 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
}
I call it like this:
private static void Main()
{
Guid guid = new Guid("{6EAE2E61-E7EE-42bf-8EBE-BAB890C5410F}");
//SingleInstance ensures only 1 instance of the app runs at one time. If another instance is started
//it will be closed. If the 2nd instance included arguments these will be passed to
//the singleInstance_ArgumentsReceived event for originally running process
using (SingleInstance singleInstance = new SingleInstance(guid))
{
//MessageBox.Show(Environment.GetCommandLineArgs().ToString());
//if (Environment.GetCommandLineArgs().Contains("RunAsAdmin"))
//MessageBox.Show("YES");
if (singleInstance.IsFirstInstance || Environment.GetCommandLineArgs().Contains("RunAsAdmin"))
{
singleInstance.ArgumentsReceived += singleInstance_ArgumentsReceived;
singleInstance.ListenForArgumentsFromSuccessiveInstances();
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
// Show the system tray icon.
//using (ProcessIcon pi = new ProcessIcon())
using (ProcessIcon pi = new ProcessIcon())
{
//Use to pass instance of ProcessIcon to LyncPresenceSwitcher
lyncPresenceSwitcher.processIcon = pi;
//Pass Lync instance
pi.lync = lyncClientController;
pi.Display();
// Make sure the application runs!
Application.Run();
}
}
else
singleInstance.PassArgumentsToFirstInstance(Environment.GetCommandLineArgs());
}
}
//Process arguments past with app execution
private static void singleInstance_ArgumentsReceived(object sender, ArgumentsReceivedEventArgs e)
{
if (settingsBox == null)
return;
foreach (String arg in e.Args)
{
//if arguments include OpenSettings open SettingsBox
if (arg == "OpenSettings")
{
settingsBox.ShowDialog();
}
if (arg == "RunAsAdmin")
{
//singleInstance.Dispose();
}
}
}
The app checks whether settings require admin access and prompts user to restart as admin when required. To restart the app I run this:
if (!IsRunAsAdmin())
{
// Launch itself as administrator
ProcessStartInfo proc = new ProcessStartInfo();
proc.UseShellExecute = true;
proc.WorkingDirectory = Environment.CurrentDirectory;
proc.FileName = Application.ExecutablePath;
proc.Verb = "runas";
proc.Arguments = "RunAsAdmin";
try
{
Process.Start(proc);
System.Environment.Exit(2);
return true;
}
catch
{
// The user refused the elevation.
// Do nothing and return directly ...
return false;
}
The problem is a new instance starts but is closed because of the mutex. Then when I close the original instance I get this:
So I was thinking I could pass an argument to the running instance to tell it to allow a new one to spawn, then close the original. Trouble is I cant figure out how to make this work.
Any help would be very much appreciated :)
p.s. I am a C# novice
Thanks!
The solution is to simply destroy your mutex prior to restarting your application:
mutex.Dispose();