передача типа примитива или структуры в качестве аргумента функции - PullRequest
0 голосов
/ 19 ноября 2009

Я пытаюсь написать достаточно общий сетевой код. У меня есть несколько видов пакетов, каждый из которых представлен своей структурой. Функция, в которой происходит вся моя отправка, выглядит так:

- (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]); 
}

Мне бы очень хотелось, чтобы эта функция могла отправлять ЛЮБОЙ вид пакетов, а не только пакеты MyPacketType.

Я подумал, может быть, если функция def была:

- (void)sendUpdatePacket:(void*)packetRef

Я мог бы передать любой указатель на пакет. Но, не зная типа пакета, я не могу разыменовать указатель.

Как мне написать функцию, которая принимает любой тип примитива / структуры в качестве аргумента?

Ответы [ 2 ]

4 голосов
/ 19 ноября 2009

То, чего вы пытаетесь достичь, это полиморфизм , который является концепцией ОО.

Так что, хотя это было бы довольно легко реализовать в C ++ (или других языках OO), это немного сложнее в C.

Один из способов обойти это - создать общую структуру «пакетов», такую ​​как эта:

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

Где член messageHandler является указателем на функцию обратного вызова, которая может обрабатывать тип сообщения, а члены messageLength и messageData говорят сами за себя.

Идея состоит в том, что метод, которому вы передаете packetStruct, будет использовать принцип Tell, Don't Ask , чтобы вызвать конкретный указатель обработчика сообщений на messageHandler, передавая messageLength и messageData без интерпретации.

Функция отправки (указанная messageHandler) будет зависеть от сообщения и сможет приводить messageData к соответствующему значимому типу, а затем значимые поля могут быть извлечены из него и обработаны и т. Д.

Конечно, все это намного проще и элегантнее в C ++ с наследованием, виртуальными методами и тому подобным.


Edit:

В ответ на комментарий:

Мне немного непонятно, как "умеет кастовать messageData для соответствующего значимый тип, а затем значимые поля могут быть извлечены от него и обрабатывается и т. д. » свершившийся.

Вы бы реализовали обработчик для определенного типа сообщения и задали бы член messageHandler как указатель на функцию для этого обработчика. Например:

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

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

Вы бы определили messageAlphaHandler() таким образом, чтобы любой класс мог легко получить указатель на функцию. Это можно сделать при запуске приложения, чтобы обработчики сообщений регистрировались с самого начала.

Обратите внимание, что для работы этой системы все обработчики сообщений должны иметь одну и ту же сигнатуру функции (т. Е. Тип и параметры возврата).

Или, если на то пошло, как messageData будет создан в первую очередь из моей структуры.

Как вы получаете пакетные данные? Вы создаете это вручную, читая это из сокета? В любом случае, вам нужно где-то закодировать его как строку байтов. Элемент int* (messageData) является просто указателем на начало закодированных данных. Член messageLength - это длина этих закодированных данных.

В обратном вызове вашего обработчика сообщений вы, вероятно, не хотите продолжать манипулировать данными как необработанными двоичными / шестнадцатеричными данными, но вместо этого интерпретируйте информацию осмысленным образом в соответствии с тип сообщения.

Приведение его к структуре, по сути, отображает необработанную двоичную информацию в значимый набор атрибутов, соответствующих протоколу обрабатываемого сообщения.

3 голосов
/ 25 ноября 2009

Ключ в том, что вы должны понимать, что все в компьютере - это просто массив байтов (или слов, или двойных слов).

ZEN MASTER MUSTARD сидит за столом и смотрит на монитор, уставившись на сложную комбинацию, казалось бы, случайных символов. СТУДЕНТ приближается.

Студент: магистр? Могу ли я прервать?

Дзен Мастер Горчица: Вы ответили на свой вопрос, сын мой.

S: Что?

ЗММ: Задавая вопрос о том, чтобы прервать меня, вы прервали меня.

S: Ой, прости. У меня вопрос о перемещении конструкций разного размера с места на место.

