📜 ⬆️ ⬇️

Using SCM to manage drivers in C # implemented using a C ++ dll / cli

Service Control Manager (SCM)


SCM is a server implemented in Windows for remote control of services (procedure calls).

In order to run a driver in Windows, it is assigned a service that provides control of this driver. Not to be confused with the device that creates the driver in the system through which messages are exchanged with the driver. This device is created after the driver has started, but the SCM ensures that the driver itself is added to the system. With SCM, you can: add, remove, start, or stop services.

Formulation of the problem


Write a buffer class that allows you to simplify the work of SCM in C #.
The very appearance of this class can be made very simple:
')
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; }; 

Constructor, destructor, finalizer, main methods, from attributes only HANDLE of object SCM. From this it follows that an instance of an object of this class will contain the created SCM object, and the methods simplify the work with it. The class is a buffer class, and since it is implemented in C ++ / cli it will automatically scale to work in the .NET environment, respectively, in C #.

Solving the problem with errors


The main problem of working with such a class is the return of error codes that occurred during the SCM process, which should be replaced at the very first stage of work with exceptions that are more familiar to the .NET environment. To do this, you can create a similar class:

 [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); }; 

As we see, an instance of this class will contain, as an attribute, only the code number that will be obtained from GetLastError (). And if you try to cast an instance to the System :: String type, it will display the full text of the message description using Windows tools.

The class has two constructors, the first - by default: saves the error code during execution. The second one gets the error code as an argument. The second one should be used in cases when it is necessary to raise an exception, but before doing so, perform any actions, after which the GetLastError () command will return incorrect values. To do this, the error code is saved, actions are performed, then an exception is thrown. An example of such actions can be found below: clearing the PTR used for marshaling (PTR must be cleared before an exception is thrown, since it is impossible to return to this piece of code in the future).

Implementation
 KernelErrorException::KernelErrorException(void) { this->errorsource = GetLastError(); } KernelErrorException::KernelErrorException(DWORD Errorsource) { this->errorsource = Errorsource; } 

In this case, the implementation of the methods will be the most elementary:

 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 allocated memory must be cleared.


The second problem of working with SCM in .NET: handle SCM cannot live long, otherwise it will cause the system to hang. Therefore, when using it, it is necessary to ensure that the removal is not the garbage collection, but the programmer himself. It is necessary to strictly describe the constructor and finalizer, in the destructor, according to the logic of the Dispose-pattern, the finalizer is called [thanks to GraD_Kh ]. The finalizer describes the release of unmanage objects:

 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)); } 

Main functionality


The implementation of all methods is very simple, the basis of which is the challenge of a specific relevant procedure, but correct execution necessarily requires all checks for exceptional situations.

Implementation
 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(); } 


The first method associates the sys file with the service by adding this service to the system. The second - removes the driver from the system, the other two - start and stop the service, respectively.

Examples of use in C #:


 try { using (ServiceControlManager scm = new ServiceControlManager()) { scm.AddDriver(serviceName, filePath); scm.StartDriver(serviceName); scm.StopDriver(serviceName); scm.DeleteDriver(serviceName); } } catch (Exception ex) { } 


Compile Settings


The most important thing to remember to constantly use marshaling between a managed and unmanaged heap. Let me remind you that for marshalling you must be in the namespace:
 using namespace System::Runtime::InteropServices; 


Do not forget to write lib:
 #pragma comment(lib, "Advapi32.lib") 


Property settings when compiling the library:
Project Properties

Afterword


Many may argue that such an approach does not make any sense, and that it is much easier in C # to use the marshalling arguments from standard libraries. But, in my opinion, my solution is more flexible. And it allows you to get rid of non-essential variables, adjusting the class for yourself. / Those who tried to configure DLLImport of these functions in x64 will understand me ... /

GitHub library sources with user interface program

Application window

Source: https://habr.com/ru/post/135410/


All Articles