Link to home
Start Free TrialLog in
Avatar of ShelfieldCollege
ShelfieldCollege

asked on

Application - creating 'bolt-on' modules...

Hi folks,

Basically I am trying to duplicate the features of another application I use daily.  In essence the application is a simple MDI which can be added to using DLLs.  For the user to add new functionatility we give them a DLL, they then drop this into the application folder and it's loaded automatically when the application next runs, giving them new menus and new dialogs available.

What I'm trying to figure out is how to do something similar in my own examples, not neccessarily as an MDI, SDI is fine but basically so that the application itself is a simple and features can be added with DLLs, thus allowing my to distribute DLLs to people to add new features to their apps, or to replace existing ones when a bug is fixed etc.

I'm not hot on DLL creation or anything like that, so needs to be for a bit of a beginner.

Any help is greatly appreciated, example application giving a simple example would be greatly appreciated if possible.

Thanks

Matt
Avatar of jmundsack
jmundsack
Flag of United States of America image

Sounds like a fun project.

I think it's going to be more of a process than just providing sample code, though.

The way I would approach this is to first of all identify a standard interface that every "snap-on" will adhere to.  In other words, what information does the DLL need to provide to the parent app, in order for the parent app to present menu items, etc.; and what, if any, information the DLL needs to obtain from the parent app.

I would then develop a DLL that defines these interfaces.  The parent app, and every snap-on DLL, would reference this interface DLL.

The main form in the parent app would implement the parent app interface, and the public-creatable class in each snap-on DLL would implement the snap-on interface.

There would need to be a way for the snap-on DLLs to register themselves with the parent app.  You suggested perhaps this would be as simple as dropping the DLL in the application path--in which case the parent app would need to have functions to loop through all the DLL files in the folder, somehow identify which ones were snap-on's, and then register them (shell out to regsvr32 to register the DLL in COM).  The parent app would then need to remember which snap-on's it has identified (perhaps by storing them in an INI file or in the registry).

When the parent app starts, it would then interrogate all the snap-on's for their menu definitions, using the properties exposed by the snap-on interface.  Then when the menu item is clicked, the parent app would invoke the snap-on's method that corresponds to the menu item clicked.

There may be others who have done this or something similar and have a sample application to share with you.  If you don't find any code, I would be happy to go through the process with you, beginning with identifying a standard interface that every snap-on will adhere to.
Avatar of ShelfieldCollege
ShelfieldCollege

ASKER

I did manage to find some example code out there, but to be honest working in a 'finished' system is a little trickier for myself than starting from scratch as i'd need to figure out whats going on before i can look at how its doing it.  So maybe starting from the ground up might help :\.

Is there anyway to do it without registering the DLLs on the system? Or is that a requirement in order to access the DLLs functions etc?

Cheers
VB DLLs are all based on COM, and in order to access the classes in a COM DLL, it must be registered in Windows.  That shouldn't be a big deal though, because your snap-on DLLs are going to have to be referenced "late bound" anyway, so your main program can easily register them prior to their use (via shelling to regsvr32).

Have you worked with creating interface classes and the "Implements" keyword before?  This is a feature of the VB language that will permit your parent app to communicate with many different species of snap-on DLLs through an early-bound, strongly-typed interface.  You could do this whole thing without interface classes and Implements (by referencing all the snap-on objects as the Object data type), but it would not be as airtight.

If you want to go ahead with building a system from the ground-up, rather than work with your whole project, why don't we build just a skeleton of what you want, and then you can create your app around it after we've perfected the snap-on functionality?  To begin, you would need to identify the information that your parent app needs to know about a snap-on, and the information that the snap-on needs to know about the parent app.

"Is there anyway to do it without registering the DLLs on the system?"
No, the DLL(s) must be registered

"Or is that a requirement in order to access the DLLs functions etc?"
Yes, that is correct
jmundsack -- consider the DLL delivered by

http://www.mztools.com/v3/mztools3.htm  (which, by the way, is a very cool set of VB6 add-ins)

clearly, MZtools was created INDEPENDENT of VB6 - that is, the developers of MZTools did not have the luxury of having a predefined interface present in VB6 that their tool set could implement.
On the contrary.  For MZ-Tools to be a VB add-in, it has to expose the predefined extensibility interface.  This interface is precisely what allows MZ-Tools to install itself on the toolbars and menus within the IDE.  It's almost the same exact concept that ShelfieldCollege is looking for, albeit on a much smaller scale.

