Logo
programming4us
programming4us
programming4us
programming4us
Home
programming4us
XP
programming4us
Windows Vista
programming4us
Windows 7
programming4us
Windows Azure
programming4us
Windows Server
programming4us
Windows Phone
 
programming4us
Windows 7

Advanced Windows 7 Programming : Working in the Background - DEVELOPING TRIGGER-START SERVICES (part 3)

- Free product key for windows 10
- Free Product Key for Microsoft office 365
- Malwarebytes Premium 3.7.1 Serial Keys (LifeTime) 2019
3/8/2014 8:23:43 PM

4. Writing Code for the TriggerStartService Example

The example service must perform a number of tasks to ensure reliable operation. For example, because this is a trigger-start service, the service must ensure that it gets installed only on an operating system that supports trigger-start services. Of course, the example must perform some useful task. In this case, the service will detect the opening and closing of a particular port and log it in the System event log. The following sections describe the various code elements of this example.

4.1. Creating the Trigger

The trigger code appears as part of the TriggerStartServiceInstaller.CS file. To add this code, right-click the TriggerStartServiceInstaller.CS file in Solution Explorer and choose View Code from the Context menu. You'll see the code editor for the TriggerStartServiceInstaller.CS file. To begin this process, add the following using statements to this file:

using System.ServiceProcess;
using System.Runtime.InteropServices;

Now that the file is configured, add the method and variables shown in Listing 1. This method creates a trigger object that the system uses to start and stop the service at the right time.

Example 1. Defining the service trigger
// Define the GUIDs used for the trigger subtype. You can obtain the GUIDs from
// http://msdn.microsoft.com/library/dd405512.aspx.
Guid FIREWALL_PORT_OPEN_GUID =
new Guid("b7569e07-8421-4ee0-ad10-86915afdad09");
Guid FIREWALL_PORT_CLOSE_GUID =
new Guid("a144ed38-8e12-4de4-9d96-e64740b1a524");

