Usando mapas de memória com um serviço

8

Criei um aplicativo que também pode ser executado como um serviço (usando uma -serviceopção). Isso funciona perfeitamente sem problemas quando estou executando o serviço em um prompt de comando (eu tenho algo configurado que permite depurá-lo em um console quando não estou sendo executado como um serviço verdadeiro). No entanto, quando tento executá-lo como um serviço verdadeiro, utilizo meu aplicativo para abrir o mapa de memória existente, recebo o erro ...

Não foi possível encontrar o arquivo especificado.

Como eu o executo como um serviço ou no console:

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

No meu aplicativo, tenho um lado de serviço e um lado de aplicativo. O objetivo do aplicativo é apenas controlar o serviço. Eu faço todo o controle usando arquivos de mapeamento de memória e parece funcionar muito bem e atende às minhas necessidades. No entanto, quando executo o aplicativo como um serviço verdadeiro, vejo nos meus logs de depuração, criando o arquivo de mapa de memória com o nome correto e as configurações de acesso. Também posso ver o arquivo sendo criado onde deveria estar. Tudo parece funcionar exatamente da mesma forma no serviço, quando depuro via console. No entanto, meu aplicativo (quando executado como um aplicativo em vez do serviço) informa que não é possível encontrar o arquivo de mapa de memória. Eu tenho que lançar o caminho do nome do arquivo no erro também, então eu sei que ele está procurando no lugar certo.

Como abro o mapa de memória (onde o erro é gerado):

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

Nota: O serviço está sendo executado com a mesma conta em que executo o Visual Studio. Como exemplo, a imagem abaixo mostra meu gerenciador de tarefas, o services.msc gui e minha conta atualmente identificada.

insira a descrição da imagem aqui

Como faço para que meu aplicativo cliente veja o arquivo de mapa de memória depois que o serviço o cria? Por que funciona quando eu o executo como um serviço de console e não quando o executo como um serviço verdadeiro?

Arvo Bowen
fonte
@ Amy: Eu acho que a versão do Visual Studio é importante porque não posso usar nada maior que o VS2017. Por exemplo, se uma resposta pudesse trabalhar com o VS2019, mas não 2017, a resposta não seria aceita. Espero que isso faça sentido.
Arvo Bowen
"Não posso usar nada maior que o VS2017" não é um motivo para suspeitar que o VS é responsável. Qual versão do C # e do .Net Framework você está usando? Adicione-os à sua pergunta. As tags VS só devem ser aplicadas quando a pergunta é sobre VS.
Amy
Em qual conta o serviço está registrado para execução? Se for a conta do sistema, é provável que o problema seja que os identificadores e nomes estejam protegidos de uma conta de usuário para acessá-los.
Joel Lucsy 28/02
3
Você prefixou o nome com Global como "Global \\ MyName"? você pode ver isso no exemplo em docs.microsoft.com/en-us/windows/win32/memory/…
Joel Lucsy
2
Bem, esse é o problema, então, seu nome deve ser @ "Global \ myappsvr_mm_toggles" para fazê-lo funcionar quando você o executa como um serviço. Os serviços são executados em uma sessão diferente da sessão do usuário conectado. O Windows mantém os espaços para nome dos objetos do kernel isolados para as sessões, para que não possam interferir entre si. A menos que você ative o espaço para nome global. Funcionou quando você o testou porque os dois programas foram executados na mesma sessão.
Hans Passant

Respostas:

5

Os Serviços do Windows são executados isoladamente, na Sessão 0, enquanto o aplicativo Console é executado em uma sessão do usuário. Para que eles se comuniquem, o arquivo mapeado de memória deve ser criado no Global\espaço para nome, para torná-lo acessível a outras sessões. por exemplo

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

Você também deve definir as permissões apropriadas para o arquivo, para garantir que todos os usuários possam acessá-lo.

Eu recomendo a leitura desta postagem Implementando arquivos mapeados de memória não persistente, expondo as comunicações no estilo IPC com o Windows Services , que explica o item acima com muito mais detalhes e tem exemplos de configuração de permissões etc.


Código-fonte copiado da postagem vinculada acima:

Criação de políticas de segurança Mutex, Mutex e 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");

Criar e gravar no 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();
    }

Leitura do FMM

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
fonte