Verwenden von Speicherzuordnungen mit einem Dienst

8

Ich habe eine Anwendung erstellt, die auch als Service- -serviceSwitch (mit einem ) ausgeführt werden kann. Dies funktioniert einwandfrei, wenn ich den Dienst über eine Eingabeaufforderung ausführe (ich habe etwas eingerichtet, mit dem ich ihn von einer Konsole aus debuggen kann, wenn er nicht als echter Dienst ausgeführt wird). Wenn ich jedoch versuche, es als echten Dienst auszuführen und dann meine Anwendung zum Öffnen der vorhandenen Speicherzuordnung verwende, wird der Fehler angezeigt ...

Die angegebene Datei konnte nicht gefunden werden.

Wie ich es als Dienst oder in der Konsole ausführe:

[STAThread]
static void Main(string[] args)
{
    //Convert all arguments to lower
    args = Array.ConvertAll(args, e => e.ToLower());

    //Create the container object for the settings to be stored
    Settings.Bag = new SettingsBag();

    //Check if we want to run this as a service
    bool runAsService = args.Contains("-service");

    //Check if debugging
    bool debug = Environment.UserInteractive;

    //Catch all unhandled exceptions as well
    if (!debug || debug)
    {
        Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException);
        AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
    }

    if (runAsService)
    {
        //Create service array
        ServiceBase[] ServicesToRun;
        ServicesToRun = new ServiceBase[]
        {
            new CRSService()
        };

        //Run services in interactive mode if needed
        if (debug)
            RunInteractive(ServicesToRun);
        else
            ServiceBase.Run(ServicesToRun);
    }
    else
    {
        //Start the main gui
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        Application.Run(new MainGUI());
    }
}

In meiner Anwendung habe ich eine Service- und eine Anwendungsseite. Der Zweck der Anwendung besteht lediglich darin, den Dienst zu steuern. Ich mache die gesamte Steuerung mithilfe von Speicherzuordnungsdateien und es scheint großartig zu funktionieren und entspricht meinen Anforderungen. Wenn ich die Anwendung jedoch als echten Dienst ausführe, der aus meinen Debug-Protokollen hervorgeht, wird die Speicherzuordnungsdatei mit dem richtigen Namen und den richtigen Zugriffseinstellungen erstellt. Ich kann auch sehen, dass die Datei dort erstellt wird, wo sie sein sollte. Im Service scheint alles genauso zu funktionieren wie beim Debuggen über die Konsole. Meine Anwendung (wenn sie als Anwendung anstelle des Dienstes ausgeführt wird) teilt mir jedoch mit, dass die Speicherzuordnungsdatei nicht gefunden werden kann. Ich habe es auch den Dateinamenpfad in den Fehler werfen, damit ich weiß, dass es an der richtigen Stelle sucht.

Wie öffne ich die Speicherzuordnung (wo der Fehler ausgelöst wird):

m_mmf = MemoryMappedFile.OpenExisting(
    m_sMapName,
    MemoryMappedFileRights.ReadWrite
);

Hinweis: Der Dienst wird als dasselbe Konto ausgeführt, in dem ich Visual Studio ausgeführt habe. Das folgende Bild zeigt als Beispiel meinen Task-Manager, die GUI services.msc und mein aktuell identifiziertes Konto.

Geben Sie hier die Bildbeschreibung ein

Wie kann ich meine Clientanwendung dazu bringen, die Speicherzuordnungsdatei anzuzeigen, nachdem der Dienst sie erstellt hat? Warum funktioniert es, wenn ich es als Konsolendienst ausführe und nicht, wenn ich es als echten Dienst ausführe?

