views:

288

answers:

2

I'm trying to write some reasonably generic networking code. I have several kinds of packets, each represented by a different struct. The function where all my sending occurs looks like:

- (void)sendUpdatePacket:(MyPacketType)packet{ 
    for(NSNetService *service in _services) 
        for(NSData *address in [service addresses]) 
            sendto(_socket, &packet, sizeof(packet), 0, [address bytes], [address length]); 
}

I would really like to be able to send this function ANY kind of packet, not just MyPacketType packets.

I thought maybe if the function def was:

- (void)sendUpdatePacket:(void*)packetRef

I could pass in anykind of pointer to packet. But, without knowing the type of packet, I can't dereference the pointer.

How do I write a function to accept any kind of primitive/struct as its argument?

+3  A: 

What you are trying to achieve is polymorphism, which is an OO concept.

So while this would be quite easy to implement in C++ (or other OO languages), it's a bit more challenging in C.

One way you could get around is it to create a generic "packet" structure such as this:

typedef struct {
    void* messageHandler;
    int   messageLength;
    int*  messageData;
} packet;

Where the messageHandler member is a function pointer to a callback routine which can process the message type, and the messageLength and messageData members are fairly self-explanatory.

The idea is that the method which you pass the packetStruct to would use the Tell, Don't Ask principle to invoke the specific message handler pointer to by messageHandler, passing in the messageLength and messageData without interpreting it.

The dispatch function (pointed to by messageHandler) would be message-specific and will be able to cast the messageData to the appropriate meaningful type, and then the meaningful fields can be extracted from it and processed, etc.

Of course, this is all much easier and more elegant in C++ with inheritance, virtual methods and the like.


Edit:

In response to the comment:

I'm a little unclear how "able to cast the messageData to the appropriate meaningful type, and then the meaningful fields can be extracted from it and processed, etc." would be accomplished.

You would implement a handler for a specific message type, and set the messageHandler member to be a function pointer to this handler. For example:

void messageAlphaHandler(int messageLength, int* messageData)
{
    MessageAlpha* myMessage = (MessageAlpha*)messageData;

    // Can now use MessageAlpha members...
    int messageField = myMessage->field1;
    // etc...
}

You would define messageAlphaHandler() in such a way to allow any class to get a function pointer to it easily. You could do this on startup of the application so that the message handlers are registered from the beginning.

Note that for this system to work, all message handlers would need to share the same function signature (i.e. return type and parameters).

Or for that matter, how messageData would be created in the first place from my struct.

How are you getting you packet data? Are you creating it manually, reading it off a socket? Either way, you need to encode it somewhere as a string of bytes. The int* member (messageData) is merely a pointer to the start of the encoded data. The messageLength member is the length of this encoded data.

In your message handler callback, you don't want probably don't want to continue to manipulate the data as raw binary/hex data, but instead interpret the information in a meaningful fashion according to the message type.

Casting it to a struct essentially maps the raw binary information on to a meaningful set of attributes matching to the protocol of the message you are processing.

LeopardSkinPillBoxHat
I'm a little unclear how "able to cast the messageData to the appropriate meaningful type, and then the meaningful fields can be extracted from it and processed, etc." would be accomplished.Or for that matter, how messageData would be created in the first place from my struct.Is this the technique that sendto() uses? That seems unlikely since it is unaware of data type at all. Based on the prototype for sendto() just knowing the address of the data, and it's length is enough information.
SooDesuNe
@SooDesuNe - I have added more information to my answer.
LeopardSkinPillBoxHat
PillBox, thanks for your great response. I didn't really think about using function pointers in that way. The solution I got below via email is a little more on-point to the needs of my question though.
SooDesuNe
@SooDesuNe - thanks for following up and glad you could solve your problem.
LeopardSkinPillBoxHat
A: 

The key is that you must realize that everything in a computer is just an array of bytes (or, words, or double words).

ZEN MASTER MUSTARD is sitting at his desk staring at his monitor staring at a complex pattern of seemingly random characters. A STUDENT approaches.

Student: Master? May I interrupt?

Zen Master Mustard: You have answered your own inquiry, my son.

S: What?

ZMM: By asking your question about interrupting me, you have interrupted me.

S: Oh, sorry. I have a question about moving structures of varying size from place to place.

ZMM: If that it true, then you should consult a master who excels at such things. I suggest, you pay a visit to Master DotPuft, who has great knowledge in moving large metal structures, such as tracking radars, from place to place. Master DotPuft can also cause the slightest elements of a feather-weight strain gage to move with the force of a dove's breath. Turn right, then turn left when you reach the door of the hi-bay. There dwells Master DotPuft.

S: No, I mean moving large structures of varying sizes from place to place in the memory of a computer.

ZMM: I may assist you in that endeavor, if you wish. Describe your problem.

S: Specifically, I have a c function that I want to accept several different types of structs (they will be representing different type of packets). So my struct packets will be passed to my function as void*. But without knowing the type, I can't cast them, or really do much of anything. I know this is a solvable problem, because sento() from socket.h does exactly that:

    ssize_t sendto(int socket, const void *message, size_t length, int flags, const struct sockaddr *dest_addr,socklen_t dest_len);

