main
maye 2025-06-17 11:50:10 +08:00
commit df591cc371
6 changed files with 772 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/target

253
Cargo.lock generated Normal file
View File

@ -0,0 +1,253 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "bitflags"
version = "2.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967"
[[package]]
name = "parsec-vdd"
version = "0.1.0"
dependencies = [
"windows",
"windows-service",
]
[[package]]
name = "proc-macro2"
version = "1.0.95"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
dependencies = [
"proc-macro2",
]
[[package]]
name = "syn"
version = "2.0.103"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e4307e30089d6fd6aff212f2da3a1f9e32f3223b1f010fb09b7c95f90f3ca1e8"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "unicode-ident"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
[[package]]
name = "widestring"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd7cf3379ca1aac9eea11fba24fd7e315d621f8dfe35c8d7d2be8b793726e07d"
[[package]]
name = "windows"
version = "0.61.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893"
dependencies = [
"windows-collections",
"windows-core",
"windows-future",
"windows-link",
"windows-numerics",
]
[[package]]
name = "windows-collections"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8"
dependencies = [
"windows-core",
]
[[package]]
name = "windows-core"
version = "0.61.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3"
dependencies = [
"windows-implement",
"windows-interface",
"windows-link",
"windows-result",
"windows-strings",
]
[[package]]
name = "windows-future"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e"
dependencies = [
"windows-core",
"windows-link",
"windows-threading",
]
[[package]]
name = "windows-implement"
version = "0.60.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "windows-interface"
version = "0.59.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "windows-link"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a"
[[package]]
name = "windows-numerics"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1"
dependencies = [
"windows-core",
"windows-link",
]
[[package]]
name = "windows-result"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6"
dependencies = [
"windows-link",
]
[[package]]
name = "windows-service"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "193cae8e647981c35bc947fdd57ba7928b1fa0d4a79305f6dd2dc55221ac35ac"
dependencies = [
"bitflags",
"widestring",
"windows-sys",
]
[[package]]
name = "windows-strings"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57"
dependencies = [
"windows-link",
]
[[package]]
name = "windows-sys"
version = "0.59.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-targets"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_gnullvm",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows-threading"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6"
dependencies = [
"windows-link",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_i686_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"

20
Cargo.toml Normal file
View File

@ -0,0 +1,20 @@
[package]
name = "parsec-vdd"
version = "0.1.0"
edition = "2024"
[dependencies]
windows-service = "0.8.0"
[dependencies.windows]
version = "0.61.3"
features = [
"Win32_Devices_DeviceAndDriverInstallation",
"Win32_Foundation",
"Win32_System_IO",
"Win32_Storage_FileSystem",
"Win32_System_Threading",
"Win32_System_Registry",
"Win32_System_SystemServices",
"Win32_System_Diagnostics_Debug",
]

373
src/lib.rs Normal file
View File

@ -0,0 +1,373 @@
#[allow(non_snake_case)]
use std::mem::{size_of, zeroed};
use windows::{
core::{GUID, PCSTR},
Win32::{
Devices::DeviceAndDriverInstallation::{
CM_Get_DevNode_Status, CM_PROB_DISABLED, CM_PROB_DISABLED_SERVICE,
CM_PROB_FAILED_POST_START, CM_PROB_HARDWARE_DISABLED, CM_PROB_NEED_RESTART,
SetupDiDestroyDeviceInfoList, SetupDiEnumDeviceInfo, SetupDiEnumDeviceInterfaces,
SetupDiGetClassDevsA, SetupDiGetDeviceInterfaceDetailA,
SetupDiGetDeviceRegistryPropertyA, DIGCF_DEVICEINTERFACE, DIGCF_PRESENT,
DN_DRIVER_LOADED, DN_HAS_PROBLEM, DN_STARTED, HDEVINFO, SPDRP_HARDWAREID,
SP_DEVICE_INTERFACE_DATA, SP_DEVICE_INTERFACE_DETAIL_DATA_A, SP_DEVINFO_DATA,
},
Foundation::{
CloseHandle, GetLastError, GENERIC_READ, GENERIC_WRITE, HANDLE,
INVALID_HANDLE_VALUE, ERROR_SUCCESS,
},
Storage::FileSystem::{
CreateFileA, FILE_ATTRIBUTE_NORMAL, FILE_FLAG_NO_BUFFERING, FILE_FLAG_OVERLAPPED,
FILE_FLAG_WRITE_THROUGH, FILE_SHARE_READ, FILE_SHARE_WRITE, OPEN_EXISTING,
},
System::{
IO::{DeviceIoControl, OVERLAPPED},
Registry::{REG_MULTI_SZ, REG_SZ},
Threading::{CreateEventA, GetOverlappedResultEx},
},
},
};
// RAII wrapper for HDEVINFO to ensure SetupDiDestroyDeviceInfoList is always called.
struct DevInfo(HDEVINFO);
impl Drop for DevInfo {
fn drop(&mut self) {
if !self.0.is_invalid() {
unsafe {
SetupDiDestroyDeviceInfoList(self.0);
}
}
}
}
// RAII wrapper for HANDLE to ensure CloseHandle is always called.
// We expose this as the public handle type.
pub struct VddHandle(HANDLE);
impl Drop for VddHandle {
fn drop(&mut self) {
close_device_handle(self.0);
}
}
// Device Status Enum
// Derived traits allow for easy comparison, printing, and copying.
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
#[repr(i32)]
pub enum DeviceStatus {
Ok = 0,
Inaccessible,
Unknown,
UnknownProblem,
Disabled,
DriverError,
RestartRequired,
DisabledService,
NotInstalled,
}
// Constants from the C header file.
// GUIDs are created from their string representations.
// C-style strings are represented as null-terminated byte slices.
pub const VDD_DISPLAY_ID: &[u8] = b"PSCCDD0\0";
pub const VDD_DISPLAY_NAME: &[u8] = b"ParsecVDA\0";
// {00b41627-04c4-429e-a26e-0265cf50c8fa}
pub const VDD_ADAPTER_GUID: GUID =
GUID::from_u128(0x00b41627_04c4_429e_a26e_0265cf50c8fa);
pub const VDD_ADAPTER_NAME: &[u8] = b"Parsec Virtual Display Adapter\0";
// {4d36e968-e325-11ce-bfc1-08002be10318}
pub const VDD_CLASS_GUID: GUID =
GUID::from_u128(0x4d36e968_e325_11ce_bfc1_08002be10318);
pub const VDD_HARDWARE_ID: &[u8] = b"Root\\Parsec\\VDA\0";
pub const VDD_MAX_DISPLAYS: usize = 8;
// IOCTL codes
#[repr(u32)]
pub enum VddCtlCode {
Add = 0x0022e004,
Remove = 0x0022a008,
Update = 0x0022a00c,
Version = 0x0022e010,
}
/// Query the driver status.
pub fn query_device_status(class_guid: &GUID, device_id: &[u8]) -> DeviceStatus {
unsafe {
let dev_info = DevInfo(SetupDiGetClassDevsA(
Some(class_guid),
None,
None,
DIGCF_PRESENT,
));
if dev_info.0.is_invalid() {
return DeviceStatus::Inaccessible;
}
let mut dev_info_data: SP_DEVINFO_DATA = zeroed();
dev_info_data.cbSize = size_of::<SP_DEVINFO_DATA>() as u32;
let mut device_index = 0;
// Loop through all devices in the class.
while SetupDiEnumDeviceInfo(dev_info.0, device_index, &mut dev_info_data).is_ok() {
let mut required_size = 0;
// First call: get the required buffer size for the hardware ID.
SetupDiGetDeviceRegistryPropertyA(
dev_info.0,
&dev_info_data,
SPDRP_HARDWAREID,
None,
None,
0,
Some(&mut required_size),
);
if required_size > 0 {
let mut prop_buffer = vec![0u8; required_size as usize];
let mut reg_data_type = 0;
// Second call: get the actual hardware ID data.
if SetupDiGetDeviceRegistryPropertyA(
dev_info.0,
&dev_info_data,
SPDRP_HARDWAREID,
Some(&mut reg_data_type),
Some(prop_buffer.as_mut_slice()),
required_size,
None,
)
.is_ok()
{
// The property is a list of null-terminated strings (REG_MULTI_SZ).
if reg_data_type == REG_SZ.0 || reg_data_type == REG_MULTI_SZ.0 {
// Safely parse the multi-string buffer.
let found_match = prop_buffer
.split(|&b| b == 0) // Split by null terminator
.filter(|s| !s.is_empty()) // Remove empty segments
.any(|s| s == &device_id[..device_id.len() - 1]); // Compare with non-null-terminated slice
if found_match {
let mut dev_status = 0;
let mut dev_problem_num = 0;
// Get the device's configuration manager status.
if CM_Get_DevNode_Status(
&mut dev_status,
&mut dev_problem_num,
dev_info_data.DevInst,
0,
) != 0
{
return DeviceStatus::NotInstalled;
}
// Map the status codes to our enum.
if (dev_status & (DN_DRIVER_LOADED | DN_STARTED)) != 0 {
return DeviceStatus::Ok;
} else if (dev_status & DN_HAS_PROBLEM) != 0 {
return match dev_problem_num {
CM_PROB_NEED_RESTART => DeviceStatus::RestartRequired,
CM_PROB_DISABLED | CM_PROB_HARDWARE_DISABLED => {
DeviceStatus::Disabled
}
CM_PROB_DISABLED_SERVICE => DeviceStatus::DisabledService,
CM_PROB_FAILED_POST_START => DeviceStatus::DriverError,
_ => DeviceStatus::UnknownProblem,
};
} else {
return DeviceStatus::Unknown;
}
}
}
}
}
device_index += 1;
}
// If the loop finishes without finding the device.
DeviceStatus::NotInstalled
}
}
/// Obtain the device handle. Returns None if it fails.
pub fn open_device_handle(interface_guid: &GUID) -> Option<VddHandle> {
unsafe {
let dev_info = DevInfo(SetupDiGetClassDevsA(
Some(interface_guid),
None,
None,
DIGCF_PRESENT | DIGCF_DEVICEINTERFACE,
));
if dev_info.0.is_invalid() {
return None;
}
let mut dev_interface_data: SP_DEVICE_INTERFACE_DATA = zeroed();
dev_interface_data.cbSize = size_of::<SP_DEVICE_INTERFACE_DATA>() as u32;
// Loop through available device interfaces.
for i in 0.. {
if SetupDiEnumDeviceInterfaces(
dev_info.0,
None,
interface_guid,
i,
&mut dev_interface_data,
)
.is_err()
{
break; // No more interfaces.
}
let mut detail_size = 0;
// First call: get required size for the detail data.
SetupDiGetDeviceInterfaceDetailA(
dev_info.0,
&dev_interface_data,
None,
0,
Some(&mut detail_size),
None,
);
if detail_size == 0 {
continue;
}
// Allocate a buffer for the SP_DEVICE_INTERFACE_DETAIL_DATA_A struct.
// The struct has a flexible array member, so we allocate enough space for it.
let mut detail_buffer = vec![0u8; detail_size as usize];
let detail = detail_buffer.as_mut_ptr()
as *mut SP_DEVICE_INTERFACE_DETAIL_DATA_A;
(*detail).cbSize = size_of::<SP_DEVICE_INTERFACE_DETAIL_DATA_A>() as u32;
// Second call: get the actual detail data, which includes the device path.
if SetupDiGetDeviceInterfaceDetailA(
dev_info.0,
&dev_interface_data,
Some(detail),
detail_size,
None,
None,
)
.is_ok()
{
// The device path is a C-style string inside the struct.
let device_path = PCSTR((*detail).DevicePath.as_ptr());
let handle = CreateFileA(
device_path,
(GENERIC_READ | GENERIC_WRITE).0,
FILE_SHARE_READ | FILE_SHARE_WRITE,
None,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL
| FILE_FLAG_NO_BUFFERING
| FILE_FLAG_OVERLAPPED
| FILE_FLAG_WRITE_THROUGH,
None,
);
if handle.is_ok() {
// On success, wrap the handle in our RAII struct and return.
return Some(VddHandle(handle.unwrap()));
}
}
}
}
// If the loop completes without finding a valid handle.
None
}
/// Release the device handle.
fn close_device_handle(handle: HANDLE) {
if !handle.is_invalid() {
unsafe {
CloseHandle(handle);
}
}
}
/// Generic DeviceIoControl for all IoControl codes.
/// Returns the output buffer value from the driver, or None on failure.
fn vdd_io_control(
vdd: &VddHandle,
code: VddCtlCode,
data: Option<&[u8]>,
) -> Option<u32> {
if vdd.0.is_invalid() {
return None;
}
unsafe {
let mut in_buffer = [0u8; 32];
if let Some(d) = data {
let len = d.len().min(in_buffer.len());
in_buffer[..len].copy_from_slice(&d[..len]);
}
let mut overlapped: OVERLAPPED = zeroed();
let event = CreateEventA(None, true, false, None).ok()?;
overlapped.hEvent = event;
let mut out_buffer = 0u32;
let mut bytes_transferred = 0;
let result = DeviceIoControl(
vdd.0,
code as u32,
Some(in_buffer.as_ptr() as *const _),
in_buffer.len() as u32,
Some(&mut out_buffer as *mut _ as *mut _),
size_of::<u32>() as u32,
None,
Some(&mut overlapped),
);
// Handle asynchronous IOCTL.
if result.is_err() && GetLastError() != ERROR_SUCCESS {
// Wait for the overlapped operation to complete with a 5-second timeout.
if GetOverlappedResultEx(vdd.0, &overlapped, &mut bytes_transferred, 5000, false)
.is_err()
{
CloseHandle(event);
return None;
}
}
CloseHandle(event);
Some(out_buffer)
}
}
/// Query VDD minor version.
pub fn vdd_version(vdd: &VddHandle) -> Option<i32> {
vdd_io_control(vdd, VddCtlCode::Version, None).map(|v| v as i32)
}
/// Update/ping to VDD to keep displays alive.
pub fn vdd_update(vdd: &VddHandle) {
vdd_io_control(vdd, VddCtlCode::Update, None);
}
/// Add/plug a virtual display. Returns the index of the added display.
pub fn vdd_add_display(vdd: &VddHandle) -> Option<i32> {
let result = vdd_io_control(vdd, VddCtlCode::Add, None);
vdd_update(vdd); // Ping immediately after adding
result.map(|v| v as i32)
}
/// Remove/unplug a virtual display.
pub fn vdd_remove_display(vdd: &VddHandle, index: i32) {
// The driver expects the index as a 16-bit big-endian integer.
let index_data = (index as u16).to_be_bytes();
vdd_io_control(vdd, VddCtlCode::Remove, Some(&index_data));
vdd_update(vdd); // Ping immediately after removing
}

114
src/main.rs Normal file
View File

@ -0,0 +1,114 @@
#[allow(non_snake_case)]
use parsec_vdd::{
query_device_status, open_device_handle, vdd_version, vdd_update,
vdd_add_display, vdd_remove_display, DeviceStatus, VddHandle,
VDD_CLASS_GUID, VDD_HARDWARE_ID, VDD_ADAPTER_GUID, VDD_MAX_DISPLAYS
};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::thread;
use std::time::Duration;
// FFI import for _getch() to read a single character without waiting for Enter.
#[link(name = "msvcrt")]
extern "C" {
fn _getch() -> i32;
}
// Main application logic, translated from main.c
fn main() {
// 1. Check driver status.
let status = query_device_status(&VDD_CLASS_GUID, VDD_HARDWARE_ID);
if status != DeviceStatus::Ok {
println!("Parsec VDD device is not OK, status: {:?}", status);
return;
}
println!("Parsec VDD device is OK.");
// 2. Obtain device handle.
// The handle is wrapped in an Option and our RAII VddHandle struct.
let vdd = match open_device_handle(&VDD_ADAPTER_GUID) {
Some(handle) => Arc::new(handle), // Use Arc for sharing between threads
None => {
println!("Failed to obtain the device handle.");
return;
}
};
println!("Successfully obtained device handle.");
if let Some(version) = vdd_version(&vdd) {
println!("Driver version: {}", version);
}
// 3. Set up the updater thread.
let running = Arc::new(AtomicBool::new(true));
let running_clone = Arc::clone(&running);
let vdd_clone = Arc::clone(&vdd);
let updater_thread = thread::spawn(move || {
while running_clone.load(Ordering::SeqCst) {
vdd_update(&vdd_clone);
thread::sleep(Duration::from_millis(100));
}
});
// 4. Main interaction loop.
let mut displays: Vec<i32> = Vec::new();
println!("\nPress 'a' to add a virtual display.");
println!("Press 'r' to remove the last added display.");
println!("Press 'q' to quit (this will unplug all displays).");
while running.load(Ordering::SeqCst) {
// Use the imported _getch function.
let key = unsafe { _getch() as u8 as char };
match key {
'q' | 'Q' => {
println!("\nQuitting...");
running.store(false, Ordering::SeqCst);
}
'a' | 'A' => {
if displays.len() < VDD_MAX_DISPLAYS {
if let Some(index) = vdd_add_display(&vdd) {
if index != -1 {
displays.push(index);
println!("Added a new virtual display, index: {}", index);
} else {
println!("Failed to add virtual display (driver returned -1).");
}
} else {
println!("IOCTL call to add display failed.");
}
} else {
println!(
"Limit exceeded ({}), could not add more virtual displays.",
VDD_MAX_DISPLAYS
);
}
}
'r' | 'R' => {
if let Some(index) = displays.pop() {
vdd_remove_display(&vdd, index);
println!("Removed the last virtual display, index: {}", index);
} else {
println!("No added virtual displays to remove.");
}
}
_ => {} // Ignore other keys
}
}
// 5. Cleanup.
println!("Removing all displays before exiting...");
for index in displays {
vdd_remove_display(&vdd, index);
}
// Wait for the updater thread to finish.
updater_thread.join().expect("Updater thread panicked");
println!("Cleanup complete. Exiting.");
// The VddHandle (wrapped in Arc) will be dropped automatically here,
// which calls CloseHandle, cleaning up the resource.
}

11
src/service.rs Normal file
View File

@ -0,0 +1,11 @@
#[marco_use]
extern crate windows_service;
use std::ffi::OsString;
use windows_service::service_dispatcher;
define_windows_service!(ffi_service_main, service_main);
fn service_main(arguments: Vec<OsString>) {
}