【问题标题】:passing primitive or struct type as function argument将原始或结构类型作为函数参数传递
【发布时间】:2010-12-18 03:55:49
【问题描述】:

我正在尝试编写一些相当通用的网络代码。我有几种数据包,每种数据包由不同的结构表示。我所有发送发生的函数如下所示:

- (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

我可以传入任何类型的数据包指针。但是,在不知道数据包类型的情况下,我无法取消引用指针。

如何编写一个函数来接受任何类型的原语/结构作为其参数?

【问题讨论】:

  • 显然 sendto() 函数能够完成此任务。我很难找到实际的源代码。 socket.h 很容易找到,但实际的功能代码在哪里可以找到呢?

标签: objective-c pointers struct argument-passing


【解决方案1】:

您要实现的是polymorphism,这是一个面向对象的概念。

因此,虽然这在 C++(或其他 OO 语言)中很容易实现,但在 C 中更具挑战性。

您可以解决的一种方法是创建一个通用的“数据包”结构,例如:

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

其中messageHandler 成员是指向可以处理消息类型的回调例程的函数指针,messageLengthmessageData 成员是不言自明的。

这个想法是,您将packetStruct 传递给的方法将使用Tell, Don't Ask 原理调用messageHandler 指向的特定消息处理程序指针,传入messageLengthmessageData 而不解释它.

调度函数(由messageHandler 指向)将是特定于消息的,并且能够将messageData 转换为适当的有意义的类型,然后可以从中提取有意义的字段并进行处理等。

当然,这一切在 C++ 中使用继承、虚拟方法等更容易、更优雅。


编辑:

回应评论:

我有点不清楚如何“能够投射 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 成员是此编码数据的长度。

在您的消息处理程序回调中,您不希望可能不希望继续将数据作为原始二进制/十六进制数据进行操作,而是解释信息以有意义的方式根据消息类型。

将其转换为结构本质上是将原始二进制信息映射到一组有意义的属性,这些属性与您正在处理的消息的协议相匹配。

【讨论】:

  • 我有点不清楚如何“能够将 messageData 转换为适当的有意义的类型,然后可以从中提取有意义的字段并进行处理等”。将完成。或者就此而言,如何从我的结构中首先创建 messageData。这是 sendto() 使用的技术吗?这似乎不太可能,因为它根本不知道数据类型。基于 sendto() 的原型,只知道数据的地址,它的长度就足够了。
  • PillBox,感谢您的出色回应。我并没有真正考虑过以这种方式使用函数指针。我通过电子邮件在下面得到的解决方案更符合我的问题的需要。
  • @SooDesuNe - 感谢您的跟进,很高兴您能解决您的问题。
【解决方案2】:

关键是您必须意识到计算机中的所有内容都只是一个字节数组(或字或双字)。

ZEN MASTER MUSTARD 坐在他的办公桌前,盯着他的显示器盯着看似随机字符的复杂图案。一名学生走近。

学生:师父?我可以打断一下吗?

Zen Master Mustard:你已经回答了你自己的询问,我的孩子。

S:什么?

ZMM:问你打断我的问题,你就打断了我。

S:哦,对不起。我有一个关于从一个地方到另一个地方移动不同大小的结构的问题。

ZMM:如果是这样,那你应该请教擅长这些事情的大师。我建议您拜访一下 DotPuft 大师,他在将大型金属结构(例如跟踪雷达)从一个地方移动到另一个地方方面拥有丰富的知识。 Master DotPuft 还可以使羽毛重量应变计​​的最轻微元素随着鸽子呼吸的力量而移动。右转,然后在您到达 hi-bay 的门时左转。 DotPuft 大师住在那里。

S:不,我的意思是在计算机内存中移动不同大小的大型结构。

ZMM:如果你愿意,我可以帮助你。描述你的问题。

S:具体来说,我有一个 c 函数,我想接受几种不同类型的结构(它们将代表不同类型的数据包)。所以我的结构数据包将作为 void* 传递给我的函数。但是在不知道类型的情况下,我无法施放它们,或者真的做很多事情。我知道这是一个可以解决的问题,因为 socket.h 中的 sento() 正是这样做的:

    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:你有没有向 MANTA 禅师描述你的问题! ?

S:是的,他说,“它只是一个指针。C 中的一切都是指针。”当我请他解释时,他说:“滚,滚,滚出我的办公室。”

ZMM:真的,你已经和主人谈过了。这对你没有帮助吗?

S:嗯,呃,不。然后我问了Zen Master Max。

ZMM:他很聪明。他的建议对你有什么用处?

S:没有。当我问他关于 sendto() 的问题时,他只是在空中挥舞着拳头。它只是一个字节数组。”

ZMM:的确,Zen Master Max 有 tau。

S:是的,他有 tau,但我该如何处理 void* 类型的函数参数?

ZMM:要学习,你必须先忘却。关键是您必须意识到计算机中的所有内容都只是一个字节数组(或字或双字)。一旦有了指向缓冲区开头的指针和缓冲区的长度,就可以将它发送到任何地方,而无需知道缓冲区中放置的数据类型。

S:好的。

ZMM:考虑一串可读文本。 “你打算建一座穿云的塔?先打下谦卑的根基。”它有 82 个字节长。或者,如果使用邪恶的 Unicode,也许是 164。防范 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;
    }

ZMM:你开悟了吗?首先,向编译器询问要提交给sendto() 的数据大小(也就是字节数)。您发送的原始数据的类型也随之发送。然后接收者查询原始数据的类型,并使用它来调用从“指向 void 的指针”(通用指针)到原始数据类型(int、char[]、结构、等等)

S:嗯,我试试看。

ZMM:放心去吧。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-11-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-03-22
    • 1970-01-01
    • 2021-02-28
    • 1970-01-01
    相关资源
    最近更新 更多