Яндекс.Метрика

    Песочница

    Использование 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# проще и удобнее, потому подобное решение можно считать буфферным фрэймворком, ну или стартапом подобного фрэймворка.

    Спасибо за внимание.