Scott Hanselman
Corillian Corporation
Code in this article
Discuss this article
Print Version
Note:The source code of this project is just the beginning. InSourceForgeThe code will be further developed. Visit this site to get help with code evolution or download the latest version of. NET Framework 2.0 or 1.1.
Content on this page
|
Hardware |
|
Interaction and abstraction with USB |
|
Eliminate bugs and read the manual |
|
User-specific settings |
|
Use plug-ins to expand applications (any language !) |
|
Summary |
Abstract:In the fourth installment of the "Some Assembly Required" column, Scott Hanselman and Bryan Batchelder found a very attractive hardware, but the software included was so bad that they used it. NET Framework 2.0 has compiled its own software. You can purchase a small wireless key fob with a USB receiver (known as a "wireless USB Security Device") manufactured by a non-signature company from some online retailers "), this means that the computer can be locked when it leaves and the lock can be canceled when it returns. However, the software that comes with it is terrible. Therefore, we planned "Some required assembly ". (We hope relevant staff of the company will read this article and start using our software !) We will also use some plug-ins written in Visual Basic to extend the application with all-new features!
Hardware
This is a strange idea! SetLight green button (available in NewEgg for $15)Fasten it to your key ring. It becomes an "exist" indicator on your computer. When you arrive, it knows; when you leave, it also knows. It can perform operations such as locking a computer, turning down the volume, or running a specific task. Great, right? This is not the case. In 1995 or so, the hardware functions were quite good, but the software included was an odd small program. "locking" the computer was simply using its own non-standard large window to cover all the applications of the user, and force the user to enter the password to remove this window. No, this is not your Windows login password, but another completely different application-specific password. Oh, my God!
Moreover, this applet cannot be expanded in any way and does not seem to contain any COM or. NET Library to easily receive device events. HoweverIdeaAnd this hardware is really attractive.Greg HughesI have discussed how to write better programs for this small button many times, but it has not been put into practice yet. Bryan Batchelder is also interested in this and thanks for his attempt for all of us.
Back to Top
Interaction and abstraction with USB
In light greenUSB wireless security Key FobThere are two pieces of hardware on the back: a button that generates a small (10 m) signal (clipped to your key) and a small USB receiver that is inserted into the computer. Interestingly, when the receiver is inserted for the first time, it does not need a driver. Windows can automatically identify this little thing! How is this possible? When you run msinfo32.exe (YourThis is not a common application on the system. Run it now !), I noticed that it registered itself as a "USB human/machine interface device" (Mouse) and a "game controller", which Windows has recognized. This makes sense because companies that make these small devices use common USB chip sets, such as those used in cheap mouse sets. In addition, you do not need to write custom device drivers. Note that the pnp id of the USB receiver is "VID_04b4 & PID_7417 ". This ID is very important: Later, we need to use it when searching for a device programmatically.
Session with a USB device and with a serial port is completely different. The name of the serial port is similar to "COM3", and no matter what device is inserted into COM3, It is COM3. If you want to find a serial port device, but you are not sure which port is open, you must program each serial port on the system to "yell": "You ?" However, in the USB environment, you know the type of the device to be searched, and do not need to worry about which port is opened. You only need to know that you need to talk to the port.
Unfortunately, the. NET base class library (BCL) does not support session with a USB device. In most cases, if you want to access a USB device from. NET, you need to use the advanced class library provided by the device manufacturer. But as we said, the manufacturer in this example does not provide any class libraries that can be used, so we will start from scratch. However, we will use Win32 APIs derived from kernel32.dll, setupapi. dll, and hid. dll ("hid" means human-machine interface device.
We start from constructing several abstraction layers, because evenYesOnly call all these Win32 functions from the UI.TrueI still want a useful "KeyFob" class, isn't it? The following is a class relationship diagram created using Visual Studio 2005 Professional Edition. You can read the graph from left to right. You may thinkKeyFobIs the class used by the application, because it is a good small device logical representation graph, we naturally think of using it as the key fob. However, what our applications really care about isExist, We must consider that there are multiple keys fob, any of which can be used with a single receiver. Therefore, we needKeyFobCollection, It isPresenceManagerA commonDictionary.PresenceManagerA list of authorized fob instances will be managed.KeyFobThey can influence the system through their existence.
KeyFobWithStatus,SerialNumberAndHandleMessageMethods andIsAuthorizedAndLastHeartbeatThis information is helpful. It protects projects that we cannot directly use. The most interesting one isKeyFobReceiver. This class is encapsulated inKeyFobClass, which isKeyFobClass provides information, but we do not need to know the content of this information externally, such as byte arrays.LastPacket. Go down,KeyFobReceiverThere isUsbStickReceiverClass. This is the first class that officially identifies the device as a USB device, where some very low-level I/O operations are performed. It hasUSBSharp type.
What's really amazing and interesting happenedUsbStickReceiverIn the upper layer of the low-level API.USBSharpClass is responsible for processing various types of Windows SDK data to be passed in and received. Next, let's take a lookUsbStickReceiverOfFindReceiverMethod. Before inserting a receiver, nothing happens to our application, right?
In the following code, I added comments to explain what is happening and what we tried to do.
public static UsbStickReceiver FindReceiver(){//We haven't found any devicesint my_device_count = 0;//We have no idea where our device is (yet)string my_device_path = string.Empty;//But we know we'll need all these methods!USBSharp.USBSharp myUsb = new USBSharp.USBSharp();//And we need to get the Human Interface Device GUIDmyUsb.CT_HidGuid();//Let the system know we're looking for active devicesmyUsb.CT_SetupDiGetClassDevs();//Get ready...int result = -1;int device_count = 0;int size = 0;int requiredSize = 0;//While nothing goes wrongwhile(result != 0){//Starting with device 0...result = myUsb.CT_SetupDiEnumDeviceInterfaces(device_count);//Let me know how much room I'm going to need to get info about this deviceint resultb = myUsb.CT_SetupDiGetDeviceInterfaceDetail(ref requiredSize, 0);//Cool, store thatsize = requiredSize;//Gimme the info you've gotresultb = myUsb.CT_SetupDiGetDeviceInterfaceDetailx(ref requiredSize, size);//Did we find the USB Receiver? Remember it's name from earlier?if(myUsb.DevicePathName.IndexOf("vid_04b4&pid_7417") > 0){//Sweet, it's device # "device_count," let's store this!my_device_count = device_count;my_device_path = myUsb.DevicePathName;//Bail, we're done!break;}device_count++;}if(my_device_path == string.Empty){Exception devNotFound = new Exception(@"Device could not be found.");throw(devNotFound);}return new UsbStickReceiver(my_device_count, my_device_path);}
This is a representative abstract example. The method name is "FindReceiver", which does not accept any parameters. However, it willUsbStickReceiverReturn to the callerKeyFobReceiverAnd many operations are hidden. In this example, all the methods we call areUsbSharpClass, and this class hides all unmanaged Win32 functions. Each class has its own responsibilities and only fulfills its own responsibilities. We are very grateful to Bryan for completing these excellent tasks.
Another interesting thing is that human-computer interface devices (HID) can use file handles to provide data access permissions. Therefore,UsbStickReceiverNext, you will receiveDevicePathAnd execute the statement:
resultb = myUsb.CT_CreateFile(devicePath);
Then, receive the result file handleHidHandleAnd execute the following statement
fs = new FileStream(new Microsoft.Win32.SafeHandles.SafeFileHandle((IntPtr)myUsb.HidHandle, false),FileAccess.Read, 5, true);
To obtain data. Make sure you read the code and can refer to the class relationship diagram at any time. This is really interesting!
Back to Top
Eliminate bugs and read the manual
Another interesting thing is that when Bryan and I (and people who have used the initial version of the application) test this application, we found that some users' systems could not find the USB receiver when it was inserted into the USB hub. These users must repeat the Receiver until the system finds it. Therefore, we reviewed the code in UsbStickReceiver. cs one by one, andEach method callCompared with the MSDN document, we found that an incorrect parameter was passed to one of the packaged Win32 methods.
//Wrong. We were passing in the previous device's resultb value, which caused random andunpredictable weirdness.int resultb = myUsb.CT_SetupDiGetDeviceInterfaceDetail(ref requiredSize,resultb);//Right. Odd as it may seem, the MSDN documentation explicitly says to pass in "0" for thesecond parameter.Whatever, dude. It makes the whole thing work!int resultb = myUsb.CT_SetupDiGetDeviceInterfaceDetail(ref requiredSize, 0);
This type of Bug is really hard to find and fix, because it can indeed run. In most cases, it can run, so we usually think of it as a hardware problem. It is difficult to debug, but it is very valuable because now we can find the USB receiver reliably.
Back to Top
User-specific settings
So far, we have completedPresenceManager. Next, let's take a look.SettingsManager. Since more than one person may be using a computer, and everyone may have their own key fob, We hope every user has their own user-specific settings. We want to obtain the path specific to the user and application. We also want to create a directory and a reasonable default setting file (if needed ).
//This constructor is private because SettingsManager is accessed via a Factoryprivate SettingsManager(){doc = new XmlDocument();string appDirPath = Path.Combine(System.Environment.GetFolderPath(System.Environment.SpecialFolder.ApplicationData),"Usb Wireless Security");configFilePath = Path.Combine(appDirPath, "SettingsV2.xml");CreateIfNeeded(appDirPath, configFilePath);doc.Load(configFilePath);}protected void CreateIfNeeded(string directory, string file){const string DEFAULT_SETTING_FILE =@"<?xml version=""1.0"" encoding=""utf-8"" ?><Settings><KeyFobs/><PresenceWindow>5</PresenceWindow><OverridePassword /><DisabledPlugins/></Settings>";DirectoryInfo di = new DirectoryInfo(directory);if(!di.Exists) { di.Create(); }FileInfo fi = new FileInfo(file);if(!fi.Exists){FileStream fs = fi.Create();StreamWriter sr = new StreamWriter(fs);sr.Write(DEFAULT_SETTING_FILE);sr.Close();}}
NET 2.0 has many new built-in settings, but as far as we need it, load a simple XML fileXmlDocumentAnd only a few lines of code. Each line of code corresponds to one user setting. The most important thing to avoid using this code is that your application must follow the "least unusual principle. This means that it should not or does not need to do anything unexpected to the user. It should only do what it should do, even if some resources are missing, it should also be done. In our example, users assume that their own settings are reasonable, so we put these settings in the C: \ documents ents and settings \ Application Data folder, we will use the following statement to retrieve the folder. System. Environment. GetFolderPath (System. Environment. SpecialFolder. ApplicationData)
If this folder does not exist, we will create a folder. If the setting file containing reasonable default values is lost, we will create a new setting file. It is such a small thing that will make your users feel happy and reduce the chances of customers making support calls.
Back to Top
Use plug-ins to expand applications (any language !)
Creating an application is interesting, but extending an application is the most interesting. The application in this article must be extended. Our application usesPresenceManagerListen on the activity of key fob, whenPresenceManagerAn event is sent when the USB receiver detects activity. Now, we want the application to notify anyone (that is, all of us) Who wants to listen to these "existing events ".
public void HandlePresenceNotification(PresenceNotificationEventArgs e){foreach(Plugin plugin in Plugins){if(plugin.Enabled){plugin.Worker.HandlePresenceNotification(e);}}}
To create a plug-in, we created a new project in Visual Studio. This time, we will use Visual Basic. We create a plug-in, that is, every time it receives a KeyFob message, it will write a message into the Windows event log. This is useful for review and debugging. It also records other users using KeyFob, who may have used my machine.
Each plug-in contains a reference to the UsbSecurity. Core Assembly. Note that even if the core assembly is written in C #, it can still be used in any. NET Language (such as VB. The USB Security. Core Assembly containsPresencePluginBaseAll plug-ins must be derived from this base class. If you look atPresencePluginBase, We will find that it provides us with many available virtual methods, suchHandlePresenceNotificationAndWorkstationLocked.
We first Add a reference to USB wireless. Core, and then derive its own class from PresensePluginBase. We also need to import the System. Diagnostics namespace so that the EventLog class can be used from the BCL.
Imports SystemImports UsbWirelessSecurityImports System.TextImports System.DiagnosticsNamespace DefaultPlugins'When the host exe finds us, point them to our Configurator!<PRESENCEPLUGINCONFIGURATOR("Event Logging Plugin")> _Class EventLoggerPluginInherits PresencePluginBaseEnd ClassEnd Namespace
In addition to the base class used to generate the plug-in, we also need to put a custom property into each plug-in class. Since each plug-in appears in a ListBox of the application UI, we need to know which plug-in the developer wants to display. Custom Attributes are an easy way to use. Plug-In developers can use them to add a "Notice remarks" to the code so that we can learn more. Note the attributes in the preceding VB code.
Next, we willPresencePluginBaseRewrite each virtual method and record the details of each message in EventLog.
Imports SystemImports UsbWirelessSecurityImports System.TextImports System.Runtime.InteropServicesNamespace DefaultPlugins'When the host exe finds us, point them to our Configurator!<PRESENCEPLUGINCONFIGURATOR("Event Logging Plugin")> _Class EventLoggerPluginInherits PresencePluginBaseDim logName As String = "USB Wireless Security"Public Overrides Sub HandleMessage(ByVal m As UsbWirelessSecurity.KeyFobMessage)MyBase.HandleMessage(m)If (Not m.MessageType = KeyFobMessageType.Heartbeat) ThenUsing aLog As New EventLog(logName)aLog.Source = logNameaLog.WriteEntry( _String.Format("Message Received: Device {0} reports {1}.", _m.SerialNumber, m.MessageType.ToString()))End UsingEnd IfEnd SubPublic Overrides Sub WorkstationLocked()MyBase.WorkstationLocked()Using aLog As New EventLog(logName)aLog.Source = logNameaLog.WriteEntry("Workstation Locked")End UsingEnd SubPublic Overrides Sub WorkstationUnlocked()MyBase.WorkstationUnlocked()Using aLog As New EventLog(logName)aLog.Source = logNameaLog.WriteEntry("Workstation Unlocked")End UsingEnd SubPublic Overrides Sub HandlePresenceNotification(ByVal eAs UsbWirelessSecurity.PresenceNotificationEventArgs)MyBase.HandlePresenceNotification(e)If (Not e.NotificationType = PresenceNotificationType.Heartbeat) ThenUsing aLog As New EventLog(logName)aLog.Source = logNameaLog.WriteEntry( _String.Format("Presence Received: Device {0} reports {1}.", _e.KeyFob.SerialNumber, e.NotificationType.ToString()))End UsingEnd IfEnd SubEnd ClassEnd Namespace
To prevent entering a message in EventLog every 250 ms, We will ignore these messages.
Back to Top
Summary
By using a concise Hardware Abstraction Layer and plug-in architecture, you can allow users who know the code to extend your applications with new features. The following are some of the ideas we ignore when extending the USB wireless security application. Bryan and I hope you can meet the challenges and start to use Visual Studio to develop such useful small devices.
• |
Send an email or an SMS when the device is inserted or unplugged |
• |
Start the default screen saver |
• |
Save all files |
• |
Start Defragmenting or Drive Cleanup |
• |
When you leave, set Skype to "Away" |
• |
Stop playing all music apps |
• |
Create a centralized Web service. Every system calls this Web service when it discovers the key fob to create a company-wide "notification service ". |
Extend existing projects as much as possible, and remember not to fear the need for some assembly !" In this case.
I am very grateful to Bryan Batchelder for his unique insights. To enable the USB key fob to be applied in. NET, he has made amazing contributions to the hardware abstraction layer!
Scott Hanselman is the chief architect of Corillian Corporation, an eFinance startup. He has 12 years of experience in developing software using C, C ++, VB, and COM, and recently he is dedicated to using VB. NET and C # for development. Scott is proud to be a Microsoft RD and MVP. He and Bill Evjen and others co-authored a new book on ASP. NET 2.0, which was published at the end of 2005. This year, he delivered a speech on code generation and software plants at the TechEd 2005 conference in Orlando. In his network diary (Http://www.computerzen.com), You can find his views on. NET connotation, programming and Web services.
Bryan Batchelder isPatchAdvisor, Inc.Head of the R & D center, a smart provider of security vulnerabilities and a company that provides security assessment services. He stillGreenline SystemsIs a provider of supply chain risk management solutions. It works with DHS to jointly ensure the safety of all goods entering the United States. He has eight years of experience in developing software using C ++, Java, ColdFusion, and ASP. Recently he specializes in. NET and C # development. In his network diary (Http://labs.patchadvisor.com/blogs/bryan), You can find his views on computer security, risk management and software development.
Go to the original English page