While writing the libvirt-cim provider, I decided that the constant repetition of common tasks in CMPI CIM providers was getting old. There were lots of things that get done over and over again that really should be generalized for the sake of the people who have to read and maintain the provider code. Fetching values from argument lists, instances, and object paths are some of the first tasks that I aimed to fix with libcmpiutil. In addition to these standardized utility functions, I created a standardized method dispatch infrastructure that makes writing method providers as simple and clean as it should have been in the first place.
Most of the CMPI providers I’ve seen (I can’t say I’m a real expert here) implement a method provider like this:
CMPIStatus Foo_InvokeMethod(CMPIMethodMI *self,
const CMPIContext *context,
const CMPIResult *results,
const CMPIObjectPath *reference,
const char *methodname,
const CMPIArgs *argsin,
CMPIArgs *argsout)
{
if (strcmp(methodname, “MethodA” == 0) {
/* Do MethodA work */
} else if (strcmp(methodname, “MethodB” == 0) {
/* Do MethodB work */
} else {
/* Return no such method */
}
}
If you’re lucky, the author will have written separate functions for each task (MethodA, MethodB, etc), however many of the providers I’ve seen just put the “work” in the body of the if, which results in hundreds of lines of muck that implements every possible method of the class in a massive function-from-hell. At the very least, the dispatch that fires off the appropriate code depending on the method name is implemented in every provider. Further, each method implementation must fetch and check its arguments for presence and type. Can you imagine how disgusting this gets for a class with 10 methods, each with a few arguments, when written by someone who doesn’t understand the benefit of functional depcomposition?
Enter the std_invokemethod module of libcmpiutil. Not only is the dispatch code modularized in such a way that “encourages” (read: “forces” the author to write separate functions, it also does argument checking. It makes sure that all of your parameters are present and of the correct type before it calls your handler function. The handler can then grab the arguments without excessive checks at every stage. The above method provider becomes this:
static CMPIStatus methodA(CMPIMethodMI *self,
const CMPIContext *context,
const CMPIResult *results,
const CMPIObjectPath *reference,
const CMPIArgs *argsin,
CMPIArgs *argsout)
{
/* Do MethodA work */
}/* Another function here for MethodB */
static struct method_handler _methodA = {
.name = “MethodA”,
.handler = methodA,
.args = {{“ParamX”, CMPI_uint16},
{“ParamY”, CMPI_string},
ARG_END
}
};/* Another handler struct here for MethodB */
static struct method_handler *my_handlers[] = {
&_methodA,
&_methodB,
NULL
};
Instead of using the standard MI creation macro, you use a special one for the std_invokemethod module, which takes a pointer to the handler list. Then, you sit back and relax as your handler functions are dispatched pre-checked for argument type correctness!