private Boolean ConfigurePortTrigger(String ServiceName)
{
// Obtain access to the service controller for this service.
using (ServiceController SC = new ServiceController(ServiceName))
{
try
{
// Create a string to hold the port information.
String PortNumber = "23\0TCP\0\0";

// Define a pointer to the port information.
IntPtr PortNumberPtr = Marshal.StringToHGlobalUni(PortNumber);

// Define the port data.
SERVICE_TRIGGER_SPECIFIC_DATA_ITEM PortData =
new SERVICE_TRIGGER_SPECIFIC_DATA_ITEM();
PortData.dwDataType =
ServiceTriggerDataType.SERVICE_TRIGGER_DATA_TYPE_STRING;
PortData.pData = PortNumberPtr;
PortData.cbData = (uint)(PortNumber.Length * 2);

// Create a pointer to the port data.
// Begin by allocating the required memory from the global heap.
IntPtr PortDataPtr = Marshal.AllocHGlobal(
Marshal.SizeOf(typeof(SERVICE_TRIGGER_SPECIFIC_DATA_ITEM)));


// Next, place the pointer to the GUID in FirewallPortOpen.
Marshal.StructureToPtr(PortData, PortDataPtr, false);

// Create the port open trigger.

// Create a pointer to the FIREWALL_PORT_OPEN_GUID GUID.
IntPtr FirewallPortOpen =
Marshal.AllocHGlobal(Marshal.SizeOf(typeof(Guid)));
Marshal.StructureToPtr(
FIREWALL_PORT_OPEN_GUID, FirewallPortOpen, false);

// Create the start service trigger.
SERVICE_TRIGGER StartTrigger = new SERVICE_TRIGGER();

// Place data in the various start trigger elements.
StartTrigger.dwTriggerType =
ServiceTriggerType.SERVICE_TRIGGER_TYPE_FIREWALL_PORT_EVENT;
StartTrigger.dwAction =
ServiceTriggerAction.SERVICE_TRIGGER_ACTION_SERVICE_START;
StartTrigger.pTriggerSubtype = FirewallPortOpen;
StartTrigger.pDataItems = PortDataPtr;
StartTrigger.cDataItems = 1;

// Create the port close trigger.

// Create a pointer to the FIREWALL_PORT_OPEN_GUID GUID.
IntPtr FirewallPortClose =
Marshal.AllocHGlobal(Marshal.SizeOf(typeof(Guid)));
Marshal.StructureToPtr(
FIREWALL_PORT_CLOSE_GUID, FirewallPortClose, false);

// Create the stop service trigger.
SERVICE_TRIGGER StopTrigger = new SERVICE_TRIGGER();

// Place data in the various stop trigger elements.
StopTrigger.dwTriggerType =
ServiceTriggerType.SERVICE_TRIGGER_TYPE_FIREWALL_PORT_EVENT;
StopTrigger.dwAction =
ServiceTriggerAction.SERVICE_TRIGGER_ACTION_SERVICE_STOP;
StopTrigger.pTriggerSubtype = FirewallPortClose;
StopTrigger.pDataItems = PortDataPtr;
StopTrigger.cDataItems = 1;

// Create an array of service triggers.
IntPtr ServiceTriggersPtr =
Marshal.AllocHGlobal(
Marshal.SizeOf(typeof(SERVICE_TRIGGER)) * 2);
// Add the start service trigger.
Marshal.StructureToPtr(StartTrigger, ServiceTriggersPtr, false);
// Add the stop service trigger.
Marshal.StructureToPtr(StopTrigger,
new IntPtr((long)ServiceTriggersPtr +
Marshal.SizeOf(typeof(SERVICE_TRIGGER))), false);



// Create a pointer to the service's trigger information
// structure.
IntPtr ServiceTriggerInfoPtr =
Marshal.AllocHGlobal(
Marshal.SizeOf(typeof(SERVICE_TRIGGER_INFO)));

// Define the service trigger information structure.
SERVICE_TRIGGER_INFO ServiceTriggerInfo =
new SERVICE_TRIGGER_INFO();

// Fill the structure with information.
ServiceTriggerInfo.cTriggers = 2;
ServiceTriggerInfo.pTriggers = ServiceTriggersPtr;

// Place a pointer to the structure in ServiceTriggerInfoPtr.
Marshal.StructureToPtr(
ServiceTriggerInfo, ServiceTriggerInfoPtr, false);

// Change the service's configuration to use triggers.
Boolean Result = ServiceNative.ChangeServiceConfig2(
SC.ServiceHandle.DangerousGetHandle(),
ServiceConfig2InfoLevel.SERVICE_CONFIG_TRIGGER_INFO,
ServiceTriggerInfoPtr);

// Get any errors.
int ErrorCode = Marshal.GetLastWin32Error();

// Clean up from all of the allocations.
Marshal.FreeHGlobal(PortNumberPtr);
Marshal.FreeHGlobal(PortDataPtr);
Marshal.FreeHGlobal(FirewallPortOpen);
Marshal.FreeHGlobal(FirewallPortClose);
Marshal.FreeHGlobal(ServiceTriggersPtr);
Marshal.FreeHGlobal(ServiceTriggerInfoPtr);

// Check for an exception.
if (!Result)
{
Marshal.ThrowExceptionForHR(ErrorCode);
return false;
}
else
return true;
}
catch
{
return false;
}
}
}


