UDPServer->DefaultPort = 2050;
UDPClient->Port = 2050;
UDPClient->Host = "127.0.0.1";
UDPServer->Active = true;
BytesToRaw (Indy 10)
Реализовывая передачу изображения по протоколу UDP столкнулся с проблемой перевода TBytes в TMemoryStream.
Для теста создал новый проект. На форму поместил два TImage, один TButton, TIdUDPClient и TIdUDPServer.
В Image1 поместил PNG изображение (153 байта)
Настраиваю компоненты:
Код:
В событии OnClick для Button1 выполняю передачу объекта серверу:
Код:
TMemoryStream *msS = new TMemoryStream();
TPngImage *png = new TPngImage();
png->Assign(Image1->Picture);
png->SaveToStream(msS);
msS->Position = soFromBeginning;
TByteDynArray DynArray = RawToBytes(msS,msS->Size);
delete png, msS;
png = NULL, msS = NULL;
UDPClient->SendBuffer(DynArray);
TPngImage *png = new TPngImage();
png->Assign(Image1->Picture);
png->SaveToStream(msS);
msS->Position = soFromBeginning;
TByteDynArray DynArray = RawToBytes(msS,msS->Size);
delete png, msS;
png = NULL, msS = NULL;
UDPClient->SendBuffer(DynArray);
Передача проходит на "ура". Теперь обрабатываю событие OnUDPRead:
Код:
void __fastcall TForm1::UDPServerUDPRead(TIdUDPListenerThread *AThread, TBytes AData,
TIdSocketHandle *ABinding)
{
TMemoryStream *msR = new TMemoryStream();
TPngImage *png = new TPngImage();
BytesToRaw(AData,msR,AData.Length); // проблемое место
msR->Position = soFromBeginning;
png->LoadFromStream(msR);
Image2->Picture->Graphic = png;
delete png, msR;
png = NULL, msR = NULL;
}
TIdSocketHandle *ABinding)
{
TMemoryStream *msR = new TMemoryStream();
TPngImage *png = new TPngImage();
BytesToRaw(AData,msR,AData.Length); // проблемое место
msR->Position = soFromBeginning;
png->LoadFromStream(msR);
Image2->Picture->Graphic = png;
delete png, msR;
png = NULL, msR = NULL;
}
Картинка появляется в Image2, но при закрытии формы возникает Access Violation.
Функция BytesToRaw в третьем параметре требует int Size. Я передаю ей размер полученных данных (AData.Length) получаю всё тот же AV.
Пробую передать размер TBytes (пробовал также для TByteDynArray):
Код:
BytesToRaw(AData,msR,sizeof(AData));
В этом случае поток msR имеет нулевую длину и ссылается в NULL
Пробовал работать с этой функцией без сетевой передачи (далее изменённый код OnClick для Button1):
Код:
TMemoryStream *msS = new TMemoryStream();
TPngImage *png = new TPngImage();
png->Assign(Image1->Picture);
png->SaveToStream(msS);
msS->Position = soFromBeginning;
TByteDynArray DynArray = RawToBytes(msS,msS->Size);
BytesToRaw(DynArray,msS,sizeof(DynArray));
msS->Position = soFromBeginning;
png->LoadFromStream(msS);
mage2->Picture->Graphic = png;
delete png, msS;
png = NULL, msS = NULL;
TPngImage *png = new TPngImage();
png->Assign(Image1->Picture);
png->SaveToStream(msS);
msS->Position = soFromBeginning;
TByteDynArray DynArray = RawToBytes(msS,msS->Size);
BytesToRaw(DynArray,msS,sizeof(DynArray));
msS->Position = soFromBeginning;
png->LoadFromStream(msS);
mage2->Picture->Graphic = png;
delete png, msS;
png = NULL, msS = NULL;
Тогда ошибок не возникает.
Подскажите пожалуйста, что я делаю неправильно?
Код:
delete png, msR;
png = NULL, msR = NULL;
png = NULL, msR = NULL;
вы по сути присваиваете указатель на объект, а потом его (объект и указатель на него) удаляете.
Объявил объект png глобально. Проблема не исчезла.
Удаление объекта возможно, так как происходит копирование в Graphic и последующее отображение на канве.
Для примера можно выполнить такой код:
Код:
TFileStream *fs = new TFileStream("1.png",fmOpenRead);
TPngImage *png = new TPngImage();
png->LoadFromStream(fs);
Image1->Picture->Graphic = png;
png->LoadFromFile("2.png");
delete png;
png = NULL;
fs->Position = soFromBeginning;
Image1->Picture->Graphic->SaveToStream(fs);
Form1->Update();
Sleep(500);
Image1->Picture->Graphic->LoadFromStream(fs);
delete fs;
fs = NULL;
TPngImage *png = new TPngImage();
png->LoadFromStream(fs);
Image1->Picture->Graphic = png;
png->LoadFromFile("2.png");
delete png;
png = NULL;
fs->Position = soFromBeginning;
Image1->Picture->Graphic->SaveToStream(fs);
Form1->Update();
Sleep(500);
Image1->Picture->Graphic->LoadFromStream(fs);
delete fs;
fs = NULL;
Объект был перезаписан, удалён и ссылка указывала в пустоту. Однако, поток fs по прежнему содержит 1.png
Что делает функция BytesToRaw() ?
Цитата: Phodopus
Что делает функция BytesToRaw() ?
Копирует бинарные значения из массива байт в нетипизированный буффер.
Для преобразования TBytes в TStream необходимо использовать функцию WriteTIdBytesToStream (обратная ей функция - ReadTIdBytesFromStream).
Если кому-то пригодится (а точно пригодится), в верхнем исходнике необходимо заменить две строчки.
В событии OnClick меняем девятую строчку на:
Код:
TByteDynArray DynArray;
ReadTIdBytesFromStream(msS,DynArray,msS->Size);
ReadTIdBytesFromStream(msS,DynArray,msS->Size);
А в событии OnUDPRead меняем седьмую строчку на:
Код:
WriteTIdBytesToStream(msR,AData,AData.Length,0);
:) Теперь необходимо только реализовать разбивку потока на максимально возможные пакеты (UDPClient->BufferSize)
Цитата: Phodopus
конечно, ведь где тут нетипизированный буфер?
msR->Memory я тоже пробовал.
Цитата: MikeSoft
msR->Memory я тоже пробовал.
Так и там он из ниоткуда не появится. См. метод SetSize(), и, возможно, нужно использовать *(msR->Memory) - я объявления BytesToRaw() не видел.
Цитата: Phodopus
Так и там он из ниоткуда не появится. См. метод SetSize(), и, возможно, нужно использовать *(msR->Memory) - я объявления BytesToRaw() не видел.
BytesToRaw обьявлена в IdGlobal.hpp
SetSize() - просто установит размер памяти потока, не возвращая указатель на выделенную память.
Я уже пробовал делать следующее:
Код:
msR->SetSize(AData.Length);
BytesToRaw(AData, msR->Memory,AData.Length);
BytesToRaw(AData, msR->Memory,AData.Length);
Так же пробовал:
Код:
msR->SetSize(AData.Length);
msR->Write(&AData,msR->Size);
msR->Write(&AData,msR->Size);
*(msR->Memory) - с этим потом вообще ничего не сделать.
А вот ReadTIdBytesFromStream и WriteTIdBytesToStream проблему решили полностью.
Цитата: Phodopus
и это по всем признакам должно было сработать
Вот и я так думал... Но увы, результат был печальным.
Меня вообще удивила проблема использования:
Код:
BytesToRaw(AData,msR,AData.Length);
Данные копируются в поток, после этого корректно отображаются на канве TImage. И только при вызове деструктора формы проявляется AV.
Я сначала грешил на то, что передаю неправильный размер (кстати, так и непонятно, почему без сетевой передачи приведеный выше код работает корректно)... Возможно, Indy всё-таки грешит.