For all intents and purposes, these "snap-on" DLLs are essentially going to be "add-ins" for the parent app.  They're COM DLLs which must be registered in Windows.  They're also registered in the system "add-ins" to differentiate them from ordinary COM DLLs, and which also allows them to be presented on the Available Add-Ins menu.

BTW, I use MZ-Tools and I think it's great.  But at its core, it is still an add-in, and it's only an add-in because it implements the extensibility interface.
Shelfield College, here is a link that might assist you:

http://www.oreilly.com/catalog/devvbad/chapter/ch01.html#28418
and here is the link to the Barnes and Noble booksellers site, where you might be able to purchase a copy of the book (it is now out of print)

http://search.barnesandnoble.com/used/productMatches.asp?z=y&PEAN=9781565925274
Thanks, that book looks a great resource, although intended for creating addins for VB (by the looks of it) I can see how it's contents and principles will prove beneficial.

I understand some of the concepts of using an Interface and classes that implement that interface, I've recently done a uni course on java OOP in which we touched on the subject.

Presumably the add-in interface would detail what methods etc all add-ins themselves should expose (if thats the right word)?

I'm a bit stuck on applying this to a my own VB app tho jmundsack.  I think the idea of creating the basic app first would be a nice one, how if I give a little more detail as to what I'm trying to achieve.

At the moment, the current application I'm thinking of recoding to use this method simply has an icon that sits down in the system tray.  When people right click the item they get options similar to those used with databases, stop/start mssql server service, backup database (which has a form) and restore database (which also has a form).

So, as an example I could do something similar to having a core application that sits in the system tray, then have a services add-in which allows controll of the windows services, then a database add-in which gives control over backups/restore etc.  

The add-in's themself would need to contain any form specific to that add-in, and also they would need to be able to retrieve settings from the core app (whos settings will actually be stored in an INI file so shouldn't be that tricky I hope, depends whether they get information direct from the parent app, or just read it from the INI file).

The parent app itself would need to be able to call the add-ins and determine what items it needs adding to the menu (seen when user right clicks on app) etc... if that makes sense?  Unless the parent app could call a function in the add-in and pass to it the object for the top level menu itself, then maybe the add-in call add the menu items itself?

I'm confused alreay :)

Cheers both for your help, seems like I'll be learning alot in creating this app.
ASKER CERTIFIED SOLUTION
Avatar of jmundsack
jmundsack
Flag of United States of America image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Hi ya, firstly thanks for taking the time to create an example application.  However when I run the project group it tells me it's unable to create the reference to the DLL and when I then run the project it understandably gives user define type error (i think)

Any Ideas?

If i try adding a reference to the DLL manually, after selecting the DLL it doesn't appear in the references list :\


Cheers

Matt
Sorry, update:

I opened the testapp project, added the reference to the DLL, save it then recompiled the exe and didn't get any errors, however it isn't showing any snap ons in the list box.  I took the on error resume next out of the look that reads the INI file and get the following error:

ActiveX component cant create object.

If thats of any use?

Presumably it's something I'm doing wrong, sorry if it is but like I say this idea of DLLs and references etc are new to me.
I'm sorry...  I should have been more explicit in my instructions.

VB DLL's need to be registered in Windows before they can be used.  To do this, run the following commands:

regsvr32 <path>\SnapOnInterfaces.dll
regsvr32 <path>\TestSnapOn.dll

Where <path> is the full path of the folder where these DLLs reside.  Once you do that, you should be able to run the app with no problem.
DOH, of course I forgot all about registering them.  It's my fault as you both did mention previously that they need to be registered. :)  Ill give it a go.
Aha, it works, and niftily now I have a right click option on DLLs that lets me register them with regsvr32, was too idle to type full path in and figured this would be easier, Cheers :)

Great, so presumably any events etc that I add to the interface are required to be overridden (sp i know) by the snap ins themselves?  Or have i got that all wrong.
Few things:

You mentioned changed the interface.  Until you're sure that your interface is pretty solid, make sure you don't compile it -- run everything in a project group and use "project compatibility."  When you're ready to go, then compile it and switch to binary compatibility.  This way you will be sure not to break all the snap-on dlls by inadvertently making a change to the interface that they implement.

Yes, when you add "Implements ...." to a class, you are promising to implement the entire interface.  It could just be an empty sub or function or property, but it has to be there.  And VB will let you know if you forget--it's a compile error not to fully implement an interface.

Lastly, you cannot Implement events using native VB.  Subs, Functions, and Properties.  That's it.  I've seen articles here and there about how to include events in this model, but it involves what many people would consider hacks.  Were you just thinking out loud, or do you actually perceive a need for event(s) in your interface(s)?