The code begins by defining two Guid objects, FIREWALL_PORT_OPEN_GUID and FIREWALL_PORT_CLOSE_GUID. These objects correspond to constants used within C++ to provide values to the Win32 API call, ChangeServiceConfig2(). If you look at any of the trigger-start service examples, you'll see a confusing list of GUIDs and wonder where the developer obtained them. These GUIDs appear at http://msdn.microsoft.com/library/dd405512.aspx. In short, you can't use just any GUID value; you must use the specific GUIDs that Microsoft has defined for trigger event types. The reason these objects are defined globally is that you may need to use them in more than one location (unlike this example, where they're used only once).

The ConfigurePortTrigger() method begins by creating a ServiceController object, SC, that uses the name of the service, ServiceName, to access the example service. Everything within the using block applies to the example service. Because P/Invoke code is less stable than managed code, you want to place it all within a try...catch block (or even several try...catch blocks) to provide more granular error control.

It's important to remember that there's a boundary between native code and managed code. This example is working in both worlds. In order to make managed code and native code work together successfully, you must marshal data between the two environments. Consequently, you see a number of uses of the Marshal class within this example. C++, and therefore the Win32 API, also relies on null-terminated strings. The P/Invoke code for this example begins by creating a multi-string, a single string that contains multiple substrings. The String, PortNumber, contains two substrings — 23 is the first string and TCP is the second string. Each of these strings is null-terminated using the \0 escape character, and the string as a whole is null-terminated by another \0 escape character.

This string exists in managed memory, so the Win32 API can't access it. To make PortNumber available to the Win32 API, the code calls Marshal.StringToHGlobalUni(), which copies the string to native memory and returns a pointer to the native memory as an IntPtr, PortNumberPtr. The SERVICE_TRIGGER_SPECIFIC_DATA_ITEM documentation at http://msdn.microsoft.com/library/dd405515.aspx specifies that the multi-string you supply must be in Unicode format and not ANSI format, which is why this example uses the Marshal.StringToHGlobalUni() method to perform the marshaling.

The SERVICE_TRIGGER_SPECIFIC_DATA_ITEM structure is used to create that data element, PortData. As with many Win32 API structures, you must specify the kind of data that you're supplying in PortData.dwDataType, which is a SERVICE_TRIGGER_DATA_TYPE_STRING in this case. The PortData.pData element contains a pointer to the native memory location that holds the string you created. You must also supply the length of that string as a uint (not an int). Because the string is in Unicode format, you must multiply the value of PortNumber.Length by 2 in order to obtain the correct data length.

Interestingly enough, the data structure PortData is also in managed memory, so again, the code must marshal it to native memory where the Win32 API can access it. Unlike strings, it's not easy to determine the size of the native memory structure pointed to by PortDataPtr. The code calls Marshal.AllocHGlobal() to allocate the memory required by the native memory structure from the global heap, but it has to tell the method how much memory to allocate. The code calls Marshal.SizeOf() to determine the native memory size of SERVICE_TRIGGER_SPECIFIC_DATA_ITEM. You absolutely must not call the standard sizeof() method to determine the size of the data structure, because sizeof() returns the managed size.

Allocating the memory required by PortData is only the first step. The memory doesn't contain any data yet. The Marshal.StructureToPtr() method moves the data in PortData to native memory and then places a pointer to that memory in PortDataPtr. The third argument is set to false because you don't want to delete any old content.

All these steps have created a data element for the trigger and marshaled it to native memory. Now it's time to create an actual trigger. The first step is to marshal the trigger subtype described by FIREWALL_PORT_OPEN_GUID to native memory. Because a Guid is a structure, the code uses the same technique as it did for PortData — it allocates the memory by calling Marshal.AllocHGlobal() and then moves the data to that memory by calling Marshal.StructureToPtr().

You create a trigger by defining a SERVICE_TRIGGER object. In this case, the code creates StartTrigger and then begins filling it with data. Remember that a trigger consists of four elements: trigger type, trigger subtype, action, and data. The trigger, StartTrigger.dwTriggerType, is a simple enumerated value, SERVICE_TRIGGER_TYPE_FIREWALL_PORT_EVENT. The action, StartTrigger.dwAction, is also an enumerated value, SERVICE_TRIGGER_ACTION_SERVICE_START. The trigger subtype, StartTrigger.pTriggerSubtype, is a pointer to the previously marshaled data pointed to by FirewallPortOpen. Likewise, the data, StartTrigger.pDataItems, is a pointer to the previously marshaled data pointed to by PortDataPtr. The structure also requires that you tell the Win32 API how many data items PortDataPtr contains, using the StartTrigger.cDataItems element.

The process for creating the stop trigger is the same as the process for the start trigger. At this point, the code has two managed triggers. The triggers point to native memory, but the data itself, the enumerated values and pointers, resides in managed memory. The code must create an array of triggers and place it in ServiceTriggersPtr. You would normally use a managed code process to create the array, but creating the array for native code use is completely different.

The code begins by allocating memory on the global heap for the array. Notice that the call to Marshal.AllocHGlobal() allocates enough memory for two SERVICE_TRIGGER data structures. As before, allocating the memory doesn't magically transfer the data. Transferring the first data structure, StartTrigger, is easy. The code simply calls Marshal.StructureToPtr() as usual for any data structure. The second transfer is a little harder because StopTrigger must end up after StartTrigger in the array. The technique the code uses to accomplish this task is to call Marshal.StructureToPtr() again, with a pointer to the native memory version of StartTrigger and a space allocated for StopTrigger. You can create an array of any size using this approach. If you had another trigger to add, you'd still call Marshal.StructureToPtr() with ServiceTriggersPtr as the first item of the second argument.

At this point, the code has created two triggers and placed them in an array. However, in order for the Win32 API to work with just about any data, it has to be placed in a package. The package, in this case, is a SERVICE_TRIGGER_INFO structure, ServiceTriggerInfo. The code fills ServiceTriggerInfo.cTriggers with the number of triggers and then places the pointer to the native memory trigger array, ServiceTriggersPtr, in ServiceTriggerInfo.pTriggers. ServiceTriggerInfo is in managed memory, so the code creates ServiceTriggerInfoPtr, which points to the same data in native memory, by calling Marshal.StructureToPtr().

All the configuration is now completed. The code calls ServiceNative.ChangeServiceConfig2() with the handle to the service (SC.ServiceHandle.DangerousGetHandle()), an enumerated value that tells what kind of change to make (SERVICE_CONFIG_TRIGGER_INFO), and a pointer to the required data (ServiceTriggerInfoPtr). Notice the call to DangerousGetHandle(). A dangerous handle is essentially a native code handle to the service. It's dangerous because the handle is outside the control of the managed environment, which means that odd things can happen to it, like getting de-allocated while still in use. Unfortunately, there isn't any alternative to providing the handle in this case. You can find a list of other tasks that you can perform using ChangeServiceConfig2() at http://msdn.microsoft.com/library/ms681988.aspx.

The ServiceNative.ChangeServiceConfig2() call returns a Boolean value, Result, that indicates success or error, but doesn't tell you what error occurred. To obtain the actual error information, the code calls Marshal.GetLastWin32Error() and places the value in ErrorCode. The error code is a simple number. You can look up the text version of the number using the ErrLook.EXE utility found in the \Program Files\Microsoft Visual Studio 10.0\Common7\Tools folder of your setup. The code also calls Marshal.ThrowExceptionForHR() to create a managed exception that should include human readable information for the error.

The code has allocated a lot of native memory. None of this memory is automatically cleaned up. In fact, if you don't manually clean it up, Windows will continue to think that the memory is allocated. The memory will remain inaccessible until the next reboot, creating a memory leak. In order to prevent a memory leak, your code must call Marshal.FreeHGlobal() for each memory allocation as shown in the example code.

Other -----------------
- Advanced Windows 7 Programming : Working in the Background - ADVANTAGES OF WORKING IN THE BACKGROUND
- Microsoft Visio 2010 : Importing Graphics (part 6) - Importing AutoCAD Drawings - Manipulating an Imported AutoCAD Drawing and Adding Furniture
- Microsoft Visio 2010 : Importing Graphics (part 5) - Importing AutoCAD Drawings - Inserting an AutoCAD File
- Microsoft Visio 2010 : Importing Graphics (part 4) - Adding Excel Charts to Your Diagrams, Importing Vector Graphics
- Microsoft Visio 2010 : Importing Graphics (part 3) - Adding Clip Art to Your Diagrams
- Microsoft Visio 2010 : Importing Graphics (part 2) - Using Images as Shapes in Visio - Handling Bitmaps and Jaggies
- Microsoft Visio 2010 : Importing Graphics (part 1) - Using Images as Shapes in Visio - Working with Images
- Microsoft Visio 2010 : Using Special Shape Features (part 2) - Control Handles , Hyperlinks, Action Tags
- Microsoft Visio 2010 : Using Special Shape Features (part 1) - Right-Click Actions,Shape Data Fields
- Microsoft Visio 2010 : Working with Text (part 3) - Text Resizing Behavior
 
 
Top 10
- Microsoft Visio 2013 : Adding Structure to Your Diagrams - Finding containers and lists in Visio (part 2) - Wireframes,Legends
- Microsoft Visio 2013 : Adding Structure to Your Diagrams - Finding containers and lists in Visio (part 1) - Swimlanes
- Microsoft Visio 2013 : Adding Structure to Your Diagrams - Formatting and sizing lists
- Microsoft Visio 2013 : Adding Structure to Your Diagrams - Adding shapes to lists
- Microsoft Visio 2013 : Adding Structure to Your Diagrams - Sizing containers
- Microsoft Access 2010 : Control Properties and Why to Use Them (part 3) - The Other Properties of a Control
- Microsoft Access 2010 : Control Properties and Why to Use Them (part 2) - The Data Properties of a Control
- Microsoft Access 2010 : Control Properties and Why to Use Them (part 1) - The Format Properties of a Control
- Microsoft Access 2010 : Form Properties and Why Should You Use Them - Working with the Properties Window
- Microsoft Visio 2013 : Using the Organization Chart Wizard with new data
 
programming4us
Windows Vista
programming4us
Windows 7
programming4us
Windows Azure
programming4us
Windows Server