使用内存映射与服务

11

我建立了一个应用程序,它也可以作为服务运行(使用-service开关)。当我从命令提示符运行服务时(我设置了一些内容,让我在不作为真正的服务运行时可以从控制台调试它),这个功能完美地工作,没有任何问题。然而,当我尝试将其作为真正的服务运行并使用我的应用程序打开现有的内存映射时,会出现以下错误...

无法找到指定的文件。

如何将其作为服务或在控制台中运行:


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

在我的应用程序中,我有一个服务端和一个应用端。应用的目的仅仅是控制服务端。我使用内存映射文件来进行所有的控制,这似乎非常好,并且符合我的需求。但是当我将应用作为真正的服务运行时,从我的调试日志中我发现它正在使用正确的名称和访问设置创建内存映射文件。我也能看到文件被创建在了正确的位置。一切似乎在服务中与通过控制台进行调试时完全相同。然而,当我将应用程序作为应用程序而不是服务运行时,它告诉我找不到内存映射文件。我让它在错误中输出文件名路径,所以我知道它正在寻找正确的地方。

我如何打开内存映射文件(出现错误的地方):

m_mmf = MemoryMappedFile.OpenExisting(
    m_sMapName,
    MemoryMappedFileRights.ReadWrite
);
注意:该服务运行于与我在Visual Studio中运行的相同账户。例如,下面的图片展示了我的任务管理器、services.msc界面和当前识别的账户。

在此输入图像描述

当服务创建内存映射文件后,如何让客户端应用程序能够看到它?为什么我将其作为控制台服务运行时可以工作,但是将其作为真正的服务运行时却无法工作?


注:原文中的“gui”是Graphical User Interface的缩写,即“图形用户界面”。

@Amy,我认为Visual Studio的版本很重要,因为我不能使用比VS2017更高的版本。例如,如果一个答案能够在VS2019上工作但不能在2017上工作,那么这个答案将不被接受。希望这样说得清楚。 - Arvo Bowen
服务注册在哪个账户下运行?如果是系统账户,问题可能是句柄和名称受保护,无法让用户账户访问它们。 - Joel Lucsy
3
你是否在名称前加了"Global\ ",例如"Global\MyName"?你可以在https://learn.microsoft.com/en-us/windows/win32/memory/creating-named-shared-memory的示例中看到这一点。 - Joel Lucsy
@JoelLucsy 我还更新了问题,并附上了一些屏幕截图,展示了当前使用的帐户。希望这有所帮助。至于全局问题,我感到困惑。那只是一个字符串值(在运行时它是正确的)。如果需要,我可以手动输入内存映射名称并将任何内容放在其中。服务和客户端GUI上运行时的值为myappsvr_mm_toggles。我不确定为什么它需要“跨会话可见”。它实际上是类中硬编码的值,两个实例都会得到。 - Arvo Bowen
2
那么问题就在这里了,它的名称必须是@"Global\myappsvr_mm_toggles",才能在您将其作为服务运行时正常工作。服务在与已登录用户会话不同的会话中运行,Windows 保持内核对象的命名空间隔离以防止它们相互干扰。除非您选择全局命名空间。当您进行测试时,它可以正常工作,因为两个程序在同一会话中运行。 - Hans Passant
显示剩余6条评论
1个回答

8

Windows服务在Session 0中独立运行,而控制台应用程序在用户会话中运行。因此,为了使它们彼此通信,必须在Global\命名空间中创建内存映射文件,以使其可访问其他会话。例如:

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

您还应设置适当的文件权限,以确保所有用户都可以访问它。

我建议阅读此帖子 使用Windows服务实现非持久化内存映射文件通过IPC方式进行通信,其中详细解释了上述内容,并提供了有关设置权限等方面的示例。


从上面链接的帖子中复制的源代码:

Mutex、Mutex安全性和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");

创建并写入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();
    }

从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);
       }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接