Песочница →
Использование SCM для управления драйверами в C# реализованной с помощью dll на C++
Service Control Manager (SCM)
SCM — реализованный в Windows сервер удаленного управления сервисами (вызова процедур).
Для запуска драйвера ему в соответствие ставится сервис, который обеспечивеет управление этим драйвером. Не путать с устройством, которое создает драйвер в системе, через которое происходит обмен сообщениями с драйвером. Это устройство создается уже после внесения драйвера в систему, а вот SCM обеспечивает само внесение. С помощью него можно добавлять, удалять, запусать или останавливать службы.
Постановка задачи
Наисать буферный класс позволяющий упростить работу SCM в C#.
Сам внешний вид этого класса можно обознать очень просто:
public ref class ServiceControlManager
{
public:
ServiceControlManager(void);
void AddDriver(String^ ServiceName, String^ BinaryPathName);
void DeleteDriver(String^ ServiceName);
void StartDriver(String^ ServiceName);
void StopDriver(String^ ServiceName);
protected:
~ServiceControlManager();
private:
SC_HANDLE SCMHandle;
};
Конструктор, деструктор, основные методы, из атрибутов только HANDLE объекта SCM, из чего следует, что экземпляр объекта этого класса будет содержать в себе созданный объект SCM, а методы упрощают с ним работу. Класс является буферным, и поскольку он реализован в C++/cli он будет автоматически масштабируем для работы в среде .NET, соответственно и в C#.
Но основная проблема работы с ним — это возвращение ошибок по коду, которое желательно на самом первом этапе работы заменить на более привычные для .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. При этом реализация методов будет самой, что ни на есть элементарной:
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 не может жить долго. Поэтому при использовании следить, чтобы удалением занималя не сбощик мусора, иначе это приведет к зависанию системы. Придется строго описать конструктор и деструктор:
ServiceControlManager::ServiceControlManager(void)
{
this->SCMHandle = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
if (!this->SCMHandle)
throw gcnew KernelErrorException();
}
ServiceControlManager::~ServiceControlManager()
{
if (!CloseServiceHandle(this->SCMHandle))
throw gcnew KernelErrorException();
}
Реализация
Реализация всех методов идeт буд-то по шаблону, но обязательно нуждается во всех проверках на исключительные ситуации. Поскольку этот класс является буферным, выносить в аргументы можно не только те ключевые поля, которые указал я, но я подобрал параметры для запуска самых стандартных драйверов…
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
{
ServiceControlManager scm = new ServiceControlManager();
scm.AddDriver(serviceName, filePath);
scm.DeleteDriver(serviceName);
scm.StartDriver(serviceName);
scm.StopDriver(serviceName);
scm.Dispose();
}
catch (Exception ex)
{
}
Послесловие
Многие могут возразить, что подобный подход не имеет никакого смысла, и что гараздо проще в C# воспользоваться маршаллингом аргументов из стандартных библиотек. Но, на мой взгляд, мое решение является более гибким. И позволяет избавиться от несущественных переменных, подстраивая класс под себя.
Другие могут сказать, что это вообще не имет смысла, т.к. проще было бы использовать MFC, но опять же, создавать и управлять формами в C# проще и удобнее, потому подобное решение можно считать буфферным фрэймворком, ну или стартапом подобного фрэймворка.
Спасибо за внимание.
28.12.2011 04:32+0400