Service Control Manager (SCM)
SCM — это сервер, реализованный в Windows, для удаленного управления сервисами (вызовом процедур).
Для того, чтобы запустить драйвер в Windows, ему в соответствие ставится сервис, который обеспечивает управление этим драйвером. Не путать с устройством, которое создает драйвер в системе, через которое происходит обмен сообщениями с драйвером. Это устройство создается уже после старта драйвера, а вот SCM обеспечивает само внесение драйвера в систему. С помощью SCM можно: добавлять, удалять, запускать или останавливать службы.
Постановка задачи
Написать буферный класс позволяющий упростить работу SCM в C#.
Сам внешний вид этого класса можно обознать очень просто:
public ref class ServiceControlManager : public IDisposable { public: ServiceControlManager(void); void AddDriver(String^ ServiceName, String^ BinaryPathName); void DeleteDriver(String^ ServiceName); void StartDriver(String^ ServiceName); void StopDriver(String^ ServiceName); protected: ~ServiceControlManager(); !ServiceControlManager(); private: SC_HANDLE SCMHandle; };
Конструктор, деструктор, финализатор, основные методы, из атрибутов только HANDLE объекта SCM. Из этого следует, что экземпляр объекта этого класса будет содержать в себе созданный объект SCM, а методы упрощают с ним работу. Класс является буферным, и поскольку он реализован в C++/cli он будет автоматически масштабируем для работы в среде .NET, соответственно и в C#.
Решение проблемы с ошибками
Основная проблема работы с таким классом — это возвращение кодов ошибок, которые произошли в ходе работы SCM, которое желательно на самом первом этапе работы заменить на более привычные для .NET среды исключения. Для этого можно создать подобный класс:
[Serializable] public ref class KernelErrorException : Exception { public: KernelErrorException(void); virtual String^ ToString() override; property virtual String^ Message { String^ get() override; }; property virtual DWORD Errorsource { DWORD get(); }; private: DWORD errorsource; internal: KernelErrorException(DWORD Errorsource); };
Как мы видим, экземпляр этого класса будет содержать, как атрибут только номер кода, который будет получен от GetLastError(). А при попытке привести экземлляр к типу System::String выведет полный текст описания сообщения средствами Windows.
Класс имеет два конструктора, первый — по умолчанию: сохраняет код ошибки при выполнении. Второй — получает код ошибки, как аргумент. Второй необходимо использовать в тех случаях, когда необходимо вызвать исключение, но перед этим выполнить какие-либо действия, после которых команда GetLastError() вернет не верные значения. Для этого сохраняется код ошибки, выполняются действия, затем вызывается исключение. Пример таких действий можно найти ниже: очиста PTR, исползуемой для маршалинга (PTR необходимо очистить до вызова исключения, т.к. вернуться к этому куску кода в дальнейшем не получится).
Реализация
При этом реализация методов будет самой, что ни на есть элементарной:
KernelErrorException::KernelErrorException(void) { this->errorsource = GetLastError(); } KernelErrorException::KernelErrorException(DWORD Errorsource) { this->errorsource = Errorsource; }
При этом реализация методов будет самой, что ни на есть элементарной:
String^ KernelErrorException::Message::get() { LPTSTR message = NULL; FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, NULL, this->errorsource, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR)&message, 0, NULL); String^ messageString = gcnew String(message); LocalFree(message); return messageString; } DWORD KernelErrorException::Errorsource::get() { return this->errorsource; } String^ KernelErrorException::ToString() { return this->Message::get(); }
Память выделенную под SCM надо очищать
Вторая проблема работы с SCM в .NET: handle SCM не может жить долго, иначе это приведет к зависанию системы. Поэтому при использовании необходимо следить за тем, чтобы удалением занимался не сбощик мусора, а сам программист. Придется строго описать конструктор и финализатор, в деструкторе же, по логике Dispose-паттерна, вызывается финализатор [спасибо GraD_Kh]. В финализаторе описывается высвобождение unmanage объектов:
ServiceControlManager::ServiceControlManager(void) { this->SCMHandle = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS); if (!this->SCMHandle) throw gcnew KernelErrorException(); } ServiceControlManager::~ServiceControlManager() { this->!ServiceControlManager(); GC::SuppressFinalize(this); } ServiceControlManager::!ServiceControlManager() { CloseServiceHandle(this->SCMHandle)); }
Основной функционал
Реализация всех методов очень проста, основа ее — это вызов конкретной соответствующей процедуры, но корректное выполнение обязательно нуждается во всех проверках на исключительные ситуации.
Реализация
void ServiceControlManager::AddDriver(String^ ServiceName, String^ BinaryPathName) { IntPtr serviceNamePtr = Marshal::StringToHGlobalUni(ServiceName); IntPtr binaryPathNamePtr = Marshal::StringToHGlobalUni(BinaryPathName); SC_HANDLE SCMHandleService = CreateService(this->SCMHandle, (LPCTSTR)serviceNamePtr.ToPointer(), (LPCTSTR)serviceNamePtr.ToPointer(), SERVICE_ALL_ACCESS, SERVICE_KERNEL_DRIVER, SERVICE_DEMAND_START, SERVICE_ERROR_NORMAL, (LPCTSTR)binaryPathNamePtr.ToPointer(), NULL, NULL, NULL, NULL, NULL); DWORD errorsource = GetLastError(); Marshal::FreeHGlobal(serviceNamePtr); Marshal::FreeHGlobal(binaryPathNamePtr); if (!SCMHandleService) throw gcnew KernelErrorException(errorsource); if (!CloseServiceHandle(SCMHandleService)) throw gcnew KernelErrorException(); } void ServiceControlManager::DeleteDriver(String^ ServiceName) { IntPtr serviceNamePtr = Marshal::StringToHGlobalUni(ServiceName); SC_HANDLE SCMHandleService = OpenService(this->SCMHandle, (LPCTSTR)serviceNamePtr.ToPointer(), SERVICE_ALL_ACCESS); DWORD errorsource = GetLastError(); Marshal::FreeHGlobal(serviceNamePtr); if (!SCMHandleService ) throw gcnew KernelErrorException(errorsource); if (!DeleteService(SCMHandleService)) throw gcnew KernelErrorException(); if (!CloseServiceHandle(SCMHandleService)) throw gcnew KernelErrorException(); } void ServiceControlManager::StartDriver(String^ ServiceName) { IntPtr serviceNamePtr = Marshal::StringToHGlobalUni(ServiceName); SC_HANDLE SCMHandleService = OpenService(this->SCMHandle, (LPCTSTR)serviceNamePtr.ToPointer(), SERVICE_ALL_ACCESS); DWORD errorsource = GetLastError(); Marshal::FreeHGlobal(serviceNamePtr); if (!SCMHandleService) throw gcnew KernelErrorException(errorsource); if (!StartService(SCMHandleService, 0, 0)) throw gcnew KernelErrorException(); if (!CloseServiceHandle(SCMHandleService)) throw gcnew KernelErrorException(); } void ServiceControlManager::StopDriver(String^ ServiceName) { IntPtr serviceNamePtr = Marshal::StringToHGlobalUni(ServiceName); SC_HANDLE SCMHandleService = OpenService(this->SCMHandle, (LPCTSTR)serviceNamePtr.ToPointer(), SERVICE_ALL_ACCESS); DWORD errorsource = GetLastError(); Marshal::FreeHGlobal(serviceNamePtr); if (!SCMHandleService) throw gcnew KernelErrorException(errorsource); SERVICE_STATUS serviceStatus; if (!ControlService(SCMHandleService, SERVICE_CONTROL_STOP, &serviceStatus)) throw gcnew KernelErrorException(); if (!CloseServiceHandle(SCMHandleService)) throw gcnew KernelErrorException(); }
Первый метод связывает sys файл с сервисом, добавляя этот сервис в систему. Второй — удаляет драйвер из системы, остальные две — запускают и останавливают сервис, соответственно.
Примеры использования в C#:
try { using (ServiceControlManager scm = new ServiceControlManager()) { scm.AddDriver(serviceName, filePath); scm.StartDriver(serviceName); scm.StopDriver(serviceName); scm.DeleteDriver(serviceName); } } catch (Exception ex) { }
Настройки при компиляции
Самое главное не забывать постояно использовать маршалинг между управляемой и не управляемой кучей. Напомню, для маршаллинга необходимо находится в пространстве имен:
using namespace System::Runtime::InteropServices;
Не забудьте прописать lib:
#pragma comment(lib, "Advapi32.lib")
Настройки свойств при компилировании библиотеки:

Послесловие
Многие могут возразить, что подобный подход не имеет никакого смысла, и что гараздо проще в C# воспользоваться маршаллингом аргументов из стандартных библиотек. Но, на мой взгляд, мое решение является более гибким. И позволяет избавиться от несущественных переменных, подстраивая класс под себя. /Те, кто пробовал настроить DLLImport этих функций в x64 меня поймут.../
GitHub исходники библиотеки с программой пользовательского интерфейса

