everal years and employers ago, I worked for a school of information technology in Australia. At one point, administrators, frustrated by a confusing and distributed collection of tools for testing and grading, decided they needed a single program that could manage lists of students and subjects, generate tests, grade submissions, and upload results to the central university system?without being constrained in any way by the actual software being assessed in the exams themselves.
By creating an ActiveX/DLL library, with test generators and markers for each product merged together, I was able to rework the system in just that way. A main program was constructed to perform all the necessary housekeeping functions and to search for the DLLs. Menus were dynamically constructed based on the DLLs present. All the logic to make and mark tests for specific products were bundled into these DLLs.
To support a new product became a matter of simply generating a new DLL and dropping it into a disk directory on each computer that was using the system. The main program needed no recompilation or reworking whatsoever.
There are countless ways that you can apply this kind of extensibility. In this article, I’ll show you how to make your own expandable system using ActiveX/DLLs as plug-ins?in this case, a menu to manage a library of games.
Each of these projects is completely stand-alone, and both compile to a single .exe file.
Through this example, you’ll learn the steps needed to convert your stand-alone programs into plug-in DLLs, and to write a generic program that detects these at run-time.
The Steps to Modularity
To convert these two disparate programs into plug-in modules, consider precisely what the requirements are, then modify the original programs to conform to a consistent implementation of these requirements.
Say you had a generic program that could launch games?with no pre-determined knowledge of those games. What sort of information would it need to retrieve from the games? What sort of commands would it need to issue to the games?
There’s no need to rework the games themselves in any way. There will be nothing different between the plug-in versions of the games and their stand-alone equivalents.
In this case, it seems self-evident that you’d want a method to play a game. You’ll also want properties to return the name of the game and its version.
Assign names to these, as follows:
- playGame: method to invoke the game, displaying its user interface
- getName: property returning the name of the game
- getVersion: property returning the version of the game
Now, convert the two stand-alone programs into plug-ins by changing them into ActiveX DLLs.
|Figure 4: Create a new ActiveX DLL project.|
Firstly, start Visual Basic. Elect to create a new project of type ActiveX DLL. Visual Basic brings up a blank class file, where it implements the methods and properties of the DLL. You’re not rewriting the game, you’re simply adding a front-end to it that adds extra facilities.
To inspect the existing code of the existing executable versions, click File/Add Project. Select an existing project, and open the Reversi.vbp project. Visual Basic sets up a project group in the IDE containing both the new and existing projects, simultaneously. That way, the existing code and filenames can be seen easily. You need to know the files that make up the game, and also where the entry point to the game is (that is, which is the main form, or where is the main subroutine).
Expand all the nodes in the project inspector so all the files of the Reversi stand-alone .exe project are listed.
|Figure 5: Using a project group.|
Right-click on the new project (Project1) and select Add Form and Add Module respectively, adding the existing form and module into this new project. One body of code is being used for both projects (and hence, should the game code be updated, then both projects benefit simply by recompiling).
|Figure 6: Adding existing code to a new project.|
It’s important to put some consideration into your project’s name, because the name given to the project and to the class are used to instantiate that class from another program.
For instance, if the project is called Project1 and the class Class1, as is the default, then another program would need to use code like so:
Dim obj as Project1.Class1Set obj = new Project1.Class1
Another reason for careful name consideration is that a consistent naming scheme helps the main program determine just which DLLs belong to it and which do not.
|Figure 7: Give the project sensible names.|
Here, I’ve used a naming scheme where the plug-in modules go by the name GMgamename.Game?that is, the class is called “Game” for each ActiveX object, and the project is called GMgamename where “GM” might stand for “Game Manager.”
Rename the project in this case to GMReversi and the class to Game, as illustrated in Figure 7.
Now, turn your attention to the class module to implement your methods and properties. These are all very simple:
Public Property Get getName() As String getName = "Reversi"End PropertyPublic Property Get getVersion() As String getVersion = "Reversi v" & Str(App.Major) & "." & Str(App.Minor)End Property
The two properties?returning the Name and Version?should be read-only at runtime, so only implement the Get accessor methods. Omit the Let accessor methods that would otherwise allow a value to be assigned to the properties.
Public Sub playGame() frmXReversi.ShowEnd Sub
|Figure 8: Check the DLLs are registered with Windows.|
There is absolutely nothing complex about this code. The playGame method simply invokes the main form for the game?just as if it had been the startup form as it is in the normal .exe file.
Click File/Make GMReversi.dll to build and compile the DLL. You’ve just made an ActiveX DLL containing a playable Reversi game.
Follow these very same steps again with the Breakout game. I’ve already produced the necessary code, available here.
Test to see if the ActiveX DLLs have been compiled correctly (and registered with Windows) by using the Project/References menu item.
Coding the GameManager
Now, you need a generic program to find and make available these plug-in modules. This GameManager must, in some way, detect the DLLs available and make use of them so that the games can be played without the user having to be aware, in any way, of the underlying files.
Things have been made considerably easier by the consistent naming convention. Just search for plug-in files that match the pattern “GM*.dll.” With all filenames that match, the program then tries to instantiate an object of the form GM*.Game. If this creates a valid object, then it is one of your plug-ins and you can assume the required functions?getName, getVersion, playGame?all exist.
Here’s the code to achieve this:
Public colPlugIns As CollectionPublic Sub FindPlugIns(ByVal PlugInPath As String) Dim strFileName As String Set colPlugIns = Nothing Set colPlugIns = New Collection strFileName = Dir(PlugInPath & "GM*.dll", vbNormal) Do While strFileName <> "" If PlugInExists(strFileName) Then colPlugIns.Add strFileName End If strFileName = Dir LoopEnd SubPrivate Function PlugInExists(strFileName As String) As Boolean Dim strCreateString As String Dim vDummy As Object On Error Resume Next strCreateString = MakeObjectFromPath(strFileName) Set vDummy = Nothing Set vDummy = CreateObject(strCreateString) If vDummy Is Nothing Then PlugInExists = False Else PlugInExists = True Set vDummy = Nothing End IfEnd FunctionPublic Function MakeObjectFromPath(strFileName As String) As String MakeObjectFromPath = ExtractFileName(strFileName) & _ ".Game"End Function
|Figure 9: Running the Game Manager.|
Notice the code above looks for DLLs in a specified plug-in path. I’ve simply used the application’s own path (App.Path), meaning the DLLs need to be copied into the same directory as the application itself. However, they need not be constrained in any way. The code above is also a segment from the GameManager project accompanying this article.
Simply add the DLL names to a list and display these back to the user. Running the GameManager.exe program yields?predictably and happily?two results.
The buttons to the side test the functionality of the DLLs, ensuring that the methods and properties work as expected. The code behind each button is almost identical, and essentially invokes a new instance of the appropriate DLL and calls the appropriate method or property. The “Play” button, for instance, is implemented thusly:
Private Sub bPlay_Click() Dim vObj As Object Set vObj = TheSelectedObject vObj.playGame Set vObj = NothingEnd SubPrivate Function TheSelectedObject() As Object Dim strCreateString As String Dim vObj As Object strCreateString = MakeObjectFromPath _ (lstPlugins.List(lstPlugins.ListIndex)) Set vObj = CreateObject(strCreateString) Set TheSelectedObject = vObjEnd Function
Again, due to naming convention consistency, all the plug-in DLLs have a class named Game with a “playGame” method that can be called for the purpose of playing the game contained in the DLL.
A close inspection of the GameManager code reveals not a single reference to any specific DLL. Thus, the code has no built-in knowledge of the Reversi or Breakout games. Nor does it possess any built-in limits to the number of DLLs that may exist.
The concepts presented here are simple, yet completely transferable to any other purpose.
By carefully considering just what each of your plug-in DLLs have in common and by enforcing a consistent naming strategy, you can easily detect all your plug-ins and write generic code to call their methods and properties.
You can also easily remove or add DLLs with new games and the GameManager program still works as expected. It needs no recompilation or redistribution. DLLs can be replaced with enhanced features and all that is required is to just “drop” the plug-in DLLs into the appropriate directory.
You can add solid functionality to the main front-end program without placing any extra burden on the plug-ins. For instance, to sort the list of games based on the player’s preference, shift those more frequently played to the top of the list. Again, this requires absolutely no built-in intelligence regarding the games or plug-ins being used. Just provide code to maintain a list of names and increment a counter each time the “Play” button is pressed (and code to save these counters, whether to a database, the registry or even an .INI or some other configuration file).
One very important thing to keep in mind is that Visual Basic registered the ActiveX DLLs with Windows when you compile them. So if you copy the DLL onto another computer, you must register it manually. You can do this with the REGSVR32 DOS command or with a deployment tool like InstallShield.
Apart from this one restriction, adding plug-ins to your application is really no more complex than basically dropping DLLs into a disk directory.
To be honest, I think this is a tremendous example of very simple programming used to achieve highly professional effects.