If a user downloads a dll from a site and then
copy that file to the application directory, how
can i make the application see it and send messages
to it?
The only way to do it properly is to make sure that any of the plugin DLLs implement a particular known interface. This is for two reasons - it means that you know what the public properties of the DLL are, and it allows you to query the interface to makes sure that it actually is a DLL that you can plugin.
Using the TypeLibrary DLL, you can scan a directory, and dynamically get the GUID, any interfaces and the progID of the DLL in that directory. Once you have amtched an interface in the DLL with an interface that you know about, you can store the progID to the DLL in an array or something. You need to check to see if the DLL is registered, and if not, register the DLL first.
After that, when you want to use the plugin, you can load it using CreateObject, then put it into a variable that is of the Interface type. This lets you call the DLL methods and properties in what is in effect early bound mode.
It's knid of complex from a design point, but not very dificult to put into practice.
OK, here's some sample code to help you on your merry way.
The attached project is the full version of this, which is pretty much a complete demo of creating plugins in VB. It dynamically detects them on the fly (even if they are unregistered), and loads them only when required, accessing thme through IDisp-binding
Code:
Private Sub LoadLanguagePlugins(pstrPluginDirectory As String, & _
Optional pbolRegisterNewLibraries As Boolean = False)
'Scans through the specified directory,
'and looks at all DLL files. It queries the DLL
'to decide if it supports the expected interface
'and if so, ads the ProgId, Name and description into
'the languages collection
Dim lobjFSO As Scripting.FileSystemObject
Dim lobjFile As Scripting.File
Dim lobjPlugin As Object
Dim lobjTypeLib As TLIApplication
Dim lobjTypeLibInfo As TypeLibInfo
Dim lstrProgID As String
Dim lobjILang As ILanguageLib.ILanguage
Dim lobjLanguage As clsLanguage
Dim llngCounter As Long
Dim lobjInterface As TLI.InterfaceInfo
Dim lobjCoClass As TLI.CoClassInfo
'Init the objects
Set lobjFSO = New FileSystemObject
Set lobjTypeLib = New TLIApplication
llngCounter = 0
For Each lobjFile In lobjFSO.GetFolder(pstrPluginDirectory).Files
If Right(UCase(lobjFile.Name), 3) = "DLL" Then
'cmbLang.AddItem lobjFile.Name
'Found a possible library to load
Set lobjTypeLibInfo = lobjTypeLib.TypeLibInfoFromFile(lobjFile.Path)
'Get the prog id
lstrProgID = lobjTypeLibInfo.Name & "." & lobjTypeLibInfo.CoClasses(1).Name
'Scan through the CoClasses
For Each lobjCoClass In lobjTypeLibInfo.CoClasses
'Loop through the intefaces to see if it
'supports the required interface.
'NOTE: the DLL can support multiple interfaces
For Each lobjInterface In lobjCoClass.Interfaces
If lobjInterface.Name = "_ILanguage" Then
'This one is OK
lstrProgID = lobjTypeLibInfo.Name & "." & lobjCoClass.Name
'Possible error point
'Load the library into the object
On Error GoTo Err_RegisterOCX
Set lobjILang = CreateObject(lstrProgID)
On Error GoTo Err_NotSupported
'Increment the counter
llngCounter = llngCounter + 1
'Query the interface
Set lobjLanguage = New clsLanguage
lobjLanguage.Language = lobjILang.LanguageSupported
lobjLanguage.Description = lobjILang.Description
lobjLanguage.ProgID = lobjILang.ProgID
lobjLanguage.Key = "Key_" & llngCounter
mcolLanguages.Add lobjLanguage, lobjLanguage.Key
'Reset error handler
On Error GoTo 0
End If
Next lobjInterface
Next lobjCoClass
End If
Err_NotSupported:
'Error occured, so we assume that this DLL is not
'a language DLL, or it failed to register
Next lobjFile
'Bail out
Exit Sub
Err_RegisterOCX:
'Debug.Print "About to register new Language Library: " & lstrProgID
' If pbolRegisterNewLibraries Then
If Not RegisterDLLOROCX(lobjFile.Path, Register, True) Then
'Error occured registering the file
Debug.Print "Failed to register " & lstrProgID
GoTo Err_NotSupported
Else
Debug.Print "Registered New Language Library: " & lstrProgID
Resume
End If
' Else
' Err.Clear
' On Error GoTo 0
' GoTo Err_NotSupported
' End If
End Sub
Public Function RegisterDLLOROCX(File As String, _
Optional Process As RegUnreg = Register, _
Optional PromptOnError As Boolean = False)_
As Boolean
Dim LoadedLib As Long
Dim EntryPoint As Long
Dim ExitCode As Long
Dim newThread As Long
Dim newThreadID As Long
On Error Resume Next
'// Check file exists
If Dir(File, vbNormal) = "" Then
If PromptOnError Then MsgBox "The file " & File & " doesn't exist",_
vbCritical, "DLL/OCX Register"
RegisterDLLOROCX = False
Exit Function
End If
LoadedLib = LoadLibrary(File) '// Load file
If LoadedLib = 0 Then
If PromptOnError Then MsgBox _
"An error occured while loading the file " & File, _
vbCritical, "DLL/OCX Register"
RegisterDLLOROCX = False
Exit Function
End If
'// Find right entery point
If Process = Register Then
EntryPoint = GetProcAddress(LoadedLib, "DllRegisterServer")
ElseIf Process = UnRegister Then
EntryPoint = GetProcAddress(LoadedLib, "DllUnregisterServer")
Else
If PromptOnError Then MsgBox _
"An error occured while loading the file " & File, _
vbCritical, "DLL/OCX Register"
RegisterDLLOROCX = False
Exit Function
End If
If EntryPoint = vbNull Then
If PromptOnError Then MsgBox _
"An error occured while locating the entery point for the file : " & _
vbNewLine & File, vbCritical, "DLL/OCX Register"
FreeLibrary (LoadedLib) '// Unload libarary
RegisterDLLOROCX = False
Exit Function
End If
Screen.MousePointer = vbHourglass
newThread = CreateThread(ByVal 0, 0, ByVal EntryPoint, _
ByVal 0, 0, newThreadID) '// Create a new thread.
If newThread = 0 Then
Screen.MousePointer = vbDefault
If PromptOnError Then
MsgBox "An error occured while attempting to create a new thread.",_
vbCritical, "DLL/OCX Register"
End If
FreeLibrary (LoadedLib) '// Unload libarary
Exit Function
End If
If WaitForSingleObject(newThread, 10000) <> 0 Then
Screen.MousePointer = vbDefault
If PromptOnError Then MsgBox _
"An error occured while attempting to register/unregister the file : " & _
vbNewLine & File, vbCritical, "DLL/OCX Register"
ExitCode = GetExitCodeThread(newThread, ExitCode)
ExitThread (ExitCode)
FreeLibrary (LoadedLib)
RegisterDLLOROCX = False
Exit Function
End If
CloseHandle (newThread) '// Close thread
FreeLibrary (LoadedLib) '// Unload libarary
Screen.MousePointer = vbDefault '// Reset cursor
RegisterDLLOROCX = True
End Function
Basically, unzip the projects, and load the gLanguagePlugin.vbg. Register the ILanguage.dll first. Obviously, there is no requirement to register the DLL's in the "PlugIns" directory.
This project basically swap languages on the form, based on all the possible languages which are in the Plugins directory. Note that the individual language plugins MUST be compiled before they will work.
Not all the code is mine - the RegisterDLLorOCX originally came from somewhere else, although I have made some changes. I'd credit it if I could remember who it was.
And I'm sorry if my languages are actually wrong - I was just using babelfish to translate the english to german, french and spanish, and I don't think I have taken into account the gender and stuff.
Try registering and unregistering the DLL's in the plugins directory, and moving them out of the plugins directory to see the plugins in action. Also, have a look at the references held in the main test application
Do you think it is good form to unregister the plugins after you're done with them and re-register them the next time the program starts?
ppl can (re)move the plugins without your knowing about it and that would leave 'dangling references'. Or is windows smart enough to keep track itself?
I mean, would the overhead of registering/unregistering the plugins on program start/exit be worth the added convinience of being able to do stuff to the .dll files without 'dangling refences' be worth it?
Unregistering ins't an issue, since Windopws (supposedly) tracks the registry entries properly. You could unregister all I guess, but that would be more work next time, having to register them each time (and I don't trust the Registry to clean up after itself anyway). Easier to just register them once.
As to what the ILanguage DLL is - it's sort of the key to the whole thing.
The main EXE has a reference to the ILanguage DLL. This DLL tells the EXE what the plugin is going to look like when it loads a plugin DLL, This way, you EXE knows that each plugin has a Name propery, and A Langiage Property etc. It also gives you the Intellisense stuff.
As you can see from the code below, the DLL is instantiated with CreateObject, then cast a variable of type ILanguage:
Code:
Dim lobjILang As ILanguage
'This line gets the prog ID of the item selected
'in the combo box (from the Flexbag collection)
Set lobjLang = mcolLanguages.Item("Key_" & cmbLang.ItemData(cmbLang.ListIndex))
lblDescription.Caption = lobjLang.Description
'Here the object is cast from type object to type ILanguage
Set lobjILang = CreateObject(lobjLang.ProgID)
'The .Initialise will appear as a dropdown intellisense thing
lobjILang.Initialise
The actual language DLL (LangGer.DLL etc) implements the ILanguage DLL - you can see in the LangGer project that the "ILanguage_XXXXX" are the only public methods.
The interesting thing here is that once the Language DLLs are compiled, and the EXE is compiled, the ILanguage DLL can actually be thrown away. It contains no actual code - merely method stubs.
Hope that explains it a bit. Look at the code in the four language DLL's to see what I mean.
I'm thinking about writing a proper article about how to implement plugins, with smaples and explanations etc. cos people seem intererested (I've explained the concept a few times, but this is the first time I've posted the sample program). I gues that will happen when I find enough time.
One really funky reason for using the ILanguage DLL is that in the Language DLL themselves, ther eis not public interface.
Try creating a project, then add a reference to LangEng.DLL.
Now try creating a variable of type clsEnglish.
It doesn't actually work, because the LangEng DLL exposes NO interface. The entire access must be done through the typelib inforation held in the ILanguage DLL.
This technique means that it is possible to make DLL's that are unusable with an intermediate, non-distributed DLL (in this case ILanguage). So another developer can't use you're DLL unless he knows how to access it properly (Obviously, there's is nothing stopping him using the TypeLibInfo to find out the interface, but the developer would want to be keen).
The ILanguage DLL also allows for IDisp binding (a form of early binding) instead of late binding. Early binding is faster than late binding, so that's also a good thing.
I've just created a modified version of the LangEng.DLL, which contains multiple languages in the one plugin (Australian and US English). Basically, both languages load from the one DLL, and work very nicely.
The only difference between the actual languages is the spelling of "Colour" and "Color"
Anyway, it demonstrates the multiple plugin support approach.
Thats cool and all....... but...... How do I implement this into my own project? I dont get this stuff, I thought plugins used dlls, not classes. As far as I can see, its just classes in there. How do I load plugins at run time?
A DLL is just a class, or number of classes, that are compiled in such a way that they expose some standard interfaces that Windows (and subsequently your program) knows about and can access.
I'll admit it's not a particularly obvious method - there are a lot of seemingly extraneous levels of code (especially the use if the ILanguage interface DLL, which on the face of it seems completely useless)
Tada, in reference to your other post about plugins, you sort of have two options.
You can use the compiled DLL approach, which is realistically a 'proper' plugin, as described above, or you could go down the scripting path, which allows the end user to create a script file as plain text, and your application then takes this text file and parses and executes the contents of the text files.
Applescript is a form of text-based scripting, and the script code is just that, code.
Photoshop and other proggies like Quark use compiled DLL's as plugins (the Quark ones need to be written in C++ cos of all the callbacks required)
At some point, your program MUST know at least something about the plugin, if only so you can verify that the plugins author isn't about to do something completely stupid. Either your program knows how to parse up the script file, or it knows what the avaialble functions and methods are (or it can at least get a list of those functions and methods). You can't just run a file at random.
ActiveX controls are all plugins, and they all expose three methods - AddRef, QueryInterface and Release(?) - without exposing those three methods, it's not and ActiveX component.
You will need to decide which approach is going to be te best for you, probably based on the sort of market and users that you are aiming for. For less skilled (non-programmer) users, the scripting is probably better, but a lot harder as you have to write a complete parse and code verification engine to make sure that the script can be executed properly.
A complete alternative would be to incorporate VBA directly into your app, but I've never tried this (the licensing hasn't helped, although aparently that is changeing in VB7).
Also, my apologies for my dreadful spelling in all all my previous posts...