Medical Imaging Interaction Toolkit  2024.06.00
Medical Imaging Interaction Toolkit
Example 6 - Spell Checker Service Module

In this example, we complicate things further by defining a new service that uses an arbitrary number of dictionary services to perform its function. More precisely, we define a spell checker service which will aggregate all dictionary services and provide another service that allows us to spell check passages using our underlying dictionary services to verify the spelling of words. Our module will only provide the spell checker service if there are at least two dictionary services available. First, we will start by defining the spell checker service interface in a file called spellcheckservice/ISpellCheckService.h:

#include <string>
#include <vector>
#ifdef US_BUILD_SHARED_LIBS
#ifdef Example_spellcheckservice_EXPORTS
#define SPELLCHECKSERVICE_EXPORT US_ABI_EXPORT
#else
#define SPELLCHECKSERVICE_EXPORT US_ABI_IMPORT
#endif
#else
#define SPELLCHECKSERVICE_EXPORT US_ABI_EXPORT
#endif
struct SPELLCHECKSERVICE_EXPORT ISpellCheckService
{
// Out-of-line virtual destructor for proper dynamic cast
// support with older versions of gcc.
virtual ~ISpellCheckService();
virtual std::vector<std::string> Check(const std::string& passage) = 0;
};

The service interface is quite simple, with only one method that needs to be implemented. Because we provide an empty out-of-line destructor (defined in the file ISpellCheckService.cpp) we must export the service interface by using the module specific SPELLCHECKSERVICE_EXPORT macro.

In the following source code, the module needs to create a complete list of all dictionary services; this is somewhat tricky and must be done carefully if done manually via service event listeners. Our module makes use of the ServiceTracker and ServiceTrackerCustomizer classes to robustly react to service events related to dictionary services. The module activator of our module now additionally implements the ServiceTrackerCustomizer class to be automatically notified of arriving, departing, or modified dictionary services. In case of a newly added dictionary service, our ServiceTrackerCustomizer::AddingService() implementation checks if a spell checker service was already registered and if not registers a new ISpellCheckService instance if at lead two dictionary services are available. If the number of dictionary services drops below two, our ServiceTrackerCustomizer implementation un-registers the previously registered spell checker service instance. These actions must be performed in a synchronized manner to avoid interference from service events originating from different threads. The implementation of our module activator is done in a file called spellcheckservice/Activator.cpp:

#include "IDictionaryService.h"
#include "ISpellCheckService.h"
#include <map>
#include <cstring>
#include <memory>
namespace {
class US_ABI_LOCAL Activator : public ModuleActivator, public ServiceTrackerCustomizer<IDictionaryService>
{
private:
class SpellCheckImpl : public ISpellCheckService
{
private:
typedef std::map<ServiceReference<IDictionaryService>, IDictionaryService*> RefToServiceType;
RefToServiceType m_refToSvcMap;
public:
std::vector<std::string> Check(const std::string& passage)
{
std::vector<std::string> errorList;
// No misspelled words for an empty string.
if (passage.empty())
{
return errorList;
}
// Tokenize the passage using spaces and punctuation.
const char* delimiters = " ,.!?;:";
char* passageCopy = new char[passage.size()+1];
std::memcpy(passageCopy, passage.c_str(), passage.size()+1);
char* pch = std::strtok(passageCopy, delimiters);
{
// Lock the m_refToSvcMap member using your favorite thread library here...
// MutexLocker lock(&m_refToSvcMapMutex)
// Loop through each word in the passage.
while (pch)
{
std::string word(pch);
bool correct = false;
// Check each available dictionary for the current word.
for (RefToServiceType::const_iterator i = m_refToSvcMap.begin();
(!correct) && (i != m_refToSvcMap.end()); ++i)
{
IDictionaryService* dictionary = i->second;
if (dictionary->CheckWord(word))
{
correct = true;
}
}
// If the word is not correct, then add it
// to the incorrect word list.
if (!correct)
{
errorList.push_back(word);
}
pch = std::strtok(nullptr, delimiters);
}
}
delete[] passageCopy;
return errorList;
}
std::size_t AddDictionary(const ServiceReference<IDictionaryService>& ref, IDictionaryService* dictionary)
{
// Lock the m_refToSvcMap member using your favorite thread library here...
// MutexLocker lock(&m_refToSvcMapMutex)
m_refToSvcMap.insert(std::make_pair(ref, dictionary));
return m_refToSvcMap.size();
}
std::size_t RemoveDictionary(const ServiceReference<IDictionaryService>& ref)
{
// Lock the m_refToSvcMap member using your favorite thread library here...
// MutexLocker lock(&m_refToSvcMapMutex)
m_refToSvcMap.erase(ref);
return m_refToSvcMap.size();
}
};
virtual IDictionaryService* AddingService(const ServiceReference<IDictionaryService>& reference)
{
IDictionaryService* dictionary = m_context->GetService(reference);
std::size_t count = m_spellCheckService->AddDictionary(reference, dictionary);
if (!m_spellCheckReg && count > 1)
{
m_spellCheckReg = m_context->RegisterService<ISpellCheckService>(m_spellCheckService.get());
}
return dictionary;
}
virtual void ModifiedService(const ServiceReference<IDictionaryService>& /*reference*/,
IDictionaryService* /*service*/)
{
// do nothing
}
virtual void RemovedService(const ServiceReference<IDictionaryService>& reference,
IDictionaryService* /*service*/)
{
if (m_spellCheckService->RemoveDictionary(reference) < 2 && m_spellCheckReg)
{
m_spellCheckReg.Unregister();
m_spellCheckReg = 0;
}
}
std::unique_ptr<SpellCheckImpl> m_spellCheckService;
ServiceRegistration<ISpellCheckService> m_spellCheckReg;
ModuleContext* m_context;
std::unique_ptr<ServiceTracker<IDictionaryService> > m_tracker;
public:
Activator()
: m_context(nullptr)
{}
void Load(ModuleContext* context)
{
m_context = context;
m_spellCheckService.reset(new SpellCheckImpl);
m_tracker.reset(new ServiceTracker<IDictionaryService>(context, this));
m_tracker->Open();
}
void Unload(ModuleContext* /*context*/)
{
// NOTE: The service is automatically unregistered
m_tracker->Close();
}
};
}