Thanks for clarifying those points, still got a couple of questions if thats ok.

Firstly, I've managed to add a second Host.Property which returns a string value similar to the AppPath and AppTitle that you created as examples ( thanks by the way ).  However, are there any limitations on what data types can be returned using this method, for example I tried it using a collection which failed ( possible due to my own implementation but there ya go )?  

Also, could you just clarify using it as a group of projects rather than individual and compiling them etc... just so I dont cause myself problems in the future?

Many many thanks

Matt
Is it possible to have the parent app pass an object ( for example a top level menu option, like File (mnuFile I usually use ) and have it change properties of that object?
Sorry, just had another go and managed to find my problem when trying to use the collection object, i was simply returning it in a similar manner to a string or other data type, without using Set :\ DOH! See, told you I was a beginner :)

Cheers

Matt

So, can ignore that question from the ones above thanks, as it works, yippeee
"Also, could you just clarify using it as a group of projects rather than individual and compiling them etc... just so I dont cause myself problems in the future?"

The reason I suggested this, gets down to the intricacies of COM and "binary compatibility."  This is a fairly extensive topic and there's a ton of information on the web to learn more about it.  But basically what it boils down to is this (a very simplified explanation):

VB DLL's are COM components.  COM components expose "interfaces" which are uniquely identified (internally--not something you can set) by a GUID (globally unique ID).  The first time you compile a VB DLL, these IDs are compiled into the DLL.  You can think of each ID as a "contract number" for the interface, where the "contract" is the signature of public enums, methods, and events defined in the interface.

A program that references this DLL using early-binding (e.g., Dim obj As ISnapOn) is actually, internally, binding to this GUID.  That is, if the GUID is {12345} (not an actual GUID, just to illustrate), then that Dim statement is basically saying, "obj" is going to look for its interface by looking up {12345} in the registry.

If you then were to go into the DLL where ISnapOn is defined and make a change to a public enum, method, or property within that interface--then the next time VB compiles the DLL, things get interesting.

If you don't have binary compatibility set, VB will assign an entirely new GUID to that interface, and the next time you load the project where you reference that DLL, your reference will be listed as "(Missing)" in the project references.

If you do have binary compatibility set, and you make a change to that interface, when you try to compile, VB will scream at you about "breaking" binary compatibility--that is, reminding you that if you break it, all the projects that rely on that interface are going to be broken as well.  Getting back to the idea of a COM interface as a "contract," your COM interface basically says, "I promise that when you create an object with the GUID {12371821-2938749283-2398273948}, it will have the following public members: ..." and then if you break binary compatibility, your component is no longer living up to that promise.

(I can tell I'm rambling, so let me bring it in.)

When you're developing an entirely new interface (such as ISnapOn or ISnapOnHost), where you may need to be making these types of changes as you figure out the best design, if you develop each DLL within a project group in the IDE, and have "project compatibility" set for each DLL, then when you run the project group, VB will make sure to update all the internal references, in all the projects in the group, while you're debugging--even if you change an interface.  If, on the other hand, you were to develop each DLL separately, you would have to update your references in all the projects that use that DLL, every time you make a change to the DLL.

So, to make a long story short (too late), developing this type of VB DLL (e.g., SnapOnInterfaces) in a group project along with all the project(s) that reference it, using project compatibility, makes it a lot easier for you to manage change to your interfaces until you're sure they're solid.  Once they're solid and your sure the interfaces will not need to change any more, then I would say compile them--and then immediately after compiling them the first time, set them to use binary compatibility.  This will ensure that projects that use the DLL will continue to work, if you have to recompile the DLL in the future (i.e., to make a change to the implementation within the DLL code, but not to make a change to the DLL's interfaces--which is a no-no after setting binary compatibility).

Hope I didn't manage to confuse you even more.  Like I said, there are volumes of information on this topic out there on the web.  I'm glad things are working out for you on this project.

Jon
OK so I think I'm starting to understand it a little, final bit is kind of a no brainer questions probably.

So, i've opened my project group with the parent app, the interface and the example snap-in all loaded, how do I set up the project compatability.. In the component tab for each projects properties is a Version Compatability section with the options you've mentioned, could you just walk me through setting this up correct whilst developing the application, and what change(s) to make to any of the individual project(s) properties once the Interface itself is finished...

