Struct в Struct, способный изменять внутренний тип Struct - PullRequest
6 голосов
/ 20 декабря 2010

Это мало что объясняет, вот что у меня есть:

public struct PACKET_HEADER
    {
        public string computerIp;
        public string computerName;
        public string computerCustomName;
    };

    public struct PACKET
    {
        public PACKET_HEADER pktHdr;
        public PACKET_DATA pktData;
    };


    public struct PACKET_DATA
    {
        public Command command;
        public string data;  
    };

    public struct DATA_MESSAGE
    {
        public string message;
    };

    public struct DATA_FILE
    {
        public string fileName;
        public long fileSize;       
    };

По сути, я хочу, чтобы поле данных в PACKET_DATA могло быть либо DATA_FILE, либо DATA_MESSAGE. Я знаю, что тип должен быть изменен, но я не знаю, что делать, является ли дженерики опцией?

конечный результат должен быть таким, чтобы я мог сделать либо:

pktData.data.fileName или же pktData.data.message

EDIT

я мог бы сделать:

public struct PACKET_DATA
{
    public Command command;
    public string data;
    public DATA_MESSAGE data_message;
    public DATA_FILE data_file;
};

и просто установить data_message или файл в null, когда они мне не нужны? как это повлияет на сериализацию / байтовый массив и отправляемые данные. Если бы я использовал классы, у меня не было бы той же проблемы

РЕДАКТИРОВАТЬ 2

public struct PACKET_MESSAGE
{
    public PACKET_HEADER pktHdr;
    public Command command;
    public DATA_MESSAGE pktData;
};

public struct PACKET_FILE
{
    public PACKET_HEADER pktHdr;
    public Command command;
    public DATA_FILE pktData;
};

Редактировать 3

У меня есть стерилизатор и де-стерилизатор, который работает с моим исходным примером, если с этим не происходит икота, то фактическая сериализация выполнена.

РЕДАКТИРОВАТЬ 4

кажется, что все работает, кроме одного, что получает мой сериализатор: «Попытка чтения или записи защищенной памяти. Это часто указывает на то, что другая память повреждена». Гунна посмотри на это, когда отправлю мое рабочее решение:)

РЕДАКТИРОВАТЬ 5

    public static byte[] Serialize(object anything)
    {
        int rawsize = Marshal.SizeOf(anything);
        byte[] rawdatas = new byte[rawsize];
        GCHandle handle = GCHandle.Alloc(rawdatas, GCHandleType.Pinned);
        IntPtr buffer = handle.AddrOfPinnedObject();
        Marshal.StructureToPtr(anything, buffer, false);
        handle.Free();
        return rawdatas;
    }

    public static object Deserialize(byte[] rawdatas, Type anytype)
    {
        int rawsize = Marshal.SizeOf(anytype);
        if (rawsize > rawdatas.Length)
            return null;
        GCHandle handle = GCHandle.Alloc(rawdatas, GCHandleType.Pinned);
        IntPtr buffer = handle.AddrOfPinnedObject();
        object retobj = Marshal.PtrToStructure(buffer, anytype);
        handle.Free();
        return retobj;
    } 

FINAL

Структуры:

public struct PACKET_HEADER
{
    public string computerIp;
    public string computerName;
    public string computerCustomName;
};

public struct PACKET
{
    public PACKET_HEADER pktHdr;
    public PACKET_DATA pktData;
};

public struct PACKET_DATA
{
    public Command command;
    public IDATA data;
    public T GetData<T>() where T : IDATA
    {
        return (T)(data);
    }
}

public interface IDATA { }

public struct DATA_MESSAGE : IDATA
{
    public string message;
}

public struct DATA_FILE : IDATA
{
    public string fileName;
    public long fileSize;
}

Как создать новый пакет (возможно, можно объединить вместе tbh):

    public static PACKET CreatePacket(Command command)
    {
        PACKET packet;
        packet.pktHdr.computerIp = Settings.ComputerIP;
        packet.pktHdr.computerName = Settings.ComputerName;
        packet.pktHdr.computerCustomName = Settings.ComputerCustomName;

        packet.pktData.command = command;
        packet.pktData.data = null;

        return packet;
    }

    public static PACKET CreatePacket(Command command, DATA_MESSAGE data_message)
    {
        PACKET packet;
        packet.pktHdr.computerIp = Settings.ComputerIP;
        packet.pktHdr.computerName = Settings.ComputerName;
        packet.pktHdr.computerCustomName = Settings.ComputerCustomName;

        packet.pktData.command = command;
        packet.pktData.data = data_message;

        return packet;
    }

    public static PACKET CreatePacket(Command command, DATA_FILE data_file)
    {
        PACKET packet;
        packet.pktHdr.computerIp = Settings.ComputerIP;
        packet.pktHdr.computerName = Settings.ComputerName;
        packet.pktHdr.computerCustomName = Settings.ComputerCustomName;

        packet.pktData.command = command;
        packet.pktData.data = data_file;

        return packet;
    }