where sendto would be called like:

    sendto(socketAddress, &myPacket, sizeof(myPacket), Other args....);

ZMM: Did you describe your problem to Zen Master MANTAR! ?

S: Yeah, he said, "It's just a pointer. Everything in C is a pointer." When I asked him to explain, he said, "Bok, bok, get the hell out of my office."

ZMM: Truly, you have spoken to the master. Did this not help you?

S: Um, er, no. Then I asked Zen Master Max.

ZMM: Wise is he. What was his advice to you useful?

S: No. When I asked him about sendto(), he just swirled his fists in the air. It's just an array of bytes."

ZMM: Indeed, Zen Master Max has tau.

S: Yeah, he has tau, but how do I deal with function arguments of type void*?

ZMM: To learn, you must first unlearn. The key is that you must realize that everything in a computer is just an array of bytes (or, words, or double words). Once you have a pointer to the beginning of a buffer, and the length of the buffer, you can sent it anywhere without a need to know the type of data placed in the buffer.

S: OK.

ZMM: Consider a string of man-readable text. "You plan a tower that will pierce the clouds? Lay first the foundation of humility." It is 82 bytes long. Or, perhaps, 164 if the evil Unicode is used. Guard yourself against the lies of Unicode! I can submit this text to sendto() by providing a pointer to the beginning of the buffer that contains the string, and the length of the buffer, like so:

char characterBuffer[300];      // 300 bytes
strcpy(characterBuffer, "You plan a tower that will pierce the clouds? Lay first the foundation of humility.");
// note that sizeof(characterBuffer) evaluates to 300 bytes.
sendto(socketAddress, &characterBuffer, sizeof(characterBuffer));

ZMM: Note well that the number of bytes of the character buffer is automatically calculated by the compiler. The number of bytes occupied by any variable type is of a type called "size_t". It is likely equivalent to the type "long" or "unsinged int", but it is compiler dependent.

S: Well, what if I want to send a struct?

ZMM: Let us send a struct, then.

struct
{
     int integerField;          // 4 bytes
     char characterField[300];  // 300 bytes
     float floatField;          // 4 bytes
} myStruct;

myStruct.integerField = 8765309;
strcpy(myStruct.characterField, "Jenny, I got your number.");
myStruct.floatField = 876.5309;

// sizeof(myStruct) evaluates to 4 + 300 + 4 = 308 bytes
sendto(socketAddress, &myStruct, sizeof(myStruct);

S: Yeah, that's great at transmitting things over TCP/IP sockets. But what about the poor receiving function? How can it tell if I am sending a character array or a struct?

ZMM: One way is to enumerate the different types of data that may be sent, and then send the type of data along with the data. Zen Masters refer to this as "metadata", that is to say, "data about the data". Your receiving function must examine the metadata to determine what kind of data (struct, float, character array) is being sent, and then use this information to cast the data back into its original type. First, consider the transmitting function:

enum
{
    INTEGER_IN_THE_PACKET =0 ,
    STRING_IN_THE_PACKET =1,  
    STRUCT_IN_THE_PACKET=2
} typeBeingSent;

struct
{
     typeBeingSent dataType;
     char data[4096];
} Packet_struct;

Packet_struct myPacket;

myPacket.dataType = STRING_IN_THE_PACKET;
strcpy(myPacket.data, "Nothing great is ever achieved without much enduring.");
sendto(socketAddress, myPacket, sizeof(Packet_struct);

myPacket.dataType = STRUCT_IN_THE_PACKET;
memcpy(myPacket.data, (void*)&myStruct, sizeof(myStruct);
sendto(socketAddress, myPacket, sizeof(Packet_struct);

S: All right.

ZMM: Now, just us walk along with the receiving function. It must query the type of the data that was sent and the copy the data into a variable declared of that type. Forgive me, but I forget the exact for of the recvfrom() function.

   char[300] receivedString;
   struct myStruct receivedStruct;  

   recvfrom(socketDescriptor, myPacket, sizeof(myPacket);

   switch(myPacket.dataType)
   {
       case STRING_IN_THE_PACKET:
            // note the cast of the void* data into type "character pointer"
            &receivedString[0] = (char*)&myPacket.data; 
            printf("The string in the packet was \"%s\".\n", receivedString); 
            break;

       case STRUCT_IN_THE_PACKET:
            // note the case of the void* into type "pointer to myStruct"
            memcpy(receivedStruct, (struct myStruct *)&myPacket.data, sizeof(receivedStruct));
            break;
    }

ZMM: Have you achieved enlightenment? First, one asks the compiler for the size of the data (a.k.a. the number of bytes) to be submitted to sendto(). You send the type of the original data is sent along as well. The receiver then queries for the type of the original data, and uses it to call the correct cast from "pointer to void" (a generic pointer), over to the type of the original data (int, char[], a struct, etc.)

S: Well, I'll give it a try.

ZMM: Go in peace.

SooDesuNe