Usar mapas de memoria con un servicio

8

Creé una aplicación que también se puede ejecutar como un -serviceconmutador de servicio (usando un ). Esto funciona perfectamente sin problemas cuando estoy ejecutando el servicio desde un símbolo del sistema (tengo algo configurado que me permite depurarlo desde una consola cuando no se ejecuta como un verdadero servicio). Sin embargo, cuando intento ejecutarlo como un verdadero servicio y luego uso mi aplicación para abrir el mapa de memoria existente, aparece el error ...

No se puede encontrar el archivo especificado.

Cómo lo ejecuto como un servicio o en la consola:

[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());
    }
}

En mi aplicación tengo un lado de servicio y un lado de aplicación. El propósito de la aplicación es solo controlar el servicio. Realizo todo el control usando archivos de mapeo de memoria y parece funcionar muy bien y se adapta a mis necesidades. Sin embargo, cuando ejecuto la aplicación como un verdadero servicio, veo en mis registros de depuración que está creando el archivo de mapa de memoria con el nombre correcto y la configuración de acceso. También puedo ver el archivo que se crea donde debería estar. Todo parece funcionar exactamente igual en el servicio que cuando depuro a través de la consola. Sin embargo, mi aplicación (cuando se ejecuta como una aplicación en lugar del servicio) me dice que no puede encontrar el archivo de mapa de memoria. También tengo que arrojar la ruta del nombre del archivo en el error, así que sé que está buscando en el lugar correcto.

Cómo abro el mapa de memoria (donde se produce el error):

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

Nota: El servicio se ejecuta con la misma cuenta en la que ejecuto Visual Studio. Como ejemplo, la siguiente imagen muestra mi administrador de tareas, la interfaz gráfica de usuario services.msc y mi cuenta actualmente identificada.

ingrese la descripción de la imagen aquí

¿Cómo puedo hacer que mi aplicación cliente vea el archivo de mapa de memoria después de que el servicio lo cree? ¿Por qué funciona cuando lo ejecuto como un servicio de consola y no cuando lo ejecuto como un verdadero servicio?

Arvo Bowen
fuente
@ Amy Creo que la versión de Visual Studio es importante porque no puedo usar nada más alto que VS2017. Por ejemplo, si una respuesta pudiera funcionar con VS2019 pero no 2017, entonces la respuesta no sería aceptada. Espero que tenga sentido.
Arvo Bowen
"No puedo usar nada más alto que VS2017" no es una razón para sospechar que VS es responsable. ¿Qué versión de C # y .Net Framework está utilizando? Añádelos a tu pregunta. Las etiquetas VS solo deben aplicarse cuando la pregunta es sobre VS.
Amy
¿En qué cuenta está registrado el servicio para ejecutar? Si se trata de la cuenta del sistema, es probable que los identificadores y nombres estén protegidos de una cuenta de usuario para acceder a ellos.
Joel Lucsy
3
¿Has prefijado el nombre con Global como "Global \\ MyName"? puedes ver esto en el ejemplo en docs.microsoft.com/en-us/windows/win32/memory/…
Joel Lucsy
2
Bueno, ese es el problema, su nombre debe ser @ "Global \ myappsvr_mm_toggles" para que funcione cuando lo ejecutas como un servicio. Los servicios se ejecutan en una sesión diferente de la sesión del usuario conectado, Windows mantiene los espacios de nombres para los objetos del núcleo aislados para las sesiones para que no puedan interferir entre sí. A menos que opte por el espacio de nombres global. Funcionó cuando lo probó porque ambos programas se ejecutaron en la misma sesión.
Hans Passant

Respuestas:

5

Los servicios de Windows se ejecutan de forma aislada, en sesión 0, mientras que su aplicación de consola se ejecuta en una sesión de usuario, por lo que para que se comuniquen entre sí, el archivo mapeado de memoria debe crearse en el Global\espacio de nombres, para que sea accesible a otras sesiones. p.ej

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

También debe establecer los permisos apropiados para el archivo, para asegurarse de que todos los usuarios puedan acceder a él.

Recomiendo leer esta publicación Implementando archivos mapeados de memoria no persistente que exponen las comunicaciones de estilo IPC con los servicios de Windows , que explica lo anterior con mucho más detalle y tiene ejemplos sobre cómo configurar los permisos, etc.


Código fuente copiado de la publicación vinculada anteriormente:

Creación de políticas de seguridad Mutex, Mutex Security y MMF

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");

Crear y escribir en el 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();
    }

Lectura del MMF

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
fuente