Introducing SvcHostify: Simplify Hosting Custom DLL Services

Have you ever struggled with hosting your own DLLs as Windows services using svchost.exe? Many developers face challenges due to the undocumented and restrictive nature of svchost.exe. That’s where SvcHostify comes in—a lightweight, open-source tool designed to make hosting custom DLL services easier than ever.

Why SvcHostify?

SvcHostify eliminates the complexity of writing svchost-compatible services. Whether you’re coding in Java, C#, or C++, this tool handles the heavy lifting, letting you focus on your application logic. No more worries about C-style exports or system quirks.

  • Support for multiple languages: Build services in Java, C#, or C++ without worrying about low-level plumbing.
  • Two hosting modes:
    • SvcHost Mode: For academic/research purposes.
    • Standalone Mode: Run services using rundll32.exe—perfect for production.
  • Streamlined JSON configuration: Define service metadata, runtime behavior, and logging in one simple file.

What Can You Do with SvcHostify?

  • Host Java applications via JVM integration.
  • Run .NET DLLs as in-process COM servers.
  • Manage lightweight C++ services by exporting minimalistic C-style functions.

Features You’ll Love

  1. Effortless Service Management:
    Install and uninstall services in seconds with simple commands.
rundll32 svchostify.dll invoke -i -c <CONFIG_FILE>
rundll32 svchostify.dll invoke -u -c <CONFIG_FILE>

  1. Centralized Logging:
    Consolidates logs from stdout, stderr, and other streams into a single file, making debugging a breeze.
  2. Production-Ready Standalone Mode:
    Distribute your service easily without needing svchost.exe.
  3. Open Source Flexibility:
    Modify, enhance, and adapt SvcHostify for your unique use cases—licensed under MIT.

Sample Use Case: Hosting a Java Service

Here’s an example JSON configuration for running a Java-based service:

{
  "workerType": "jvm",
  "name": "MyJavaService",
  "displayName": "My Java Test Service",
  "context": "path/to/your/app.jar",
  "accountType": "networkService",
  "standalone": true,
  "logger": {
    "basePath": "logs/java.log",
    "maxSize": "10 MiB",
    "maxFiles": 5
  }
}

A few lines of JSON are all it takes to spin up your service!

Who Should Use SvcHostify?

  • Developers who need a quick way to deploy custom DLL services on Windows.
  • Organizations looking to simplify the deployment of internal tools and applications.
  • Hobbyists and researchers exploring the possibilities of Windows Service customization.

Get Started Today!

Visit the SvcHostify GitHub Repository to download the latest release, check out detailed documentation, and dive into sample projects.

Simplify your Windows Service development with SvcHostify—your next DLL-hosting project just got easier!

Controlling the C++ Runtime Linkage: A Portable Solution

A large number of open-source C++ projects choose Modern CMake as a powerful tool used for managing the building process across multiple platforms. Modern CMake refers to the practices, features, and methodologies introduced in CMake 3.x (and beyond) that simplify, improve, and modernize the build system configuration for C++ and other languages. It emphasizes clarity, reusability, and portability, and aims to make CMake easier to maintain while leveraging its full potential.

For the most part, the CMAKE_<LANG>_FLAGS CMake variable is provided for consumers to set additional compiler flags for the <LANG> programming language. For example, CMAKE_CXX_FLAGS impacts the current C++ compiler and CMAKE_C_FLAGS will take effect when building a C target in the subsequent tasks.

