Skip to content

[Code Quality] NativeMethods.cs — duplicate Win32 status struct (ServiceStatus and SERVICE_STATUS) — name also collides with the public ServiceStatus enum #865

@Christophe-Rogiers

Description

@Christophe-Rogiers

Severity: Warning

File: src/Servy.Core/Native/NativeMethods.cs, lines 149-173

NativeMethods declares the Win32 service-status struct twice with two different names but byte-identical layouts:

/// <summary>Represents the status of a service.</summary>
[StructLayout(LayoutKind.Sequential)]
public struct ServiceStatus
{
    public int dwServiceType;
    public int dwCurrentState;
    public int dwControlsAccepted;
    public int dwWin32ExitCode;
    public int dwServiceSpecificExitCode;
    public int dwCheckPoint;
    public int dwWaitHint;
}

/// <summary>Internal structure for service status reports.</summary>
[StructLayout(LayoutKind.Sequential)]
public struct SERVICE_STATUS
{
    public int dwServiceType;
    public int dwCurrentState;
    public int dwControlsAccepted;
    public int dwWin32ExitCode;
    public int dwServiceSpecificExitCode;
    public int dwCheckPoint;
    public int dwWaitHint;
}

Both wrap the same Win32 SERVICE_STATUS ABI. Different P/Invoke signatures use different names:

  • ServiceManager.cs:568var status = new NativeMethods.ServiceStatus(); _windowsServiceApi.ControlService(serviceHandle, SERVICE_CONTROL_STOP, ref status);
  • Service.cs:554 and Service.cs:2068SERVICE_STATUS status = new SERVICE_STATUS { ... }; (passed to SetServiceStatus via P/Invoke).

Two problems compound:

  1. DRY violation — identical struct declared twice. Adding a field, attribute, or [StructLayout] tweak requires maintaining two copies and remembering both call paths use it. A subtle layout drift between them would silently corrupt SCM communication.

  2. Naming collisionServy.Core.Enums.ServiceStatus is the public service-state enum used in 30+ places across Servy.Core.DTOs, Servy.Manager.ViewModels, StatusConverter, etc. NativeMethods.ServiceStatus is a completely unrelated P/Invoke struct. Reading ServiceManager.cs:568 in isolation makes you double-take: "is new NativeMethods.ServiceStatus() calling the enum or a struct?" Even resharper-style "go to definition" lands on whichever appears first. The PascalCase struct should not exist alongside the enum of the same simple name.

Suggested fix:

Pick one struct — the SERVICE_STATUS (uppercase, Win32-style) is the convention used by P/Invoke definitions everywhere else and avoids the enum collision. Then:

  1. Delete NativeMethods.ServiceStatus.
  2. Update IWindowsServiceApi.ControlService and WindowsServiceApi.ControlService (and any other refs) to take ref SERVICE_STATUS.
  3. Update ServiceManager.cs:568 to new SERVICE_STATUS() (or just default(SERVICE_STATUS)).

If [ExcludeFromCodeCoverage] is the reason no test caught the duplication, that's an additional argument for consolidation.

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions