winformsプログラムとしても実行できるC#サービスを作成する方法は?
-
05-07-2019 - |
質問
C#で記述されたWindowsサービスがあり、バックエンドデータベースへの多数のネットワークデバイスのプロキシとして機能します。テストのために、またバックエンドをテストするためにシミュレーションレイヤーを追加するには、テストオペレーターがシミュレーションを実行できるようにするためのGUIが必要です。また、デモ版として送信するためのストライプバージョンです。 GUIとサービスを同時に実行する必要はありません。この決闘操作を達成するための最良の方法は何ですか?
編集: この質問、サービスとして実行していますかおよび この優れたコード マーク・グラヴェル
次の行を使用して、GUIを実行するかサービスとして実行するかをテストします。
if (arg_gui || Environment.UserInteractive || Debugger.IsAttached)
コードは次のとおりです。
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
using System.ComponentModel;
using System.ServiceProcess;
using System.Configuration.Install;
using System.Diagnostics;
namespace Form_Service
{
static class Program
{
///
/// The main entry point for the application.
///
[STAThread]
static int Main(string[] args)
{
bool arg_install = false;
bool arg_uninstall = false;
bool arg_gui = false;
bool rethrow = false;
try
{
foreach (string arg in args)
{
switch (arg)
{
case "-i":
case "-install":
arg_install = true; break;
case "-u":
case "-uninstall":
arg_uninstall = true; break;
case "-g":
case "-gui":
arg_gui = true; break;
default:
Console.Error.WriteLine("Argument not expected: " + arg);
break;
}
}
if (arg_uninstall)
{
Install(true, args);
}
if (arg_install)
{
Install(false, args);
}
if (!(arg_install || arg_uninstall))
{
if (arg_gui || Environment.UserInteractive || Debugger.IsAttached)
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());
}
else
{
rethrow = true; // so that windows sees error...
ServiceBase[] services = { new Service1() };
ServiceBase.Run(services);
rethrow = false;
}
}
return 0;
}
catch (Exception ex)
{
if (rethrow) throw;
Console.Error.WriteLine(ex.Message);
return -1;
}
}
static void Install(bool undo, string[] args)
{
try
{
Console.WriteLine(undo ? "uninstalling" : "installing");
using (AssemblyInstaller inst = new AssemblyInstaller(typeof(Program).Assembly, args))
{
IDictionary state = new Hashtable();
inst.UseNewContext = true;
try
{
if (undo)
{
inst.Uninstall(state);
}
else
{
inst.Install(state);
inst.Commit(state);
}
}
catch
{
try
{
inst.Rollback(state);
}
catch { }
throw;
}
}
}
catch (Exception ex)
{
Console.Error.WriteLine(ex.Message);
}
}
}
[RunInstaller(true)]
public sealed class MyServiceInstallerProcess : ServiceProcessInstaller
{
public MyServiceInstallerProcess()
{
this.Account = ServiceAccount.NetworkService;
}
}
[RunInstaller(true)]
public sealed class MyServiceInstaller : ServiceInstaller
{
public MyServiceInstaller()
{
this.Description = "My Service";
this.DisplayName = "My Service";
this.ServiceName = "My Service";
this.StartType = System.ServiceProcess.ServiceStartMode.Manual;
}
}
}
解決
基本的に2つの選択肢があります。 UIアプリから呼び出すことができるサービスでAPIを公開するか、サービスをwinformsアプリまたはサービスとして実行できるようにします。
最初のオプションは非常に簡単です-リモーティングまたはWCFを使用してAPIを公開します。
2番目のオプションは、「ガッツ」を移動することで実現できます。アプリを別のクラスに追加し、サービスラッパーとwin-formsラッパーを作成します。これらは両方とも「ガッツ」を呼び出します。クラス。
static void Main(string[] args)
{
Guts guts = new Guts();
if (runWinForms)
{
System.Windows.Forms.Application.EnableVisualStyles();
System.Windows.Forms.Application.SetCompatibleTextRenderingDefault(false);
FormWrapper fw = new FormWrapper(guts);
System.Windows.Forms.Application.Run(fw);
}
else
{
ServiceBase[] ServicesToRun;
ServicesToRun = new ServiceBase[] { new ServiceWrapper(guts) };
ServiceBase.Run(ServicesToRun);
}
}
他のヒント
サービスのアセンブリを参照する新しいwinformsアプリを作成します。
以下のコードを使用する場合:
[DllImport("advapi32.dll", CharSet=CharSet.Unicode)]
static extern bool StartServiceCtrlDispatcher(IntPtr services);
[DllImport("ntdll.dll", EntryPoint="RtlZeroMemory")]
static extern void ZeroMemory(IntPtr destination, int length);
static bool StartService(){
MySvc svc = new MySvc(); // replace "MySvc" with your service name, of course
typeof(ServiceBase).InvokeMember("Initialize", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.InvokeMethod,
null, svc, new object[]{false});
object entry = typeof(ServiceBase).InvokeMember("GetEntry",
BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.InvokeMethod, null, svc, null);
int len = Marshal.SizeOf(entry) * 2;
IntPtr memory = Marshal.AllocHGlobal(len);
ZeroMemory(memory, len);
Marshal.StructureToPtr(entry, memory, false);
return StartServiceCtrlDispatcher(memory);
}
[STAThread]
static void Main(){
if(StartService())
return;
Application.Run(new MainWnd()); // replace "MainWnd" with whatever your main window is called
}
その後、EXEはサービス(SCMによって起動された場合)またはGUI(他のプロセスによって起動された場合)として実行されます。
本質的に、ここで行ったことはすべて、リフレクターを使用して計算されます。 ServiceBase.Run
の内容ここで複製します(プライベートメソッドを呼び出すため、リフレクションが必要です)。 ServiceBase.Run
を直接呼び出さない理由は、(SCMによって起動されていない場合)サービスを開始できないことを user に伝えるメッセージボックスをポップアップするためです。サービスを開始できないことを code に伝えるために何も返しません。
これはリフレクションを使用してプライベートフレームワークメソッドを呼び出すため、フレームワークの将来のリビジョンでは正しく機能しない可能性があります。 注意事項:
FireDaemon もあります。これにより、Windowsアプリケーションをサービスとして実行できます。
さらに役立つ情報については、サービスとして実行していますかをご覧ください。情報。
最も重要なことは、インタラクティブに実行しているか、サービス経由で実行しているかを確実に判断する方法です。
サービスと通信できる別のプロセスを実装する必要があります。 XP以前のシステムではUIを表示するサービスを使用できますが、Vista以降では使用できなくなりました。
別の可能性は、サービスを使用せずに、タスクバーにあるアプリケーションを使用することです(Roxio Drag-to-Disc、およびおそらくあなたのアンチウイルスソフトウェアがそこに住んでいると考えてください)。右クリックするとメニューを起動し、ダブルクリックするとUIを起動するクロック。
サービスが適切に変調されている場合、サービスとして実行可能ファイルで、またはテスト用のguiを使用して実行可能ファイルでサービスをホストできます。 このメソッドもサービスで使用します。スタンドアロンのサービス実行可能ファイルは本番環境でサービスをホストしますが、サービスをホストするためのコンソールアプリもあります。
コードを異なるコンポーネントに分離します。1つのコンポーネントはサービスの側面を管理し、もう1つは実際のビジネスロジックを実行します。サービスコンポーネントからビジネスロジックを作成して操作します。 (ビジネスロジックの)テストのために、サービスコンポーネントなしでビジネスロジックコンポーネントを使用するWinFormまたはコンソールアプリケーションを作成できます。さらに良いことに、大部分のテストには単体テストフレームワークを使用します。サービスコンポーネントのメソッドの多くは、間違いなくユニットテスト可能です。
ビジネスロジックをサービスクラスにカプセル化し、ファクトリパターンを使用してそれらのサービスを作成する場合、デスクトップアプリケーション(デスクトップファクトリ)とWebサービス(WCFのホスト)に同じサービスセットを使用できます。
サービス定義:
[ServiceContract]
public interface IYourBusinessService
{
[OperationContract]
void DoWork();
}
public class YourBusinessService : IYourBusinessService
{
public void DoWork()
{
//do some business logic here
}
}
ビジネスを行うためのサービスを取得するデスクトップWinFormsのファクトリー:
public class ServiceFactory
{
public static IYourBusinessService GetService()
{
//you can set any addition info here
//like connection string for db, etc.
return new YourBusinessService();
}
}
これは、WCF ServiceHostクラスまたはIISでホストします。どちらも、サービスの各インスタンスをインスタンス化する方法を指定できるため、接続文字列などの初期化を行うことができます。
コマンドライン引数を使用して別の実行可能ファイルを呼び出すサービスを作成して、フォームなしで実行できるようにします。そのexeがコマンドライン引数なしで呼び出されると、フォームが表示され、通常どおり動作します。