Skip to content

Hosting your C#/Java/C++ project as a DLL service easily, NOT ONLY svchost.exe!

License

Notifications You must be signed in to change notification settings

refvalue/SvcHostify

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

19 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

SvcHostify: Hosting Your Own Codebase as A DLL Service Easily

Motivation

svchost.exe is a critical system process for hosting DLLs as Windows Services, but some of its internal workings are undocumented. Creating custom svchost DLL services, especially when using languages like Java or C#, can be cumbersome. This is mainly due to the need for exporting C-style functions to interact with svchost.exe, which complicates the development process. To address this challenge, I created the "svchostify" project, which simplifies the creation of svchost DLL services, making it easier for developers to work with svchost.exe and supporting a wider range of programming languages.

SvcHost Mode - For Personal Use

Microsoft states that svchost.exe is reserved for use by the operating system’s own services, so hosting as a SvcHost service is intended for academic and research purposes only, and might be used in personal environments. It is not recommended for use in production systems.

Standalone Mode - For Production Use (NEW in v0.1.1)

This project provides a production-friendly mode, which allows it to run as a standalone service loaded by rundll32.exe, the operating system's DLL loader. This mode can be distributed without requiring any third-party executables. NVIDIA, for instance, uses this method to create its services.

standalone.png

NOTE: This mode is set by default started in v0.1.1.

Prerequisites

The latest Visual C++ Redistributable is required for everything to function properly, only for x64 Windows.

Downloading the Hosting DLL

This project provides a C++ dynamic library that enables hosting a DLL service, which can be registered to be loaded by svchost.exe. The latest compiled release can be found on the Release page. The default name for the prebuilt DLL is typically svchostify.dll. You can download and extract it to any location on your disk that you prefer.

Command-Line Overview

The svchostify.dll is a self-executable library, meaning it can be directly loaded by rundll32.exe—the Windows tool used to run DLLs—by locating the C-style function entry specified by the user.

rundll32 svchostify.dll invoke <OPTIONS>

Available options and arguments are listed below:

Parameter Description
--install, -i Installs the service.
--uninstall, -u Uninstalls the service.
--config-file, -c Specifies a configuration file that the installation or uninstallation is based on.
--help, -h Displays details of the command line arguments.

To install or uninstall a service, use:

rundll32 svchostify.dll invoke -i -c <CONFIG_FILE>
rundll32 svchostify.dll invoke -u -c <CONFIG_FILE>

NOTE: The configuration payload, along with the service metadata, will be written to the Windows Registry simultaneously, eliminating the need for a config file after installation.

JSON Configuration

The hosting service reads a JSON file for its internal configuration and the key-value pairs and objects are defined as follows:

Field Name Type Description Possible Values Default Required
workType string The type of the service. com, pure_c, jvm, executable Yes
name string The name of the service. Any Yes
displayName string The display name of the service. Any Yes
context string Arbitrary content according to the work type: a coclass {GUID} for com, a DLL path for pure_c, a CLASSPATH for jvm, an EXE path for executable. Any Yes
accountType string The account type under which the service runs. localSystem, networkService, localService Yes
standalone
(NEW in v0.1.1)
boolean Indicates whether to run as a standalone service (hosted in rundll32.exe) instead of svchost.exe true, false true No
postQuitMessage boolean Indicates whether to post a quit message before the service exits when the type is executable. true, false false No
description string A description of the service. Any null No
jdkDirectory string The JDK Directory. Any valid directory path null No
workingDirectory string The initial working directory. Any valid directory path The DLL location No
arguments array of string The startup arguments for the service. List of string null No
dllDirectories array of string Additional directories for loading DLLs. List of directories The DLL location No
logger object Logger configuration object. See below See below No

Logger Configuration Object

Field Name Type Description Possible Values Default Required
basePath string The base path of the logging file. Any valid directory path logs/svchostify.log Yes
maxSize string The maximum size of one single log file. The pattern is \d+\s*(KiB|MiB|GiB|TiB)?. Any valid values like 10 MiB 50 MiB No
maxFiles number The maximum count of log files. Any positive integer 5 No

Note: The complete JSON schema can be found here.

JSON Samples

Hosting a JAR package

{
  "workerType": "jvm",
  "name": "ABC_SomeTestSvc_Java",
  "displayName": "ABC Test Java Service",
  "context": "E:/Projects/SvcHostify/samples/svchost.jar",
  "accountType": "networkService",
  "standalone": true,
  "description": "A Test Java Service for SvcHost.",
  "jdkDirectory": "D:/jdk-21",
  "workingDirectory": "D:/",
  "arguments": [
    "Hello",
    "World"
  ],
  "logger": {
    "basePath": "logs/logger_java.log",
    "maxSize": "10 MiB",
    "maxFiles": 10
  }
}

The sample project is located here.

Hosting a C# DLL implemented as an in-process COM server

{
  "workerType": "com",
  "name": "ABC_MyTestSvc_CSharp",
  "displayName": "ABC Test CSharp Service",
  "context": "{47D7093F-69E2-D17D-422D-49BE836EF3A5}",
  "accountType": "localSystem",
  "standalone": true,
  "description": "Test CSharp (Hosted as a COM server) Service for SvcHost.",
  "workingDirectory": "D:/",
  "arguments": [
    "Hello",
    "World"
  ],
  "logger": {
    "basePath": "logs/logger_csharp.log",
    "maxSize": "10 MiB",
    "maxFiles": 10
  }
}

The sample project can be checked here.

Hosting a C++ DLL by calling exported pure C functions