(де) сериализация выше.

Простой пример:

PACKET packet = Packet.CreatePacket(command, data_file);
byte[] byData = Packet.Serialize(packet);

другой конец:

PACKET returnPacket = (PACKET)Packet.Deserialize(socketData.dataBuffer, typeof(PACKET));
                        // Get file
string fileName = returnPacket.pktData.GetData<DATA_FILE>().fileName;
long fileSize = returnPacket.pktData.GetData<DATA_FILE>().fileSize;

Кажется, все работает хорошо и модно:)

Ответы [ 4 ]

3 голосов
/ 20 декабря 2010

На этот вопрос нужен четкий ответ, поэтому я постараюсь подвести итог:

Если вы хотите взять структуру данных C # и преобразовать ее в байтовый массив, вы можете сделать это со структурами и маршалингом, или с классами (или структурами, но зачем вам) и инфраструктурой сериализации (например, BinaryFormatter) ) или пользовательская логика сериализации (например, с BinaryWriter). Мы могли бы обсудить, что лучше, но давайте пока предположим, что мы идем со структурами, и мы используем Marshaling. Хотя я скажу, что структуры очень ограничены и должны использоваться в основном по мере необходимости для взаимодействия с функциями Win32 API.

Итак, проблема в том, что у нас есть контейнерная структура, которая может содержать один из двух типов дочерних структур. Если вы собираетесь маршалировать структуру, такие вещи, как обобщения и / или использование общего интерфейса для дочерних типов структур, не сработают. По сути, у вас есть единственный вариант - иметь в контейнере обе структуры и флаг bool, указывающий, какая из структур будет использоваться. Недостатком является увеличение размера ваших пакетов, поскольку вы также отправляете неиспользуемую дочернюю структуру.

В данном случае результат выглядит следующим образом:

public struct PACKET_DATA
{
    public Command command;
    public string data;
    public bool is_message_packet;
    public DATA_MESSAGE data_message;
    public DATA_FILE data_file;
};

Тем не менее, в вашем случае использование структур и Marshalling действительно будет работать только внутри вашего собственного процесса, потому что ваши структуры содержат строки. Когда структура содержит указатели на строки нефиксированной длины, эти строки размещаются в другом месте и не будут являться частью байтового массива, который вы копируете, будут только указатели на них. Вам также необходимо в какой-то момент вызвать Marshal.DestroyStructure с помощью IntPtr, который вы передали StructureToPtr, чтобы очистить эти строковые ресурсы.

Итак, мораль этой истории: можете ли вы создавать структуры, которые делают то, что вы изначально просили: да. Если вы используете их, как вы: нет. Поскольку у вас есть структура данных переменного размера, которую вы пытаетесь отправить по сети (я полагаю, так как структура называется PACKET), структуры не будут работать, вам действительно нужно использовать какую-то платформу сериализации или собственную логику сериализации.

1 голос
/ 20 декабря 2010
    public struct PACKET_DATA
    {
        public IData data;
        public T GetData<T>() where T : IDATA
        {
           return (T)data;
        }
    }

    public interface IDATA { }

    public struct DATA_MESSAGE : IDATA
    {
        public string message;
    }

    public struct DATA_FILE : IDATA
    {
        public string fileName;
        public long fileSize;
    }

PACKET_DATA packetData = new PACKET_DATA();
packetData.data = new DATA_MESSAGE();
var message = packetData.GetData<DATA_MESSAGE>().message;
0 голосов
/ 20 декабря 2010

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

public interface IDataType
{
}

public struct PACKET_DATA
{
    public Command command;
    public IDataType data;  
};

public struct DATA_MESSAGE : IDataType
{
    public string message;
};

public struct DATA_FILE : IDataType
{
    public string fileName;
    public long fileSize;       
};
0 голосов
/ 20 декабря 2010

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

Какова цель использования здесь структур, а не классов? Это для взаимодействия? (В этом случае сценарий взаимодействия будет диктовать правильное решение). Или это для того, чтобы избежать выделения бокса / кучи?

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