Встала вот такая проблема:
Есть приложение, которое показывает некий график. Параметры ему передаются в командной строке.
Необходимо сделать так, чтобы при повторном запуске, копия передавала новые параметры в уже запущенное приложение и завершало работу.
bool createdNew;
string mutName = "TrendView";
var mut = new System.Threading.Mutex(true, mutName, out createdNew);
if (!createdNew)
{
//тут нужно передать параметры в уже работающую программу.
Shutdown();
}
Какой механизм такое позволяет. Параметры — это string и int;
Здравствуйте, MAMOHT, Вы писали:
MAM>Добрый день, уважаемые.
MAM>Встала вот такая проблема: MAM>Есть приложение, которое показывает некий график. Параметры ему передаются в командной строке. MAM>Необходимо сделать так, чтобы при повторном запуске, копия передавала новые параметры в уже запущенное приложение и завершало работу.
MAM>
MAM> bool createdNew;
MAM> string mutName = "TrendView";
MAM> var mut = new System.Threading.Mutex(true, mutName, out createdNew);
MAM> if (!createdNew)
MAM> {
MAM> //тут нужно передать параметры в уже работающую программу.
MAM> Shutdown();
MAM> }
MAM>
MAM>Какой механизм такое позволяет. Параметры — это string и int;
MAM>P.S. Если это важно, то WPF.
MAM>Спасибо.
Запаковать в массив байт.
Затем использовать WM_COPYDATA
Здравствуйте, MAMOHT, Вы писали:
MAM>Необходимо сделать так, чтобы при повторном запуске, копия передавала новые параметры в уже запущенное приложение и завершало работу.
Схематически (winforms, делал лет 10 назад, лень разбираться):
— попробовать присвоить Mutex, получилось — сервер, не получилось — клиент.
— сервер подписывается на
protected override void WndProc(ref Message m)
{
// get data
Filter.GetData(ref m, this);
base.WndProc(ref m);
}
где
public static void GetData(ref Message m, FormMain form)
{
// if not registered yet
if (_message == 0)
_message = RegisterWindowMessage("SomeUniqueMessageString");
// if registered message incoming
if (m.Msg == _message)
{
// generate file name
string fileName = "Lalala" + m.WParam.ToString() + m.LParam.ToString();
// get data
IntPtr fileMap = OpenFileMapping(FILE_MAP_READ, false, fileName);
IntPtr buf = MapViewOfFile(fileMap, FILE_MAP_READ, 0, 0, 0);
// read
XmlDocument document = new XmlDocument();
document.LoadXml(Marshal.PtrToStringAnsi(buf));
...
// finalize
UnmapViewOfFile(buf);
CloseHandle(fileMap);
}
}
- клиент шлет как-то так
public static void SendData(string file)
{
// find window
hWnd = FindWindow(null, "MyAppIsTheBest");
if (hWnd != IntPtr.Zero)
{
// if not registered yet
if (_message == 0)
_message = RegisterWindowMessage("SomeUniqueMessageString");
// generate filename
Random random = new Random();
IntPtr lparam = (IntPtr)random.NextDouble();
IntPtr wparam = (IntPtr)random.NextDouble();
string fileName = "Lalala" + wparam.ToString() + lparam.ToString();
// open file and map it
FileStream stream = File.Open(file, FileMode.Open, FileAccess.Read, FileShare.Read);
IntPtr fileMap = CreateFileMapping(stream.Handle, IntPtr.Zero, PAGE_READONLY, 0, 0, fileName);
// send notification message
SendMessage(hWnd, _message, wparam, lparam);
// close
UnmapViewOfFile(fileMap);
CloseHandle(fileMap);
stream.Close();
// delete file
while (File.Exists(file))
{
try { File.Delete(file); }
catch { }
}
}
Вроде бы как клиент создает файл (содержащий данные для передачи), мапит его и шлет мессагу (содержащую имя файла) серверу. Сервер тоже мапит, считывает, обрабатывает. Все DllImport искать на PInvoke.
Здравствуйте, MAMOHT, Вы писали:
MAM>Добрый день, уважаемые.
MAM>Встала вот такая проблема: MAM>Есть приложение, которое показывает некий график. Параметры ему передаются в командной строке. MAM>Необходимо сделать так, чтобы при повторном запуске, копия передавала новые параметры в уже запущенное приложение и завершало работу.
MAM>
MAM> bool createdNew;
MAM> string mutName = "TrendView";
MAM> var mut = new System.Threading.Mutex(true, mutName, out createdNew);
MAM> if (!createdNew)
MAM> {
MAM> //тут нужно передать параметры в уже работающую программу.
MAM> Shutdown();
MAM> }
MAM>
MAM>Какой механизм такое позволяет. Параметры — это string и int;
MAM>P.S. Если это важно, то WPF.
MAM>Спасибо.
Я через файл как то делал. Приложение при запуске проверяет есть ли уже запущенный экземпляр, если есть,
то создает файл с параметрами запуска в определенной папке, другой экземпляр получает уведомление об этом у FileSystemWatcher и считывает параметры.
Здравствуйте, Rinbe, Вы писали:
R>Я через файл как то делал. Приложение при запуске проверяет есть ли уже запущенный экземпляр, если есть, R>то создает файл с параметрами запуска в определенной папке, другой экземпляр получает уведомление об этом у R>FileSystemWatcher и считывает параметры.
Единственная поправка — не стоит делать через FileSystemWatcher. При большой нагрузке на диск имеет свойство пропадать события (Microsoft не гарантирует). Проще через именованный Event
Здравствуйте, GlebZ, Вы писали:
GZ>Здравствуйте, Rinbe, Вы писали:
R>>Я через файл как то делал. Приложение при запуске проверяет есть ли уже запущенный экземпляр, если есть, R>>то создает файл с параметрами запуска в определенной папке, другой экземпляр получает уведомление об этом у R>>FileSystemWatcher и считывает параметры. GZ>Единственная поправка — не стоит делать через FileSystemWatcher. При большой нагрузке на диск имеет свойство пропадать события (Microsoft не гарантирует). Проще через именованный Event
Да такое может быть но на практике на обычном детскопе не наблюдалось.
Здравствуйте, MAMOHT, Вы писали:
MAM>Есть приложение, которое показывает некий график. Параметры ему передаются в командной строке. MAM>Необходимо сделать так, чтобы при повторном запуске, копия передавала новые параметры в уже запущенное приложение и завершало работу.
Я думаю, проще всего сделать это с помощью пайпов — pipes.
Здравствуйте, hardcase, Вы писали:
K>>Я думаю, проще всего сделать это с помощью пайпов — pipes. H>Они же заменят мутекс\семафор.
Только писанины много, да и сама концепция пайпов несколько необычная, со своими прибаутками. WCF поднять быстрее.
Здравствуйте, GlebZ, Вы писали:
GZ>Только писанины много, да и сама концепция пайпов несколько необычная, со своими прибаутками. WCF поднять быстрее.
А какой WCF binding для такого сценария лучше всего подойдет (чтобы не требовал настроек системы и раздачи каких-то особенных прав процессам/файлам и т.п.) ?
A>А какой WCF binding для такого сценария лучше всего подойдет (чтобы не требовал настроек системы и раздачи каких-то особенных прав процессам/файлам и т.п.) ?
NamedPipeBinding?
Здравствуйте, MAMOHT, Вы писали: MAM>Встала вот такая проблема: MAM>Есть приложение, которое показывает некий график. Параметры ему передаются в командной строке. MAM>Необходимо сделать так, чтобы при повторном запуске, копия передавала новые параметры в уже запущенное приложение и завершало работу.
Меня не покидает ощущение, что я уже выкладывал этот код.
SingleInstance.cs
namespace WpfApp.Configuration
{
using System;
using System.Collections;
using System.Collections.Generic;
using System.Deployment.Application;
using System.IO;
using System.Linq;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Ipc;
using System.Runtime.Serialization.Formatters;
using System.Threading;
using System.Windows;
using System.Windows.Threading;
public interface ISingleInstanceApp
{
void ActivateInstance(IList<string> args);
}
/// <summary>
/// This class checks to make sure that only one instance of
/// this application is running at a time.
/// </summary>public static class SingleInstance<TApplication>
where TApplication : Application, ISingleInstanceApp
{
/// <summary>
/// Suffix to the channel name.
/// </summary>private const string ChannelNameSuffix = "SingeInstanceIpcChannel";
/// <summary>
/// String delimiter used in channel names.
/// </summary>private const string Delimiter = ":";
/// <summary>
/// IPC protocol used (string).
/// </summary>private const string IpcProtocol = "ipc://";
/// <summary>
/// Remote service name.
/// </summary>private const string RemoteServiceName = "SingleInstanceApplicationService";
/// <summary>
/// IPC channel for communications.
/// </summary>private static IpcServerChannel _channel;
private static IList<string> _commandLineArgs;
/// <summary>
/// Application mutex.
/// </summary>private static Mutex _singleInstanceMutex;
public static IList<string> CommandLineArgs
{
get
{
return _commandLineArgs;
}
}
/// <summary>
/// Cleans up single-instance code, clearing shared resources, mutexes, etc.
/// </summary>public static void Cleanup()
{
if (_singleInstanceMutex != null)
{
_singleInstanceMutex.Close();
_singleInstanceMutex = null;
}
if (_channel != null)
{
ChannelServices.UnregisterChannel(_channel);
_channel = null;
}
}
/// <summary>
/// Checks if the instance of the application attempting to start is the first instance.
/// If not, activates the first instance.
/// </summary>
/// <returns>True if this is the first instance of the application.</returns>public static bool InitializeAsFirstInstance(string uniqueName)
{
_commandLineArgs = GetCommandLineArgs();
// Build unique application Id and the IPC channel name.string applicationIdentifier = uniqueName + Environment.UserName;
string channelName = String.Concat(applicationIdentifier, Delimiter, ChannelNameSuffix);
// Create mutex based on unique application Id to check if this is the first instance of the application.bool firstInstance;
_singleInstanceMutex = new Mutex(true, applicationIdentifier, out firstInstance);
if (firstInstance)
{
CreateRemoteService(channelName);
}
else
{
SignalFirstInstance(channelName, _commandLineArgs);
}
return firstInstance;
}
#region Private Methods
/// <summary>
/// Activates the first instance of the application with arguments from a second instance.
/// </summary>
/// <param name="args">List of arguments to supply the first instance of the application.</param>private static void ActivateFirstInstance(IList<string> args)
{
// Set main window state and process command line argsif (Application.Current == null)
{
return;
}
((TApplication)Application.Current).ActivateInstance(args);
}
/// <summary>
/// Callback for activating first instance of the application.
/// </summary>
/// <param name="arg">Callback argument.</param>
/// <returns>Always null.</returns>private static object ActivateFirstInstanceCallback(object arg)
{
// Get command line args to be passed to first instance
IList<string> args = arg as IList<string>;
ActivateFirstInstance(args);
return null;
}
/// <summary>
/// Creates a remote service for communication.
/// </summary>
/// <param name="channelName">Application's IPC channel name.</param>private static void CreateRemoteService(string channelName)
{
BinaryServerFormatterSinkProvider serverProvider = new BinaryServerFormatterSinkProvider();
serverProvider.TypeFilterLevel = TypeFilterLevel.Full;
IDictionary props = new Dictionary<string, string>();
props["name"] = channelName;
props["portName"] = channelName;
props["exclusiveAddressUse"] = "false";
// Create the IPC Server channel with the channel properties
_channel = new IpcServerChannel(props, serverProvider);
// Register the channel with the channel services
ChannelServices.RegisterChannel(_channel, true);
// Expose the remote service with the REMOTE_SERVICE_NAME
IpcRemoteService remoteService = new IpcRemoteService();
RemotingServices.Marshal(remoteService, RemoteServiceName);
}
/// <summary>
/// Gets command line arguments - for ClickOnce deployed applications,
/// command line arguments may not be passed directly, they have to be retrieved.
/// </summary>
/// <returns>List of command line argument strings.</returns>private static IList<string> GetCommandLineArgs()
{
IEnumerable<string> args = null;
if (AppDomain.CurrentDomain.ActivationContext == null)
{
// The application was not ClickOnce deployed, get arguments from standard APIs
// Skip the executable file name
args = Environment.GetCommandLineArgs().Skip(1);
}
else if (ApplicationDeployment.IsNetworkDeployed)
{
// The application was ClickOnce deployed
// ClickOnce deployed apps cannot receive traditional command line argumentsif (AppDomain.CurrentDomain.SetupInformation.ActivationArguments != null)
{
args = AppDomain.CurrentDomain.SetupInformation.ActivationArguments.ActivationData;
}
}
if (args == null)
{
args = Enumerable.Empty<string>();
}
return args.ToArray();
}
/// <summary>
/// Creates a client channel and obtains a reference to the remoting service exposed by the server -
/// in this case, the remoting service exposed by the first instance. Calls a function of the remoting service
/// class from the second instance to the first and cause it to activate itself.
/// </summary>
/// <param name="channelName">Application's IPC channel name.</param>
/// <param name="args">
/// Command line arguments for the second instance, passed to the first instance to take appropriate action.
/// </param>private static void SignalFirstInstance(string channelName, IList<string> args)
{
IpcClientChannel secondInstanceChannel = new IpcClientChannel();
ChannelServices.RegisterChannel(secondInstanceChannel, true);
string remotingServiceUrl = IpcProtocol + channelName + "/" + RemoteServiceName;
// Obtain a reference to the remoting service exposed by the server i.e the first instance of the application
IpcRemoteService firstInstanceRemoteServiceReference = (IpcRemoteService)RemotingServices.Connect(typeof(IpcRemoteService), remotingServiceUrl);
// Check that the remote service exists, in some cases the first instance may not yet have created one, in which case
// the second instance should just exitif (firstInstanceRemoteServiceReference != null)
{
// Invoke a method of the remote service exposed by the first instance passing on the command line
// arguments and causing the first instance to activate itself
firstInstanceRemoteServiceReference.InvokeFirstInstance(args);
}
}
#endregion Private Methods
#region Private Classes
/// <summary>
/// Remoting service class which is exposed by the server i.e the first instance and called by the second instance
/// to cause the first instance to activate itself.
/// </summary>private class IpcRemoteService : MarshalByRefObject
{
/// <summary>
/// Remoting Object's ease expires after every 5 minutes by default. We need to override the InitializeLifetimeService class
/// to ensure that lease never expires.
/// </summary>
/// <returns>Always null.</returns>public override object InitializeLifetimeService()
{
return null;
}
/// <summary>
/// Activates the first instance of the application.
/// </summary>public void InvokeFirstInstance(IList<string> args)
{
if (Application.Current != null)
{
// Do an asynchronous call to ActivateFirstInstance function
Application.Current.Dispatcher.BeginInvoke(
DispatcherPriority.Normal, new DispatcherOperationCallback(SingleInstance<TApplication>.ActivateFirstInstanceCallback), args);
}
}
}
#endregion Private Classes
}
}
Как использовать в WPF:
namespace WpfApp
{
// ...
/// <summary>
/// Interaction logic for App.xaml
/// </summary>public partial class App : Application, ISingleInstanceApp
{
private const string InstanceKey = "WpfApp_InstanceKey";
[STAThread]
public static void Main()
{
if (SingleInstance<App>.InitializeAsFirstInstance(InstanceKey))
{
var application = new App();
application.InitializeComponent();
application.Run();
// Allow single instance code to perform cleanup operations
SingleInstance<App>.Cleanup();
}
}
void ISingleInstanceApp.ActivateInstance(IList<string> args)
{
// Someone tried to run the app again
// 'args' has the command line arguments
}
// ...
}
}
Код был найден где-то на просторах сети и усовершенствован для корректной работы в окружении ClickOnce. Работает!
Здравствуйте, GlebZ, Вы писали:
GZ>Единственная поправка — не стоит делать через FileSystemWatcher. При большой нагрузке на диск имеет свойство пропадать события (Microsoft не гарантирует).
Здравствуйте, Draqon, Вы писали:
BB>>А откуда инфа, что Microsoft не гарантирует?
D>Из MSDN. FileSystemWatcher использует ReadDirectoryChangesW, а про нее в MSDN написано что она может терять события.
В изначальном посте говорилось о потерях при "большой нагрузке на диск", а в MSDN вижу только про потери при переполнении буфера. Поэтому, если создать файл в отдельном каталоге и использовать фильтр FILE_NOTIFY_CHANGE_LAST_WRITE, то потерь вроде не должно быть
Другое дело, что нет гарантий, что при приходе события "пишущий" процесс закончил работу с файлом — зафлашил весь текст и закрыл его. Поэтому я бы предпочел писать в реестр.