ЗММ: Если это правда, тогда вам следует проконсультироваться с мастером, который преуспевает в таких вещах. Я предлагаю вам посетить Мастера DotPuft, который имеет большие знания в перемещении больших металлических конструкций, таких как следящие радары, с места на место. Master DotPuft также может заставить малейшие элементы тензодатчика с легким весом двигаться с силой дыхания голубя. Поверните направо, затем поверните налево, когда доберетесь до двери хай-бей. Там обитает Мастер DotPuft.

С: Нет, я имею в виду перемещение больших структур разного размера с места на место в памяти компьютера.

З.М.М.: Я могу помочь вам в этом начинании, если хотите. Опишите свою проблему.

S: В частности, у меня есть функция c, для которой я хочу принимать несколько различных типов структур (они будут представлять различные типы пакетов). Так что мои структурные пакеты будут переданы моей функции как void *. Но, не зная типа, я не могу их разыграть или вообще ничего не делаю. Я знаю, что это решаемая проблема, потому что sento () из socket.h делает именно это:

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

где sendto будет называться как:

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

ZMM: Вы рассказали о своей проблеме Мастеру Zen MANTAR!

S: Да, он сказал: «Это просто указатель. Все в C это указатель». Когда я попросил его объяснить, он сказал: «Бок, бок, убирайся из моего офиса».

ЗММ: Действительно, вы говорили с мастером. Вам это не помогло?

S: Гм, нет. Затем я спросил мастера дзен Макс.

ЗММ: Мудрый он. Какой его совет был вам полезен?

S: Нет. Когда я спросил его о sendto (), он просто закрутил кулаки в воздухе. Это просто массив байтов. "

ZMM: Действительно, у Дзен Мастера Макса есть тау.

S: Да, у него есть тау, но как мне обращаться с аргументами функции типа void *?

ЗММ: Чтобы учиться, вы должны сначала отучиться. Ключ в том, что вы должны понимать, что все в компьютере - это просто массив байтов (или слов, или двойных слов). Если у вас есть указатель на начало буфера и его длину, вы можете отправить его куда угодно без необходимости знать тип данных, помещаемых в буфер.

S: ОК.

ZMM: рассмотрим строку читаемого человеком текста. «Вы планируете башню, которая пробьет облака? Сначала положите фундамент смирения». Это 82 байта в длину. Или, может быть, 164, если используется злой Unicode. Защитите себя от лжи Unicode! Я могу отправить этот текст в sendto (), указав указатель на начало буфера, который содержит строку, и длину буфера, например:

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: Обратите внимание, что число байтов символьного буфера автоматически рассчитывается компилятором. Число байтов, занимаемых любым типом переменной, относится к типу "size_t". Вероятно, он эквивалентен типу "long" или "unsinged int", но зависит от компилятора.

S: Ну, а если я хочу отправить структуру?

ZMM: Тогда давайте отправим структуру.

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: Да, это здорово при передаче данных через сокеты TCP / IP. Но как насчет плохой функции приема? Как он может определить, отправляю ли я массив символов или структуру?

ZMM: Один из способов - перечислить различные типы данных, которые могут быть отправлены, а затем отправить тип данных вместе с данными. Мастера дзен называют это «метаданными», то есть «данными о данных». Ваша принимающая функция должна проверить метаданные, чтобы определить, какой тип данных (структура, число с плавающей запятой, массив символов) отправляется, а затем использовать эту информацию для приведения данных обратно в исходный тип. Сначала рассмотрим функцию передачи:

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: Хорошо.

ZMM: Теперь только мы пройдемся вместе с функцией приема. Он должен запросить тип отправленных данных и скопировать данные в переменную, объявленную этого типа. Простите, но я забыл точное значение функции recvfrom().

   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;
    }

ЗММ: Достигли ли вы просветления? Во-первых, кто-то спрашивает у компилятора размер данных (например, количество байтов), которые должны быть переданы в sendto(). Вы отправляете тип исходных данных, которые также отправляются вместе. Затем получатель запрашивает тип исходных данных и использует его для вызова правильного преобразования типа «указатель на пустоту» (универсальный указатель) к типу исходных данных (int, char [], структура, и др.)

S: Хорошо, я попробую.

ЗММ: иди с миром.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...