{
  "workerType": "pureC",
  "name": "ABC_MyTestSvc_CPP",
  "displayName": "ABC Test C++ Service",
  "context": "refvalue-test-service.dll",
  "accountType": "localSystem",
  "standalone": true,
  "description": "Test C++ Service for SvcHost.",
  "workingDirectory": "D:/",
  "arguments": [
    "Hello",
    "World"
  ],
  "logger": {
    "basePath": "logs/logger_cpp.log",
    "maxSize": "10 MiB",
    "maxFiles": 10
  }
}

The sample project is provided for review here.

Calling Conventions

Programming Language Calling Method
Java Implements org.refvalue.SvcHostify class with static methods void run(String[] args) and void onStop()
C# Implements the COM interface ISvcHostify and its methods void Run(string[] args) and void OnStop()
C/C++ Exports extern "C" functions void refvalue_svchostify_run(std::size_t argc, const char* argv[]) and void refvalue_svchostify_on_stop()

Some quick samples are provided in the samples directory. Feel free to take a look!

Prototypes

Java

package org.refvalue;

import java.io.PrintStream;
import java.io.UnsupportedEncodingException;
import java.util.concurrent.atomic.AtomicBoolean;

public final class SvcHostify {
    private static final AtomicBoolean running = new AtomicBoolean(false);

    static {
        try {
            // Uses UTF-8 encoding all the way.
            System.setOut(new PrintStream(System.out, true, "UTF-8"));
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
    }

    /**
     * The main routine of the service.
     * 
     * @param args The input arguments.
     */
    public static void run(String[] args) {
        // The main loop of your service.
        for (int i = 0; running.getAcquire(); i++) {
            System.out.println(String.format("Hello service counter: %d", i));

            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        System.out.println("Service has stopped from Java.");
    }

    /**
     * This function will be called by the SvcHostify routine in another thread and
     * you can, for example, send a signal to your 'run' routine to stop gracefully.
     */
    public static void onStop() {
        System.out.println("Requesting a stop.");

        running.setRelease(false);
    }
}

C#

using System.Runtime.InteropServices;

namespace Refvalue.Samples.SvcHost
{
    [ComVisible(true)]
    [Guid("47D7093F-69E2-D17D-422D-49BE836EF3A5")]
    [ClassInterface(ClassInterfaceType.None)]
    public class TestService : ISvcHostify
    {
        private int _running = 0;

        /// <summary>
        /// The service entry point.
        /// </summary>
        /// <param name="args">The input arguments</param>
        public void Run(string[] args)
        {
            Interlocked.Exchange(ref _running, 1);

            for (int i = 0; Interlocked.CompareExchange(ref _running, 1, 1) == 1; i++)
            {
                Console.WriteLine($"Hello service counter: {i}");
                Thread.Sleep(100);
            }

            Console.WriteLine("Service has stopped from CSharp.");
        }

        /// <summary>
        /// This function will be called by the SvcHostify routine in another thread and
        /// you can, for example, send a signal to your 'Run' routine to stop gracefully.
        /// </summary>
        public void OnStop()
        {
            Console.WriteLine("Requesting a stop.");

            Interlocked.Exchange(ref _running, 0);
        }
    }
}

C++

#include <atomic>
#include <cstddef>
#include <cstdio>
#include <exception>
#include <iostream>
#include <thread>

namespace {
    class test_service {
    public:
        test_service() : running_{false} {}

        /**
         * @brief The service entry point.
         * @param argc The count of the arguments.
         * @param argv The input arguments.
         */
        void run(std::size_t argc, const char* argv[]) {
            using std::chrono_literals::operator""ms;

            running_.store(true, std::memory_order::release);

            for (std::size_t i = 0; running_.load(std::memory_order::acquire); i++) {
                std::cout << "Hello service counter: " << i << '\n';
                std::this_thread::sleep_for(100ms);
            }
        }

        /**
         * This function will be called by the SvcHostify routine in another thread and,
         * you can, for example, send a signal to your 'run' routine to stop gracefully.
         */
        void on_stop() {
            printf("Requesting a stop.\n");

            running_.store(false, std::memory_order::release);
        }

    private:
        std::atomic_bool running_;
    };

    test_service service;
} // namespace

extern "C" {
__declspec(dllexport) void refvalue_svchostify_run(std::size_t argc, const char* argv[]) try {
    service.run(argc, argv);
} catch (std::exception& ex) {
    std::cout << ex.what() << '\n';
}

__declspec(dllexport) void refvalue_svchostify_on_stop() try { service.on_stop(); } catch (std::exception& ex) {
    std::cout << ex.what() << '\n';
}
}

Logging Redirection

This system automatically redirects all output sent to stdout and stderr streams to a log file. It captures logs from various sources, including:

  • Java: System.out.println
  • C#: Console.WriteLine
  • C++: std::cout, spdlog, printfand other standard output methods

The log file is continuously updated, with a new entry added every time the output exceeds 4 KB, ensuring that log data is refreshed at regular intervals. This redirection simplifies logging by consolidating output from multiple languages and libraries into a single, centralized log file for easier monitoring and troubleshooting.

Exception Handling in Your Code

The hosting system automatically handles JNI exceptions as well as COM exceptions generated by the .NET Runtime. Throwing exceptions from Java code and the C# coclass implementation are expected behaviors. It's NOT RECOMMENDED to throw exceptions within the pure_c routines.

LICENSE

This project is licensed under the MIT License.

You are free to use, modify, and distribute the software, including for commercial purposes, under the terms of the MIT License. See the LICENSE file for more details.

About

Hosting your C#/Java/C++ project as a DLL service easily, NOT ONLY svchost.exe!

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published