Layer1
Tags: win10, csharp, service, pinvoke
Published: 2018-09-05 01:11:02

---
title: Layer1
published: true
tags: win10,csharp,service,pinvoke
canonical_url: 
---

[I’ve covered “layer0”](./more-windows-service-in-c-3keg), a Windows service that among other things spawns and monitors a process as the logged in user- “layer1”. It only makes sense to talk about layer1.

## Session Events

We want to detect when the user is logging out so we can have layer1 exit cleanly and layer0 not try to restart it. There’s two parts to this:

1. Detecting that the user session is coming to an end
1. Returning an exit code for [`GetExitCodeProcess()`](https://docs.microsoft.com/en-us/windows/desktop/api/processthreadsapi/nf-processthreadsapi-getexitcodeprocess)

```csharp
static L1ExitCode ExitCode = L1ExitCode.RestartLayer1;

static int Main(string[] args)
{
    try
    {
        // These events only work if we have a Windows message pump
        Microsoft.Win32.SystemEvents.SessionEnding += SessionEnding;
        Microsoft.Win32.SystemEvents.SessionEnded += SessionEnded;
        Microsoft.Win32.SystemEvents.SessionSwitch += SessionSwitch;

        RunLayer1();
    }
    finally
    {
        Microsoft.Win32.SystemEvents.SessionEnding -= SessionEnding;
        Microsoft.Win32.SystemEvents.SessionEnded -= SessionEnded;
        Microsoft.Win32.SystemEvents.SessionSwitch -= SessionSwitch;
    }
    return (int)ExitCode;
}

static void SessionEnding(object sender, Microsoft.Win32.SessionEndingEventArgs args) => sessionEvent(SessionEvent.SessionEnding);
static void SessionEnded(object sender, Microsoft.Win32.SessionEndedEventArgs args) => sessionEvent(SessionEvent.SessionEnded);
static void SessionSwitch(object sender, Microsoft.Win32.SessionSwitchEventArgs args) => sessionEvent(SessionEvent.SessionSwitch);

static void sessionEvent(SessionEvent sessionEvent)
{
    switch(sessionEvent)
    {
        case SessionEvent.SessionEnded:
        case SessionEvent.SessionEnding:
            ExitCode = L1ExitCode.UserLoggingOut;
            // Trigger layer1 shutdown
            break;
        case SessionEvent.SessionSwitch:
        default:
            // Do nothing
            break;
    }
}
```

When a user logs out, first `SessionEvent.SessionEnding` occurs followed shortly by `SessionEvent.SessionEnded`.

The [docs for `Microsoft.Win32.SystemEvents`](https://docs.microsoft.com/en-us/dotnet/api/microsoft.win32.systemevents) state the events we’re interested in are only raised if a message pump is running. It just so happens that layer1 has to have a message loop.

## Message Loop

One of the oddities of our platform is that the [EC](https://en.wikipedia.org/wiki/Embedded_controller) driver delivers power button presses as low-level keyboard events.

Install hook and start message loop:
```csharp
private delegate IntPtr LowLevelKeyboardProc(int nCode, IntPtr wp, IntPtr lp);
LowLevelKeyboardProc lpfn;
IntPtr hHook;

bool setHWInterface2()
{
    cts = new CancellationTokenSource();
    keyboardHookProcTask = Task.Factory.StartNew((_) =>
    {
        // Need to create delegate that won't get GC'd
        lpfn = new LowLevelKeyboardProc(KeyboardHookProc);

        var moduleHandle = GetModuleHandle(null);
        //using (var curModule = System.Diagnostics.Process.GetCurrentProcess().MainModule)
        //{
        // var moduleHandle = GetModuleHandle(curModule.ModuleName);
        // // SetWindowsHookEx()
        //}
        hHook = SetWindowsHookEx(WH_KEYBOARD_LL, lpfn, moduleHandle, 0);
        if (hHook == IntPtr.Zero)
        {
            Log("SetWindowsHookEx failed", LogLevel.Error);
        }
        else
        {
            // SetWindowsHookEx requires a Windows message loop on the thread

            winMsgLoopAppCtx = new ApplicationContext();
            Application.Run(winMsgLoopAppCtx);
            // Using Application.DoEvents() seems cleaner, but hook function wasn't getting called
            //while (!cts.IsCancellationRequested)
            //{
            // Application.DoEvents();
            // // Sleep long enough this thread rarely runs, but short enough the button will be responsive
            // await Task.Delay(250, cts.Token);
            //}

            if (!UnhookWindowsHookEx(hHook))
                Log("UnhookWindowsHookEx failed", LogLevel.Warn);
        }
    }, cts.Token, TaskCreationOptions.LongRunning);
    return true;
}
```

`lpfn = new LowLevelKeyboardProc(KeyboardHookProc)` to keep a reference to the delegate is interesting because without it you can end up with the runtime exception: [`A callback was made on a garbage collected delegate`](https://stackoverflow.com/questions/6193711/call-has-been-made-on-garbage-collected-delegate-in-c).

Use [SetWindowsHookEx](https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-setwindowshookexw) to register a callback when `WH_KEYBOARD_LL` event occurs.

We create and pass an [ApplicationContext](https://docs.microsoft.com/en-us/dotnet/api/system.windows.forms.applicationcontext) to [Application.Run()](https://docs.microsoft.com/en-us/dotnet/api/system.windows.forms.application.run#System_Windows_Forms_Application_Run_System_Windows_Forms_ApplicationContext_) to create our message loop. To later stop it:

```csharp
winMsgLoopAppCtx.ExitThread();
```

It might also be possible to use [`Application.DoEvents()`](https://docs.microsoft.com/en-us/dotnet/api/system.windows.forms.application.doevents?redirectedfrom=MSDN&view=netframework-4.7.2#System_Windows_Forms_Application_DoEvents), but we weren’t able to get that working.

Our hook callback is based off [pinvoke.net sample](https://www.pinvoke.net/default.aspx/Structures/KBDLLHOOKSTRUCT.html):

```csharp
IntPtr KeyboardHookProc(int code, IntPtr wParam, IntPtr lParam)
{
    if (code >= 0)
    {
        var kbScan = (KBDLLHOOKSTRUCT)Marshal.PtrToStructure(lParam, typeof(KBDLLHOOKSTRUCT));

        // Filter spurious power button events, play audio feedback via PC speaker (also in EC driver), etc.
    }

    // Ensure other applications that have installed hooks receive hook notifications
    return CallNextHookEx(hHook, code, wParam, lParam);
}
```

There’s additional details in [LowLevelKeyboardProc](https://msdn.microsoft.com/en-us/library/ms644985%28v=VS.85%29.aspx?f=255&MSPPError=-2147217396).

## Big Red Button

During development both layer0 and layer1 are usually running as console applications. Because [layer0 will restart layer1](./more-windows-service-in-c-3keg), we originally had to stop layer0 then stop layer1. This was pretty annoying so we changed it so both close if either windows is closed:

```csharp
static OS.Pinvoke.ConsoleCtrlDelegate ConsoleCtrlDelegate;

public static void StartLayerN(ILayerN layer)
{
    // If user hits ctrl-c or closes console window, shut everything down
    ConsoleCtrlDelegate += ctrlTypes =>
    {
        switch (ctrlTypes)
        {
            case OS.Pinvoke.CtrlTypes.CTRL_C_EVENT:
            case OS.Pinvoke.CtrlTypes.CTRL_CLOSE_EVENT:
                // Tell layer0 and layer1 to exit
                
                // Need to wait here otherwise OS terminates the process (without waiting for layer1, etc.)
                layer.Wait();
                // We handled the signal and thus return true as per https://docs.microsoft.com/en-us/windows/console/handlerroutine
                return true;
        }
        return false;
    };
    OS.Pinvoke.SetConsoleCtrlHandler(ConsoleCtrlDelegate, true);

    //...
}

```

## Wrapping Up

This project has given us many opportunities to wade through the thicket of Windows APIs. I can’t say it’s all been enjoyable, but it’s certainly been educational.

Will probably do a few more posts in the future- there’s just so many amusing (and convoluted) things you can do on Windows. But, I think I’ve done enough for the time being.