6

Using Delphi 2010, I am needing to write a program to support modules, or plug-ins. Although a little contrived, assume I have an app which converts datafiles/text files. It will support 30 input formats and those same 30 formats as outputs. The first release will probably only implement a few of these formats. My challenge is that I want a data driven process flow.

For example, assume I have a PARSE_FILE routine. If my input data file format is 'Format_A', then then when I call PARSE_FILE, it should know to use PARSE_FILE_Format_A, as opposed to the other 29 different versions of the PARSE_FILE routine.

PARSE_FILE is just an an example. I will probably have 60 different common functions, LOAD_FILE, GET_DELIMITER, PARSE_FILE, etc, but each of these functions will be a little different for each of the 30 different formats. What technique can I use so that if I am Loading a file with FORMAT_A, each one of these 60 different common routines uses the proper 'version' of these 60 routines?

Keep in mind that I am starting with just 5 input formats, and will add other formats later, so I need a way of centrally defining this "mapping', so wherever these routines are used throughout my code, the proper version of the routine will be used even though I call the generic version.

Cody Gray - on strike
  • 239,200
  • 50
  • 490
  • 574
user1009073
  • 3,160
  • 7
  • 40
  • 82
  • 3
    I'd go with Interfaces, define a IMyPlugin interface, class(es) that implement this interface, each *.dll exports a function called "RegisterPlugin(AInterface: IMyPlugin)", when app starts, it scans the "plugin folder" for files, for each "plugin file"(dll), you try to load it and call it's "RegisterPlugin" function, when plugin is loaded, you can call methods on the interface to manipulate data. my 2 cents (: –  Feb 26 '13 at 15:45
  • you either go with DLL and COM-compatible `IInterface` type (COM-standardized data types only, so that you would not crash everything when EXE and DLL would have different compiler version or options). Or you go with BPL, like VCL itself is made. JediVCL has some simplistic plugin infrastructure, but it would that base is not rocket science. Hard id planning API and that is always your creative work. There is good article about 1st approach, dunno if you can read it via Google Translate: http://www.gunsmoker.ru/2011/12/delphi.html – Arioch 'The Feb 26 '13 at 16:26

1 Answers1

5
  1. Define your set of standard routines that you require every plugin module to implement in an interface type. Say, IFileFormatHandler, which contains a PARSE_FILE function, etc.
  2. Following principles of interface design and separation of duties, avoid putting functions in an interface that some implementations will not be interested in or able to implement. Define optional functions in a separate interface that an implementing class can choose to implement or not. For example, if you anticipate that there will be some file formats that your app will read but for various reasons will not be able to write, then you should put read operations in one interface and write operations in a different interface.
  3. If you will be authoring all the plugins in Delphi, you should use BPL packages to share common types. Place your IFileFormatHandler interface type in one BPL package (ie, Common.bpl) so that all the modules can reference the common interface type. Each plugin module itself is also in its own BPL package. (Multiple file format handlers could exist in the same BPL package, but the baseline example is one per BPL)
  4. If this is your first swing at building a plugin architecture, don't try to cover multilanguage support at the same time. Stick with Delphi for now. Write the app and the modules in Delphi. Build a modular project to completion in Delphi, then take a step back and spend some time understanding COM or binary interface requirements to support modules written in languages other than Delphi.
  5. In your common BPL package, also define a global function that modules can call to make themselves known to the host application - RegisterPlugin(name: string; instance: IFileFormatHandler) for example. This registers the plugin name and instance in an internal list that the host application can use to find out what plugins are available and call them.
  6. For each file handling plugin module, define a class that implements the common interface(s) defined in the shared BPL package. In the unit initialization of the class, call the RegisterPlugion() function to register the class with the host app.
  7. The host application uses the common package, and the module packages each use the common package.
  8. The host application only interacts with the module implementations via the functions defined in the common interface(s).
  9. The host application can use IS to test whether a particular module object instance implements an optional interface, and AS to obtain that optional interface.
  10. Interfaces are reference counted in Delphi, so as long as the host application holds onto a reference to a module object instance (via RegisterPlugin, for example) the module instance will be kept alive and in memory. When the last reference is released, the module instance will be disposed of.
  11. The host application will need to find and load the module package bpls at runtime, using LoadPackage or similar fn.
  12. Sharing one list of plugin module instances across the app will work fine for most single-threaded applications. If you anticipate using these plugin modules in multiple threads at the same time, consider shifting this design to a factory pattern instead of holding singleton instances of modules in memory. It is far easier and usually more performant to manage multithreading by constructing instances on demand within the thread of use using a factory than to make one instance that must be safe to call from multiple threads.
dthorpe
  • 35,318
  • 5
  • 75
  • 119
  • This appears to be strictly class based. Can I do this with NON-Class functions? For a contrived example, if File_FormatA is being processed, then every time I call MinMax, use the one associated with File_FormatA, but if using File_FormatB, use MinMax found in the File_FormatB code line. (Assume File_FormatA and File_FormatB are not using clases. That may not be the case, but I am just trying to understand this better.) – user1009073 Feb 26 '13 at 20:20
  • 1
    A class is a collection of related functions. Yes, you can write your code as global functions instead of as members of a class. How will the host application call the MinMax function in File_FormatB, and how will you distinguish that from the MinMax function File_FormatA? You could keep lists of function pointers and look up the function pointer for the MinMax function associated with File_FormatB at runtime. Or you could define your functions in classes and have the compiler do all that work for you. Classes and interfaces were created to solve this problem. Use them. – dthorpe Feb 26 '13 at 20:24
  • Actually when developing BPL-based plugin system, i arranged such global functions into `virtual class methods`, then i passed them together by assigning single TClass variable. But code completion was nto playing well with that :-( – Arioch 'The Feb 26 '13 at 21:41
  • @Arioch'The yes, you can do that too, but if you're going to go to all the trouble of forcing a required class hierarchy onto the plugin modules, you might as well allow them to have instance data. Storing all internal state in singleton globals doesn't scale well. – dthorpe Feb 26 '13 at 22:26
  • Poor design decisions 15 years ago is no excuse for advising new code to follow the same path. – dthorpe Feb 27 '13 at 17:36