A Component Class Example

team lib

A Component Class Example

Components based on the Component class include the IDisposable interface. The inclusion of this interface adds to the number of things you can do with the component. Instead of performing simple tasks such as math calculations or working within the .NET environment, the component can begin to explore the outside world a little. For example, you can use PInvoke to make calls to the Win32 API.

The sample component in this section performs the simple task of obtaining the local computer name by using the GetComputerNameEx() Win32 API function. This is an easy function to use, but it demonstrates the power of working with the Win32 API in your .NET applications as needed. Obviously, the .NET Framework already encapsulates much of the functionality found in the Win32 API, so youll always want to verify that the required information is inaccessible from the .NET Framework before you create a Win32 API solution.

Deriving from the Component Class

The component we create in this section will actually have two methods . The first method can return an individual machine name based on the name type input provided by the client. The second method will return all names associated with the computer in a specific order. The sample in Listing 8-3 shows the component code for this example. You can find this code in the Chapter08\CompName folder of the books companion content.

Listing 8-3: CompName.cs
start example
 usingSystem; usingSystem.ComponentModel; usingSystem.Runtime.InteropServices; usingSystem.Text; namespaceCompName { ///<summary> ///Thisenumerationcontainscomputernamevaluesthatareused ///fortheGetComputerNameEx()methodcall. ///</summary> publicenumCOMPUTER_NAME_FORMAT { ComputerNameNetBIOS, ComputerNameDnsHostname, ComputerNameDnsDomain, ComputerNameDnsFullyQualified, ComputerNamePhysicalNetBIOS, ComputerNamePhysicalDnsHostname, ComputerNamePhysicalDnsDomain, ComputerNamePhysicalDnsFullyQualified, ComputerNameMax }; ///<summary> ///AninterfaceusedtoaccesstheComputerNamefunctions. ///</summary> [Guid("AD65E427-A809-4ca0-B408-D364D25D619A"), InterfaceType(ComInterfaceType.InterfaceIsDual)] publicinterfaceIComputerName { stringGetSingleName(COMPUTER_NAME_FORMATNameType); stringGetAllNames(); } ///<summary> ///Thisclassobtainscomputernameinformationfrom ///theremoteserver. ///</summary> [Guid("EE24578A-29CC-4554-95F6-C8F4D4F73284"), ClassInterface(ClassInterfaceType.None)] publicclassComputerName:Component,IComputerName { publicComputerName() { // //TODO:Addconstructorlogichere // } ///<summary> ///Obtainsoneofmanynamesheldbyacomputer.Theprecisename ///returnedisdeterminedbytheNameTypeargument. ///</summary> [DllImport("Kernel32.dll", CharSet=CharSet.Auto, SetLastError=true)] publicstaticexternBooleanGetComputerNameEx(COMPUTER_NAME_FORMATNameType, StringBuilderlpBuffer, refInt32lpnSize); publicstringGetSingleName(COMPUTER_NAME_FORMATNameType) { StringBuilderBuffer;//Bufferusedtoholdnamedata. Int32BufferSize;//Sizeofthedatabufferon //return. //Initializethebuffer. Buffer=newStringBuilder(80); BufferSize=80; //Obtaintherequestednamestring. if(GetComputerNameEx(NameType,Buffer,refBufferSize)) returnBuffer.ToString(); else return "NoNameAvailable"; } publicstringGetAllNames() { StringBuilderBuffer;//Bufferusedtoholdentirestring. //Initializethebuffer. Buffer=newStringBuilder(); //Calleachnamestringinturn.Addthenameofeachstring //beforemakingthecall. Buffer.Append("ComputerNameDnsDomain= "); Buffer.Append(GetSingleName(COMPUTER_NAME_FORMAT.ComputerNameDnsDomain)); Buffer.Append("\r\nComputerNameDnsFullyQualified= "); Buffer.Append(GetSingleName (COMPUTER_NAME_FORMAT.ComputerNameDnsFullyQualified)); Buffer.Append("\r\nComputerNameDnsHostname= "); Buffer.Append(GetSingleName (COMPUTER_NAME_FORMAT.ComputerNameDnsHostname)); Buffer.Append("\r\nComputerNameMax= "); Buffer.Append(GetSingleName(COMPUTER_NAME_FORMAT.ComputerNameMax)); Buffer.Append("\r\nComputerNameNetBIOS= "); Buffer.Append(GetSingleName (COMPUTER_NAME_FORMAT.ComputerNameNetBIOS)); Buffer.Append("\r\nComputerNamePhysicalDnsDomain= "); Buffer.Append(GetSingleName (COMPUTER_NAME_FORMAT.ComputerNamePhysicalDnsDomain)); Buffer.Append("\r\nComputerNamePhysicalDnsFullyQualified= "); Buffer.Append(GetSingleName (COMPUTER_NAME_FORMAT.ComputerNamePhysicalDnsFullyQualified)); Buffer.Append("\r\nComputerNamePhysicalDnsHostname= "); Buffer.Append (GetSingleName (COMPUTER_NAME_FORMAT.ComputerNamePhysicalDnsHostname)); Buffer.Append("\r\nComputerNamePhysicalNetBIOS= "); Buffer.Append(GetSingleName(COMPUTER_NAME_FORMAT.ComputerNamePhysicalNetBIOS)); //Returntheresultofallthecalls. returnBuffer.ToString(); } } } 
end example

This example has two elements we havent really touched on in previous chapters. The first is a public enumeration thats helpful in working with the GetComputerNameEx() function. Using an enumeration of this type makes it easier for someone using your component to provide the correct input reducing programming errors and making the code self-documenting in some respects. The COMPUTER_NAME_FORMAT enumeration lists the name types that an application can request from the component. The reason that the enumeration is placed at this level is to make it easier for the user to access the enumeration; it also ensures that an unmanaged client, such as a Visual C++ application, will actually see and use the enumeration.

The IComputerName interface defines the two methods used for this example. Notice that the GetSingleName() method accepts a COMPUTER_NAME_FORMAT enumeration value as input. Again, this is simply good coding practice because it ensures the method will receive the input it requires to perform the task.

The ComputerName class declaration shows that this case subclasses the Component class and implements the IComputerName interface. Although the class implements more interfaces, the basic declaration methodology hasnt changed. You still need to add the [Guid] and [ClassInterface] attributes at a minimum.

This component requires use of a Win32 API function, GetComputerNameEx() , to obtain the computer name information the client will request. Notice how this function is declared. You can use this format for many Win32 API calls. The [DllImport] attribute will normally require all three arguments presented in the sample code. At a minimum, you must define the DLL where the call is foundthis information normally appears in the Visual Studio .NET help file for that function near the bottom of the help topic. You can also find the function calls using the Depends utility. In some cases, youll want to set a specific character set to ensure any string input is in the format required by the function. The SetLastError argument ensures the component can retrieve any error information from the function call by using the Marshal.GetLastWin32Error() method.


Never use the SetLastError() or GetLastError() Win32 API function calls in a managed application or component. Because of the way Win32 API function calls are marshaled, theres no way you can know whether these functions will work as anticipated. Always use the Marshal.GetLastWin32Error() method to retrieve error information about a Win32 API function call.

Although the GetComputerNameEx() function declaration doesnt require the use of the COMPUTER_NAME_FORMAT enumeration as input, using this enumeration does make it easier to use the function call. The Int32 value, lpnSize , is a value type and is normally passed to the function by value. Because this function returns the final string size in this variable, we need to add the ref keyword to pass the value by reference. Its important to look for potential problem areas like this when you create Win32 API function declarations in your code.

The GetSingleName() method is the actual workhorse of this component. The code begins by creating a buffer of a specific size to use with the GetComputerNameEx() function. Notice the use of a StringBuilder , rather than a String , as input to the GetComputerNameEx() function. Always use a StringBuilder object if you expect to receive output from a Win32 API function to ensure the string is marshaled properly. If the GetComputerNameEx() function returns false , either an error happened or the computer doesnt support the name requested. This component makes things simple by assuming the computer doesnt support the name. However, in a production application, youd probably want to check for a last error condition to ensure that an error didnt occur. In many cases, the common language runtime wont tell you about Win32 API function call errors unless you specifically request the information.

The GetAllNames() method is longer than the GetSingleName() method, but its actually easier to understand. All it does is build a string based on multiple calls to the GetSingleName() method. Notice how the GetAllNames() method uses a StringBuilder object to create the output. This technique is actually more efficient than using a string because the code can modify a StringBuilder object. All string objects are re-created for each change, resulting in a performance hit.

Performing the Component Class Setup

The setup for this COM+ application is the same as the simple component application discussed earlier in the chapter. The example uses an application name of CompNameApp , and well export the application as CompNameAppInstall . Make sure you export the application as a COM+ 1.0 application if necessary to perform testing on your server setup.

Unlike the simple component created earlier, this component includes support for the IDisposable interface. The component must provide this support to work properly when using the Win32 API call. In fact, this component supports the four interfaces shown in Figure 8-4.

click to expand
Figure 8-4: A component based on the Component class displays four interfaces within the COM+ environment.

Creating the Client

The client for this application will need to test both the single-name and all- name modes of operation. The all-name mode of operation is the easiest to test because it doesnt require any input from the user. For this example, this simplicity means that the all-name mode of operation looks similar to the simple component test code described earlier.

The single-name mode of operation does require a little additional work. The sample in Listing 8-4 from CompNameTestDlg.cpp shows the code youll need to test the component in this code. You can find the complete source code for this example in the Chapter08\CompNameTest folder of the books companion content.

Listing 8-4: CompName client code
start example
 voidCCompNameTestDlg::OnBnClickedSingle() { IComputerName*pComputerName;//Componentinterfacepointer. _bstr_tOutputData;//Outputfromcomponent. COMPUTER_NAME_FORMATNameType;//NameTypeEnumeration. //InitializetheCOMenvironment. CoInitialize(NULL); //Createtheobject. CoCreateInstance(CLSID_ComputerName, NULL, CLSCTX_ALL, IID_IComputerName, (void**)&pComputerName); //Determinewhichnametheuserhasselected. switch(m_Select.GetCurSel()) { case0: NameType=COMPUTER_NAME_FORMAT_ComputerNameNetBIOS; break; case1: NameType=COMPUTER_NAME_FORMAT_ComputerNameDnsHostname; break; case2: NameType=COMPUTER_NAME_FORMAT_ComputerNameDnsDomain; break; case3: NameType=COMPUTER_NAME_FORMAT_ComputerNameDnsFullyQualified; break; case4: NameType=COMPUTER_NAME_FORMAT_ComputerNamePhysicalNetBIOS; break; case5: NameType=COMPUTER_NAME_FORMAT_ComputerNamePhysicalDnsHostname; break; case6: NameType=COMPUTER_NAME_FORMAT_ComputerNamePhysicalDnsDomain; break; case7: NameType= COMPUTER_NAME_FORMAT_ComputerNamePhysicalDnsFullyQualified; break; case8: NameType=COMPUTER_NAME_FORMAT_ComputerNameMax; } //Getthecomputernames. OutputData=pComputerName->GetSingleName(NameType); //Displaytheoutputonscreen. m_Output.SetWindowText(OutputData); //UninitializetheCOMenvironment. CoUninitialize(); } 
end example

This example begins much as the simple component example did. This code initializes the COM environment, and then it creates an instance of the object. The code then uses a switch to determine which option the user selected from the combo box. The combo box Type property value is set to Drop List so that the user can select only from the authorized options. The switch obtains the current selection using the GetCurSel() method and converts that value to a COMPUTER_NAME_FORMAT enumeration value. If you look in the TLH file for this application, youll see the enumeration adds the type name to each of the name values.

One oddity to notice is how the CLR marshals the StringBuilder object. To an unmanaged application, this object appears as a _bstr_t object. This object encapsulates BSTR values and makes it very easy to work with them in unmanaged code. In this case, all we need to do is use the SetWindowText() method to display the text directly on screen.

Once you have the code put together and installed, you can test it using a method similar to the one for the simple component test. The client will call to the server for the information this client provides. Figure 8-5 shows typical output for this application when the user clicks All Names. As before, youll want to validate that the server is actually called by checking the application activity indicators in the Component Services console.

click to expand
Figure 8-5: Typical output from the application shows a single computer name or all the available names.
team lib

COM Programming with Microsoft .NET
COM Programming with Microsoft .NET
ISBN: 0735618755
EAN: 2147483647
Year: 2006
Pages: 140

Similar book on Amazon

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net