Dienstag, 12. April 2016

Avoiding Temporal Coupling - Part 2/2

In part one of this post I briefly introduced the problems which come with temporal coupling and "passing a block" as a technique to overcome those problems. In this last part of the post I want to demonstrate "passing a block" in pure C.

Lets start with a basic implementation:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

typedef void (*FileCommandFunc)(FILE*);

int withFile(const char* fileName, const char* fileAccessMode,
             FileCommandFunc func) {
    FILE *myFile = fopen(fileName, fileAccessMode);
    if (NULL == myFile) {
        fprintf(stderr, "Error opening file '%s': %s\n",
                fileName, strerror(errno));
        return EXIT_FAILURE;
    }
 
    func(myFile);

    fclose(myFile);
    return EXIT_SUCCESS;
}

void printFirstLine(FILE* file) {
    char buffer[1000] = {0};
    if (fgets(buffer, sizeof(buffer), file)!=NULL) {
        printf("%s", buffer); 
    }
}

void printWholeFile(FILE* file) {
    char buffer[1000] = {0};
    while (fgets(buffer, sizeof(buffer), file)!=NULL) {
        printf("%s", buffer); 
    }
}

int main() {
    withFile("demo.txt", "r", printFirstLine);
    withFile("demo.txt", "r", printWholeFile);
}
Function withFile takes three arguments: the name of the file to be opened, the access mode (read, write, append...) and a file command. The latter is a pointer to a function which accepts a file object as only argument.

printFirstLine and printWholeFile are two example commands. Both employ fgets to traverse the file line by line.

In main we see "passing a block" in action. File "demo.txt" is opened in read mode and the actions implemented in the command functions are executed against the file. Error handling and closing of the file handle takes place inside withFile - the user of the function doesn't have to bother with that.

When coding this first draft I realized that my solution is quite limited. Inside the command you can only work with the file passed in and global variables. Other variables aren't simply accessible.

With the GNU Compiler Collection gcc there is the nested functions  feature which nicely works around this limitation:
int main() {
    const char* header = "Content of demo.txt";

    void printWholeFile(FILE* file) {
        puts(header);
        char buffer[1000] = {0};
        while (fgets(buffer, sizeof(buffer), file)!=NULL) {
            printf("%s", buffer); 
        }
    }

    withFile("demo.txt", "r", printWholeFile);
}
I admit the example is a little bit constructed since you could easily print the header before the call of withFile but there you go. The idea is simple: Since printWholeFile is now defined inside main, printWholeFile has access to all global variables and the local variables of main as well.

The downside of this is that it only works in gcc. Here is a more general implementation which uses a technique you find for example in the glibc comparision functions.
The command function accepts a second argument which is a void pointer. This allows us to pass anything into the command. We only have to cast it to the right type before using it. This is basically an accepted disabling of C's type checking.

Here is the code:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

// withFile.h
typedef void (*FileCommandFunc)(FILE*, void*);

typedef struct {
    const char* name;
    const char* accessMode;
    FileCommandFunc func;
    void* commandArgs;
} withFileArgs;

// withFile.c
int withFile(withFileArgs args) {
    FILE *myFile = fopen(args.name, args.accessMode);
    if (NULL == myFile) {
        fprintf(stderr, "Error opening file '%s': %s\n",
                args.name, strerror(errno));
        return EXIT_FAILURE;
    }
 
    args.func(myFile, args.commandArgs);

    fclose(myFile);
    return EXIT_SUCCESS;
}

// production code

// the commands
void printFirstLine(FILE* file, void* commandArgs) {
    const char* header = commandArgs;

    puts(header);
    char buffer[1000] = {0};
    if (fgets(buffer, sizeof(buffer), file)!=NULL) {
        printf("%s", buffer); 
    }
}

void printWholeFile(FILE* file, void* notUsed) {
    char buffer[1000] = {0};
    while (fgets(buffer, sizeof(buffer), file)!=NULL) {
        printf("%s", buffer); 
    }
}

// using the commands
int main() {
    withFile((withFileArgs){.name="demo.txt", 
                            .accessMode="r", 
                            .func=printFirstLine, 
                            .commandArgs="Content of demo.txt"});

    withFile((withFileArgs){.name="demo.txt", 
                            .accessMode="r", 
                            .func=printWholeFile});
}
For convinces I've also replaced the growing number of arguments of withFile with something you call configuration object in Javascript, in C it is the beautiful marriage of designated initializers and compound literals, both introduced in C99.

Also note that in function printFirstLine the cast from the void pointer commandArgs to the const char pointer header is implicit - no extra cast operation needed on the right hand side of the equals operator.

1 Kommentar:


  1. this concept is new to me thanks for publishing i got more information from your blog it is really interesting.

    php Training in Chennai

    AntwortenLöschen