if(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
  string(APPEND CMAKE_CXX_FLAGS " /EHsc")
else()
  string(APPEND CMAKE_CXX_FLAGS " -fexceptions")
endif()

The CMAKE_<LANG>_FLAGS variable will always override both Release and Debug targets. To set flags separately for one single build type, extra variables like CMAKE_<LANG>_FLAGS_RELEASE and CMAKE_<LANG>_FLAGS_DEBUG are introduced for such kind of usage.

if(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
  string(APPEND CMAKE_CXX_FLAGS_DEBUG " /Od")
  string(APPEND CMAKE_CXX_FLAGS_RELEASE " /O2")
else()
  string(APPEND CMAKE_CXX_FLAGS_DEBUG " -O0")
  string(APPEND CMAKE_CXX_FLAGS_RELEASE " -O3")
endif()

On Windows, Visual Studio 2015 and later versions of Visual Studio all use one Universal Visual C Runtime Library, called the Universal CRT (UCRT). The UCRT is a Microsoft Windows operating system component. It’s included as part of the operating system in Windows 10 or later, and Windows Server 2016 or later. The Visual C++ Runtime Library is usually distributed with the corresponding MSVC tooling or installed independently with third-party software. The VC++ Runtime Library depends on the UCRT in the operating system.

The UCRT and the VC++ Runtime have static and dynamic libraries within MSVC and thereby the MSVC’s compiler cl.exe provides several options for linkage mode control of the two runtimes simultaneously. The options are:

Option (Release)Option (Debug)Description
/MD/MDdLinking to UCRT & VC++ Runtime statically
/MT/MTdLinking to UCRT & VC++ Runtime dynamically

For GCC on Linux or Unix platform, this compiler is designed to use options, such as -static-libstdc++ and -static-libgcc, to enable the static libraries of libstdc++.a, libgcc.a, or libc++.a. The libgcc is known as the GCC Low-level Runtime Library, which exists on some platform and GCC generates calls to routines in this library automatically, whenever it needs to perform some operation that is too complicated to emit inline code for.

For Clang on these platforms, it contains the same options as GCC has for compatibility considerations. The options in Clang are aliases to -static-libc++ and -static-compiler-rt, which are identical to those of GCC.

CompilerOptionDescription
GCC-static-libstdc++Linking to libstdc++ statically
GCC-static-libgccLinking to libgcc statically
Clang-static-libc++
-static-libstdc++
Linking to libc++ statically
Clang-static-libgcc
-static-compiler-rt
Linking to libcompiler-rt statically.

In a C++ project, it is recommended to keep the same configuration of compilation for all dependencies. Because of different compilers and option names, it is a bit complex to write CMake code in a portable way and users may set the options via -DCMAKE_<LANG>_FLAGS=xxx in the command line, that could lead to incorrect building configurations or an unexpected behavior.

A solution is to define a CMake option in convention, e.g. WITH_STATIC_RUNTIME, indicating that it is the official method to modify the runtime linkage mode.

option(WITH_STATIC_RUNTIME OFF "Linking to the C++ runtime statically.")

A user-defined CMake function is constructed here to generate flags for different compilers correspondingly, complying the aforementioned rules.

function(es_make_c_cxx_runtime_flags)
  set(options STATIC_RUNTIME)
  set(one_value_args RESULT_DEBUG_FLAGS RESULT_RELEASE_FLAGS)
  set(multi_value_args "")
  cmake_parse_arguments(PARSE_ARGV 0 ARG "${options}" "${one_value_args}" "${multi_value_args}")
  es_ensure_parameters(es_make_c_cxx_runtime_flags ARG RESULT_DEBUG_FLAGS RESULT_RELEASE_FLAGS)

  if(CMAKE_C_COMPILER_ID STREQUAL "MSVC" OR CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
    if(ARG_STATIC_RUNTIME)
      set(debug_flag /MTd)
      set(release_flag /MT)
    else()
      set(debug_flag /MDd)
      set(release_flag /MD)
    endif()
  elseif(CMAKE_C_COMPILER_ID STREQUAL "GNU" OR CMAKE_C_COMPILER_ID STREQUAL "Clang" OR CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
    if(ARG_STATIC_RUNTIME)
      set(debug_flag "-static-libstdc++ -static-libgcc")
      set(release_flag ${debug_flag})
    else()
      set(debug_flag "")
      set(release_flag "")
    endif()
  else()
    message(FATAL_ERROR "Unsupported compiler: ${CMAKE_CXX_COMPILER_ID}.")
  endif()
  
  set(${ARG_RESULT_DEBUG_FLAGS} ${debug_flag} PARENT_SCOPE)
  set(${ARG_RESULT_RELEASE_FLAGS} ${release_flag} PARENT_SCOPE)
endfunction()

The es_ensure_parameters function raises a fatal error when the mandatory parameters do not exist. Here is an example showing how to use this function:

if(WITH_STATIC_RUNTIME)
  set(runtime_switch STATIC_RUNTIME)
else()
  set(runtime_switch "")
endif()

es_make_c_cxx_runtime_flags(
  ${runtime_switch}
  debug_flags
  release_flags
)

message(STATUS "debug_flags: ${debug_flags}")
message(STATUS "release_flags: ${release_flags}")

Another design goal is exception safety, that is whatever users set the flags to never corrupts the building process. A workaround is to clear the old runtime flags first and set the new flags after that. A possible implementation is to iterate all the variables to remove conflicting flags and then append the generated flags to these variables. The string(REPLACE ...) function is capable of text replacement operations.

string(REPLACE "string-to-find" "replacement" <RESULT> "source")

set(input_str "Hello world")
string(REPLACE "Hello" "Echo" output_str "${input_str}")
message(STATUS "${output_str}")

In CMake, a list is actually strings separated by semicolons, so an ordinary string can be transformed to a list after inserting semicolons within it. The CMAKE_<LANG>_FLAGS and CMAKE_<LANG>_<BUILD_TYPE> variables use spaces as delimiters and can be passed to the foreach statement by replacing all spaces with semicolons. It is viable to retrieve conflicting flags by reversing the WITH_STATIC_RUNTIME option.

if(ARG_STATIC_RUNTIME)
  set(runtime_switch STATIC_RUNTIME)
  set(reverse_runtime_switch "")
else()
  set(runtime_switch "")
  set(reverse_runtime_switch STATIC_RUNTIME)
endif()

es_make_c_cxx_runtime_flags(
  ${reverse_runtime_switch}
  RESULT_DEBUG_FLAGS reverse_debug_flags
  RESULT_RELEASE_FLAGS reverse_release_flags
)

string(REPLACE " " ";" reverse_debug_flag_list "${reverse_debug_flags}")
string(REPLACE " " ";" reverse_release_flag_list "${reverse_release_flags}")

foreach(item IN LISTS reverse_debug_flag_list)
  string(REPLACE ${item} "" CMAKE_C_FLAGS "${CMAKE_C_FLAGS}")
  string(REPLACE ${item} "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
  string(REPLACE ${item} "" CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG}")
  string(REPLACE ${item} "" CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG}")
endforeach()

foreach(item IN LISTS reverse_release_flag_list)
  string(REPLACE ${item} "" CMAKE_C_FLAGS "${CMAKE_C_FLAGS}")
  string(REPLACE ${item} "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
  string(REPLACE ${item} "" CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE}")
  string(REPLACE ${item} "" CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE}")
endforeach()

Now all the variables do not contain conflicting flags anymore and the new flags can be appended to them immediately.

es_make_c_cxx_runtime_flags(
  ${runtime_switch}
  RESULT_DEBUG_FLAGS debug_flags
  RESULT_RELEASE_FLAGS release_flags
)

string(APPEND CMAKE_C_FLAGS_DEBUG " ${debug_flags}")
string(APPEND CMAKE_CXX_FLAGS_DEBUG " ${debug_flags}")
string(APPEND CMAKE_C_FLAGS_RELEASE " ${release_flags}")
string(APPEND CMAKE_CXX_FLAGS_RELEASE " ${release_flags}")

If all the above code are located inside a CMake function, which has its own inner scope of variables. Extra set statements are necessary to copy the inside variables to the outer ones.

set(CMAKE_C_FLAGS ${CMAKE_C_FLAGS} PARENT_SCOPE)
set(CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS} PARENT_SCOPE)
set(CMAKE_C_FLAGS_DEBUG ${CMAKE_C_FLAGS_DEBUG} PARENT_SCOPE)
set(CMAKE_CXX_FLAGS_DEBUG ${CMAKE_CXX_FLAGS_DEBUG} PARENT_SCOPE)
set(CMAKE_C_FLAGS_RELEASE ${CMAKE_C_FLAGS_RELEASE} PARENT_SCOPE)
set(CMAKE_CXX_FLAGS_RELEASE ${CMAKE_CXX_FLAGS_RELEASE} PARENT_SCOPE)

Well done! Everything might be OK now. The full application can be checked at my personal repo named “Cpp Essence” on GitHub. I will appreciate your stars and contributions there! Big thanks.

Windows String Representations and Simple Conversions via __bstr_t

The Windows NT kernel (from WinNT to Windows 11) internally uses UTF-16 strings by default, including Windows Drivers, Native Applications and COM clients and servers, etc. All other common encodings like UTF-8, GBK, GB18030, BIG-5 should always be converted to UTF-16 before invocations to the kernel functions within ntdll.dll.

There is a compatible layer upon the kernel layer, which is called Win32 API, a huge heritage left by the Win9X series. Win32 API is a family of functions for programmers to communicate with the operating system and hardware conveniently. Microsoft keeps this compatibility on the Windows NT Kernel so that most of the Win32 functions are still remaining unchanged and ABI-compatible. For example the CreateFile function does exist from Windows 98 to Windows 11. That is unbelievable because a Linux distribution may break any API in a minor update!

HANDLE CreateFile(
  [in]           LPCSTR                lpFileName,
  [in]           DWORD                 dwDesiredAccess,
  [in]           DWORD                 dwShareMode,
  [in, optional] LPSECURITY_ATTRIBUTES lpSecurityAttributes,
  [in]           DWORD                 dwCreationDisposition,
  [in]           DWORD                 dwFlagsAndAttributes,
  [in, optional] HANDLE                hTemplateFile
);

Some challenges started to occur. The Unicode Standard was then generally accepted by OS vendors after Win9X was released while the Win9X was still using the ANSI encoding or some multi-byte encoding in the terminal country like GB2312 and BIG-5. The Windows NT Kernel chose the UTF-16 as its kernel string representations that hindered the working progress of compatibility.

To resolve this issue, Microsoft’s talented engineers decided to create duplicates of the corresponding old Win32 APIs. The only difference of these two versions is the string types: LPCSTR vs LPCWSTR, the aliases of const char* and const wchar_t* in C++. The former represents the ANSI, and the latter stores a UTF-16 encoded string. To distinguish the mangled names at the C ABI level, the developers simply added a single-word suffix for the function: -A for the ANSI version and -W for the Unicode version. It provides much flexibility for users to call any of them in their projects.

BOOL DeleteFileA(
  [in] LPCSTR lpFileName
);

BOOL DeleteFileW(
  [in] LPCWSTR lpFileName
);

Generally speaking, the encoding API MultibyteToWideChar and WideCharToMultiByte are the usual way to reach the goal of interoperability for user-mode programs using different internal string representations on Windows.

int WideCharToMultiByte(
  [in]            UINT                               CodePage,
  [in]            DWORD                              dwFlags,
  [in]            _In_NLS_string_(cchWideChar)LPCWCH lpWideCharStr,
  [in]            int                                cchWideChar,
  [out, optional] LPSTR                              lpMultiByteStr,
  [in]            int                                cbMultiByte,
  [in, optional]  LPCCH                              lpDefaultChar,
  [out, optional] LPBOOL                             lpUsedDefaultChar
);

int MultiByteToWideChar(
  [in]            UINT                              CodePage,
  [in]            DWORD                             dwFlags,
  [in]            _In_NLS_string_(cbMultiByte)LPCCH lpMultiByteStr,
  [in]            int                               cbMultiByte,
  [out, optional] LPWSTR                            lpWideCharStr,
  [in]            int                               cchWideChar
);

It requires two calls to each function for one single conversion, the first call to calculate the buffer size and the second to perform the actual conversion. There is a simpler method to behave equivalently, that is to say, via the __bstr_t class that is supplied within the compiler’s COM support.

The COM always uses UTF-16 strings as mentioned above and the BSTR type (that is wchar_t* with some extra header) is the standard string type of COM. MSVC has native support for BSTR called __bstr_t, an encapsulation of the BSTR data type, providing a simplified version compared with the general approach.

A faster way to do encoding conversions is to instantiate an object of _bstr_t using the constructor based on the signature const char*. This overload takes an ANSI string and converts it to a UTF-16 string immediately and the _bstr_t has a const wchar_t* operator() to do the implicit cast and vice versa.

#include <string>
#include <string_view>

#include <comutil.h>

const std::string str{ "Hello some 汉字 characters" };
const _bstr_t wide_str{ str.c_str() };
const std::wstring_view{ wide_str };

#include <string>
#include <string_view>

#include <comutil.h>

const std::wstring str{ L"一些宽字符,逆向转换" };
const _bstr_t wide_str{ str.c_str() };
const std::string_view{ wide_str };