2013年12月10日 星期二

Another private problem in C.

Rather than the private property in the C structures, the optional-private property(or usually be called as namespace) is more interesting and suffering. The situation might be, there is a big system, which contains several subsystems, each has its own interfaces to be accessed by the other subsystems, or the application if it is a library. Beside the interface, there are also some implement helper functions which are expected to be called only within the subsystem, such as callback functions, helper functions, reused code fragments... etc. As a simple example, say we have a subsystem handles socket stuff:



/* Sockets.c */
typedef ssize_t (*SocketOp)(int fd, void *Msg, size_t MsgLen, void *Argu);
struct Socket
{
    int fd;
    void *Argu;
    SocketOp ReadCB;
    SocketOp WriteCB;
}

ssize_t SocketRead(struct Socket *pSock, void *Msg, size_t MsgLen)
{
    return pSock->ReadCB(pSock->fd, Msg, MsgLen, pSock->Argu);
}
And several implements:
/* Socket_NonBlocking.c */
ssize_t NBReadCB(int fd, void *Msg, size_t MsgLen, void *Argu)
{
    return recv(fd, Msg, MsgLen, MSG_DONTWAIT);
}

ssize_t NBWriteCB(int fd, void *Msg, size_t MsgLen, void *Argu)
{
    return send(fd, Msg, MsgLen, MSG_DONTWAIT);
}

/* Socket_NonConnect.c */
ssize_t NCReadCB(int fd, void *Msg, size_t MsgLen, void *Argu)
{
    struct sockaddr *Addr = (struct sockaddr *)Argu;

    return recvfrom(fd, Msg, MsgLen, 0, Addr, sizeof(struct sockaddr));
}

ssize_t NCWriteCB(int fd, void *Msg, size_t MsgLen, void *Argu)
{
    struct sockaddr *Addr = (struct spckaddr *)Argu;

    return recvfrom(fd, Msg, MsgLen, 0, Addr, sizeof(struct sockaddr));
}
Here the callbacks NBReadCB, NBWriteCB, NCReadCB and NCWriteCB are expected to only be assigned to the pointers in Sockets.c, instead of being directly called in any position with any reason. Then the dilemma comes for whether setting these callbacks static or not: if it is not, then there should be an corresponding header files claims the proto type of these callbacks and be included by Socket.c, which makes it possible to be called directly for every one who include the header; on the other hand, if they are set to static, they could be called only within the file, which implies the callbacks are going messed up with the interfaces: Sockets.c, in a same big file.

Pattern of solutions is combining both the interfaces and implementations into one file, only when it is going to compile. For example, including all the implements source files, say:
#include "Sockets_NonBlocking.c"
#include "Sockets_NonConnect.c"
,which have been set to static, in the main part/interface part. Note that it should only be done if it is sure to the only unique instances of the whole system. One other similar way is a script to doing the combination before compiling, such as amalgamation in SQLite.

A different solution has been seen to prevent the dilemma would be use the specific #define in the interface source file and checking by #ifdef in every implements, to set the limitations of the implements header includers, which looks like:
/* Sockets.c */
#define __SOCKET_INNER_
#include "Socket_NonBlocking.h"

/* Sockets_NonBlocking.h */
#ifndef __SOCKET_INNER_
/* Any way makes compiler yell... */
#else
ssize_t NBReadCB(int fd, void *Msg, size_t MsgLen, void *Argu);
ssize_t NBWriteCB(int fd, void *Msg, size_t MsgLen, void *Argu);
#endif

to prevent someone include Socket_NonBlocking.h directly and not be expected to, although a blind define adding could break this. Also defect comparing with previous ways is symbol of these callbacks will be listed and exposed, which might causes huge differences in size and performance if it is compiled to be a library. The including method is better choice so far in my opinion.

沒有留言:

張貼留言