Note that we do not need to unregister the service in stop() method, because the C++ Micro Services library will automatically do so for us. The spell checker service that we have implemented is very simple; it simply parses a given passage into words and then loops through all available dictionary services for each word until it determines that the word is correct. Any incorrect words are added to an error list that will be returned to the caller. This solution is not optimal and is only intended for educational purposes.

Note
In this example, the service interface and implementation are both contained in one module which exports the interface class. However, service implementations almost never need to be exported and in many use cases it is beneficial to provide the service interface and its implementation(s) in separate modules. In such a scenario, clients of a service will only have a link-time dependency on the shared library providing the service interface (because of the out-of-line destructor) but not on any modules containing service implementations. This often leads to modules which do not export any symbols at all and hence need to be loaded into the running process manually or by using the auto-loading mechanism.
Due to the link dependency of our module to the module containing the dictionary service interface as well as a default implementation for it, there might be at least one dictionary service registered when our module is loaded, depending on your linker settings (e.g. on Windows, the linker usually by default optimizes the link dependency away since our module does not actually use any symbols from the dictionaryservice module. On Linux however, the link dependency is kept by default.) To observe the dynamic registration and un-registration of our spell checker service, we require the availability of at least two dictionary services.

For an introduction how to compile our source code, see Example 1 - Service Event Listener.

After running the usCoreExamplesDriver program we should make sure that the module from Example 1 is active. We can use the s shell command to get a list of all modules, their state, and their module identifier number. If the Example 1 module is not active, we should load the module using the load command and the module's identifier number or name that is displayed by the s command. Now we can load the spell checker service module by entering the l spellcheckservice command which will also trigger the loading of the dictionaryservice module containing the english dictionary:

CppMicroServices-build> bin/usCoreExamplesDriver
> l eventlistener
Starting to listen for service events.
> l spellcheckservice
Ex1: Service of type IDictionaryService/1.0 registered.
> s
Id | Name                 | Status
-----------------------------------
 - | dictionaryclient     | -
 - | dictionaryclient2    | -
 - | dictionaryclient3    | -
 - | frenchdictionary     | -
 - | spellcheckclient     | -
 1 | CppMicroServices     | LOADED
 2 | Event Listener       | LOADED
 3 | Dictionary Service   | LOADED
 4 | Spell Check Service  | LOADED
>

To trigger the registration of the spell checker service from our module, we load the frenchdictionary using the l frenchdictionary command. If the module from Example 1 is still active, then we should see it print out the details of the service event it receives when our new module registers its spell checker service:

CppMicroServices-build> bin/usCoreExamplesDriver
> l frenchdictionary
Ex1: Service of type IDictionaryService/1.0 registered.
Ex1: Service of type ISpellCheckService/1.0 registered.
>

We can experiment with our spell checker service's dynamic availability by stopping the french dictionary service; when the service is stopped, the eventlistener module will print that our module is no longer offering its spell checker service. Likewise, when the french dictionary service comes back, so will our spell checker service. We create a client for our spell checker service in Example 7. To exit the usCoreExamplesDriver program, we use the q command.

Next: Example 7 - Spell Checker Client Module

Previous: Example 5 - Service Tracker Dictionary Client Module

usServiceProperties.h
usServiceInterface.h
usModuleActivator.h
ModuleContext
Definition: usModuleContext.h:91
US_USE_NAMESPACE
#define US_USE_NAMESPACE
Definition: usGlobalConfig.h:75
US_ABI_LOCAL
#define US_ABI_LOCAL
Definition: usGlobalConfig.h:117
US_EXPORT_MODULE_ACTIVATOR
#define US_EXPORT_MODULE_ACTIVATOR(_activator_type)
Export a module activator class.
Definition: usModuleActivator.h:119
usServiceTracker.h
usModuleContext.h
usServiceTrackerCustomizer.h