Arvo Bowen
quelle
@ Amy Ich würde denken, dass die Version von Visual Studio wichtig ist, da ich nichts höheres als VS2017 verwenden kann. Wenn beispielsweise eine Antwort mit VS2019, jedoch nicht mit 2017 funktionieren könnte, wird die Antwort nicht akzeptiert. Hoffe das macht Sinn.
Arvo Bowen
"Ich kann nichts Höheres als VS2017 verwenden" ist kein Grund zu der Annahme, dass VS dafür verantwortlich ist. Welche Version von C # und das .Net Framework verwenden Sie? Fügen Sie diese Ihrer Frage hinzu. Die VS-Tags sollten nur angewendet werden, wenn es sich um VS handelt.
Amy
Unter welchem ​​Konto ist der Dienst registriert, unter dem er ausgeführt werden soll? Wenn es sich um das Systemkonto handelt, besteht das Problem wahrscheinlich darin, dass die Handles und Namen vor einem Benutzerkonto geschützt sind, um darauf zuzugreifen.
Joel Lucsy
3
Haben Sie dem Namen Global wie "Global \\ MyName" vorangestellt? Sie können dies anhand
Joel Lucsy
2
Nun, das ist das Problem. Der Name muss @ "Global \ myappsvr_mm_toggles" sein, damit es funktioniert, wenn Sie es als Dienst ausführen. Dienste werden in einer anderen Sitzung als die angemeldete Benutzersitzung ausgeführt. Windows hält Namespaces für Kernelobjekte für Sitzungen isoliert, damit sie sich nicht gegenseitig stören können. Es sei denn, Sie entscheiden sich für den globalen Namespace. Es hat funktioniert, als Sie es getestet haben, da beide Programme in derselben Sitzung ausgeführt wurden.
Hans Passant

Antworten:

5

Windows-Dienste werden isoliert in der Sitzung ausgeführt 0, während Ihre Konsolenanwendung in einer Benutzersitzung ausgeführt wird. Damit sie miteinander kommunizieren können, muss die Speicherzuordnungsdatei im Global\Namespace erstellt werden, damit sie für andere Sitzungen zugänglich ist. z.B

var file = MemoryMappedFile.CreateOrOpen(@"Global\MyMemoryMappedFile", ...

Sie sollten auch die entsprechenden Berechtigungen für die Datei festlegen, um sicherzustellen, dass alle Benutzer darauf zugreifen können.

Ich würde empfehlen, diesen Beitrag zu lesen. Implementieren von nicht persistenten Speicherzuordnungsdateien Offenlegen der IPC-Kommunikation mit Windows-Diensten. In diesem Beitrag werden die obigen Informationen ausführlicher erläutert und Beispiele zum Festlegen der Berechtigungen usw.


Quellcode aus dem oben verlinkten Beitrag kopiert:

Erstellung von Mutex-, Mutex-Sicherheits- und MMF-Sicherheitsrichtlinien

bool mutexCreated;
Mutex mutex;
MutexSecurity mutexSecurity = new MutexSecurity();
MemoryMappedFileSecurity mmfSecurity = new MemoryMappedFileSecurity();

mutexSecurity.AddAccessRule(new MutexAccessRule(new SecurityIdentifier(WellKnownSidType.WorldSid, null), 
MutexRights.Synchronize | MutexRights.Modify, AccessControlType.Allow));
mmfSecurity.AddAccessRule(new AccessRule<MemoryMappedFileRights>("everyone", MemoryMappedFileRights.FullControl, 
AccessControlType.Allow));

mutex = new Mutex(false, @"Global\MyMutex", out mutexCreated, mutexSecurity);
if (mutexCreated == false) log.DebugFormat("There has been an error creating the mutex"); 
else log.DebugFormat("mutex created successfully");

Erstellen und Schreiben in den MMF

MemoryMappedFile file = MemoryMappedFile.CreateOrOpen(@"Global\MyMemoryMappedFile", 4096, 
MemoryMappedFileAccess.ReadWrite, MemoryMappedFileOptions.DelayAllocatePages, mmfSecurity, 
HandleInheritability.Inheritable);

using (MemoryMappedViewAccessor accessor = file.CreateViewAccessor()) {
   string xmlData = SerializeToXml(CurrentJobQueue) + "\0"; // \0 terminates the XML to stop badly formed 
issues when the next string written is shorter than the current

    byte[] buffer = ConvertStringToByteArray(xmlData);
    mutex.WaitOne();
    accessor.WriteArray<byte>(0, buffer, 0, buffer.Length);
    mutex.ReleaseMutex();
    }

Lesen aus dem Geldmarktfonds

using (MemoryMappedFile file = MemoryMappedFile.OpenExisting(
   @"Global\MyMemoryMappedFile", MemoryMappedFileRights.Read)) {

     using (MemoryMappedViewAccessor accessor =
         file.CreateViewAccessor(0, 0, MemoryMappedFileAccess.Read)) {
         byte[] buffer = new byte[accessor.Capacity];

         Mutex mutex = Mutex.OpenExisting(@"Global\MyMutex");
         mutex.WaitOne();
         accessor.ReadArray<byte>(0, buffer, 0, buffer.Length);
         mutex.ReleaseMutex();

         string xmlData = ConvertByteArrayToString(buffer);
         data = DeserializeFromXML(xmlData);
       }
C. Augusto Proiete
quelle