Is this why in the example I've seen of a such an application, when browsing the DLL with some third party software I saw they had a DoAction function which presumably returned true or false, presumably this is done so that different actions can be accomplished without needing to change the Interface itself, just the implementation of the interface within the individual snap-ins?

Wow, that almost sounded like i knew what i was talking about :) (although probably wrong ;P )

Many thanks

Matt
Sorry to post again, just a quick thank you for spending the time to post that rather lengthy response to explain binary compatability etc...

Nice one.
First off, sorry for the delay getting back to you.  Friday was a bit hectic.  Secondly, thanks for the kind words in the member comments.  I really do appreciate that.

About the project compatibility question: did you start with the skeleton project I gave you, or did you copy the code into your own projects?  Before I uploaded everything, I had already gone in and compiled the code and set binary compatibility on the interfaces DLL, so if you started with this project, we'll need to undo it.  If you started with your own projects, the default setting should already be project compatibility.

Yes, it sounds like you understand the concept.  The "DoAction" approach is one way to ensure a consistent interface, because, as you noted, you can change anything you want in the implementation and still keep binary compatibility, as long as you don't change the public signature.  (It's not the approach I'd use, just because it seems kinda hacky.  Although, if I were going to implement a DoAction function, I wouldn't have it return Boolean--why limit yourself to two values?  Better off returning a Long.)

Jon
"First off, sorry for the delay getting back to you."
- Don't worry, I know what it can be like some days :)

"Better off returning a Long"
- Yeah I see what you mean, at least then I can stil use 0 and 1 for true/false but any other value to mean different results.

"did you start with the skeleton project I gave you"
- Yes I did, at the moment I'm only using the example project until I understand what's going on fully (I'm getting there), also this is a concept I'm considering for the next release of an application I'm writing, the first release isn't public yet so it's just something I'm floating round to see if I can get my head around, and I think I'm starting to understand it.

So, maybe the answer is yes we'll need to remove any compatability settings already in place, so that I can put them in again following your guidance.

"thanks for the kind words in the member comments"
- You're most welcome, I work in a support role at my present employer and I know how nice it is when people are genually thankful for your help, of which I am :)

Cheers

Matt
Ok, to remove the compatibility settings, here's what I recommend.

1. close out of VB
2. delete the Group1 project group
3. open the SnapOnInterfaces project first
4. in project > component, delete the entire filename under binary compatibility
5. set it to project compatibility
6. save the project and exit VB
7. un-register SnapOnInterfaces.dll using this command:

    regsvr32 -u (path)\SnapOnInterfaces.dll
    (where path is the full path of the folder where the dll resides)

8. delete the files "SnapOnInterfaces.dll and "Copy of SnapOnInterfaces.dll"
9. now open the main project
10. add the SnapOnInterfaces project
11. go into the project > references for the main project
12. un-check the "missing" reference
13. check the "SnapOnInterfaces" reference (should point to the .vbp)
14. click ok
15. now add the TestSnapOn project
16. go into the project > references for the TestSnapOn project
17. un-check the "missing" reference
18. check the "SnapOnInterfaces" reference (should point to the .vbp)
19. click ok
20. save everything

That should do it.  Now if you need to make any changes to the interfaces in SnapOnInterfaces, you won't be hounded for breaking binary compatibility.  When your interfaces are solid, then go ahead and 1) compile the interfaces DLL, and then 2) set it to binary compatibility.

Let me know how it goes.
Hi yas,

Thanks for the post above, following those steps through without any problems or anything going wrong.  Have not got a new project group in which the main project and test snap on reference the SnapOnInterfaces project.

Last final thing before I leave you alone is:
"2) set it to binary compatibility."

Would this be compile the SnapOnInterfaces DLL...

Then in the SnapOnInterfaces project set it to binary and browse to the compiled project DLL.  Is that a) correct and b) the only thing that would need changing?

Cheers
Yep, that's pretty much it; however, I would a) create a copy of the dll after compiling, and b) browse to the copy of the dll under binary compatibility.  Keeping the version-compatibility copy separate from the original dll helps when you need to re-compile (to avoid locking issues if the original dll is currently in use).

Glad it's all working out for you!

Jon
o0o yeah that makes sense, hadn't thought of that.

Cheers mate, I'll close question and award points, your a star.

Thanks very much, and no doubt I shall be back on asking more questions when I get round to working on this stage of the project.

I am however planning to change my name on here as my name is currently of a previous employer and isn't practicaly as I don't work there no more.

Many Thanks

Matt
Shame there aint a grade higher than A :)

Thanks again, think I'll have fun with this one.