[MFC/C++]Отправка битов CBitmap через сокет и их повторное построение на стороне получателя

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

Во-первых, мне удалось отобразить список изображений из папки в элемент управления Listbox. После этого я также обработал событие щелчка в каждой строке списка, чтобы загрузить и показать изображение в Picture Control (тип Bitmap) с правой стороны. Вы можете увидеть изображение ниже для простоты понимания: Пожалуйста, нажмите здесь, чтобы увидеть изображение моего диалогового окна MFC

Вот код. Обратите внимание, что m_ListCtrl и static_picture — это переменные списка и элемента управления изображением:

void CMyClientDlg::OnLbnSelchangeList1(){

CString imagePath;
m_ListCtrl.GetText(m_ListCtrl.GetCurSel(),imagePath);

CImage picture;
picture.Load(imagePath);

if (!picture.IsNull())
{
    float screenWidth = 200, screenHeight = 200;
    float imageWidth = picture.GetWidth();
    float imageHeight = picture.GetHeight();

    //scaling:
    float pictureRatio = imageWidth/ imageHeight;
    float newImageWidth;
    float newImageHeight;
    int aligmentX = 0;
    int aligmentY = 0;
    if (pictureRatio <= 1)
    {
        newImageWidth = imageWidth*(screenHeight/imageHeight);
        newImageHeight = screenHeight;
        aligmentX = (screenWidth-newImageWidth)/2;
    }
    else
    {
        newImageWidth = screenWidth;
        newImageHeight = imageHeight*(screenWidth/imageWidth);
        aligmentY = (screenHeight - newImageHeight)/2;
    }
    //end scaling.
    CDC *screenDC = GetDC();
    CDC mDC;
    mDC.CreateCompatibleDC(screenDC);

    CBitmap bitMap;
    bitMap.CreateCompatibleBitmap(screenDC, screenWidth, screenHeight);
    CBitmap *pob = mDC.SelectObject(&bitMap);
    mDC.SetStretchBltMode(HALFTONE);
    picture.StretchBlt(mDC.m_hDC, aligmentX, aligmentY, newImageWidth, newImageHeight, 0, 0, imageWidth, imageHeight, SRCCOPY);
    mDC.SelectObject(pob);

    /*.......code to convert bitmap to BYTE* ........*/
    /*.......code to send BYTE* over socket........*/       

    //display the bit map
    static_picture.SetBitmap((HBITMAP)bitMap.Detach());

    //clean up
    ReleaseDC(screenDC);
}   

}

Итак, теперь я хотел бы сделать еще один шаг и попытался работать с сокетом... и да, я успешно отправил и получил простой char* или CString через сокет.
Что я хочу сделать, так это: вместо отображения изображения в этом диалоговом окне оно показывает изображение в другом диалоговом окне (сервере).
Как-то я узнал, что есть 2 звуковые функции: SetBitmapBits() и GetBitmapBits() (честно говоря, я только что прочитал это в каком-то источнике и понятия не имел, подходят ли они для моей цели здесь).

Итак, я добавил этот фрагмент кода, чтобы превратить приведенное выше растровое изображение в массив BYTE bmpBuffer:

BITMAP bmpProperties;
bitMap.GetBitmap(&bmpProperties);
int bmpDemension = bmpProperties.bmWidthBytes*bmpProperties.bmHeight;
BYTE* bmpBuffer=(BYTE*)GlobalAlloc(GPTR, bmpDemension);
bitMap.GetBitmapBits(bmpDemension,bmpBuffer);

Затем отправьте этот массив через сокет:

UpdateData(TRUE);
char *socketBuffer = reinterpret_cast<char*>(bmpBuffer);
send(m_ClientSocket, socketBuffer, sizeof(socketBuffer), 0);
//clean up after send
GlobalFree((HGLOBAL)bmpBuffer);

В другом диалоге. Примечание. Я жестко запрограммировал размер растрового изображения на 160000, просто чтобы упростить проблему:

void CMyServer2Dlg::OnReceive(){    
char *socketBuffer = new char [1025];
int iLen; 
iLen = recv(m_sConnected, socketBuffer, 1025, NULL);
if(iLen==SOCKET_ERROR)
{
    AfxMessageBox("Could not Receive");
}
else
{
    BYTE* bmpBuffer = reinterpret_cast<BYTE*>(socketBuffer);

    //re-construct the bitmap
    CBitmap clone;
    CDC *screenDC = GetDC();
    CDC mDC;
    mDC.CreateCompatibleDC(screenDC);

    clone.CreateCompatibleBitmap(screenDC, 200, 200);
    clone.SetBitmapBits(160000,bmpBuffer);

    //Picture control(type bitmap) has variable "static_picture"
    static_picture.SetBitmap((HBITMAP)clone.Detach());

    UpdateData(FALSE);
    ReleaseDC(screenDC);
    GlobalFree((HGLOBAL)bmpBuffer);
}
delete socketBuffer;

И это просто не работает... Скажите, пожалуйста, где я накосячил? И извините за длинный пост.....


person Trung Nguyen    schedule 04.05.2016    source источник
comment
Получите +1 за поиск решений (да, GetBitmapBits и SetBitmapBits — правильные функции, я думаю). Но что вы подразумеваете под не работает?   -  person user253751    schedule 04.05.2016
comment
Спасибо за ответ. На стороне получателя отображается только черный цвет 200x200, а не изображение, которое я нажал на стороне отправителя. Я также попытался перестроить битовую карту на стороне отправителя для тестирования, и она работает нормально.   -  person Trung Nguyen    schedule 04.05.2016
comment
Поправьте меня, если ошибаюсь. Ваш приемный буфер равен 1025, а размер растрового изображения — 160000. Достаточно ли велик буфер?   -  person Ethan F.    schedule 04.05.2016
comment
Кроме того, даже если вы попросите получить 1025 байтов сразу, вы можете не получить их все — вам может потребоваться вызвать recv несколько раз, чтобы получить все байты.   -  person user253751    schedule 04.05.2016
comment
Есть ли причина, по которой растровое изображение не отправляется/не принимается в виде растрового файла?   -  person Barmak Shemirani    schedule 04.05.2016
comment
@ИтанФ. спасибо за ваш ответ, это тоже мое первое подозрение, но после двойной проверки sizeof(bmpBuffer) на стороне отправителя он показывает только 4 байта. А поскольку тип данных BYTE — это не что иное, как беззнаковый символ, я думаю, что буфер приема 1025 достаточно велик?   -  person Trung Nguyen    schedule 04.05.2016
comment
@TrungNguyen Я думаю, что sizeof(bmpBuffer) не возвращает размер пространства, а размер указателя, который составляет 4 байта. Если картинка 200х200, то требуется 40000/8=5000 байт. Конечно, ваш буфер недостаточно велик.   -  person Ethan F.    schedule 04.05.2016
comment
@ИтанФ. Это правда! но, как сказал иммибис, я не могу получить их все за один звонок, как я могу это сделать?   -  person Trung Nguyen    schedule 04.05.2016
comment
@BarmakShemirani. Да, отправить целый файл кажется намного проще, но ради обучения я хочу больше поработать над построением растрового изображения. И я думаю, что в некоторых реальных обстоятельствах сохранение файла на стороне сервера считается плохой практикой?   -  person Trung Nguyen    schedule 04.05.2016
comment
@TrungNguyen, посмотри мой ответ. 2 ошибки, которые вы сделали. Во-первых, вы отправляете только 4 байта, во-вторых, вы получаете только 1025 байт.   -  person Ethan F.    schedule 04.05.2016


Ответы (1)


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

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

Шаг 1: Упакуйте размер растрового изображения. Я предполагаю, что здесь размер меньше 64 КБ, поэтому используется int. Если размер может быть больше 64 КБ, вы можете использовать INT64.

int bmpDemension = bmpProperties.bmWidthBytes*bmpProperties.bmHeight;
int bufferSize = bmpDemension + sizeof(int);
BYTE* bmpBuffer=(BYTE*)GlobalAlloc(GPTR, bufferSize );
bitMap.GetBitmapBits(bmpDemension,bmpBuffer + sizeof(int));
memcpy(bmpBuffer, &bmpDemension, sizeof(int)); // put the size into the head of package.

Шаг 2: Отправьте его. Имейте в виду, я использую здесь bufferSize, потому что sizeof(bmpBuffer) возвращает размер указателя, который равен 4, а не размер пробела.

UpdateData(TRUE);
char *socketBuffer = reinterpret_cast<char*>(bmpBuffer);
send(m_ClientSocket, socketBuffer, bufferSize , 0);
 //clean up after send
GlobalFree((HGLOBAL)bmpBuffer);

На стороне получателя: сначала вы считываете размер растрового изображения, а затем получаете в соответствии с размером данных.

void CMyServer2Dlg::OnReceive(){    
char socketBuffer[1025];
int iLen; 
iLen = recv(m_sConnected, socketBuffer, sizeof(int), NULL); //read the bigmap size
if(iLen==SOCKET_ERROR)
{
AfxMessageBox("Could not Receive");
}
else
{
int dimension = *((int *) socketBuffer);
char * bitmapBuffer = new char[dimension];
int readSize = dimension;
char * pBuffer = bitmapBuffer;
while (readSize > 0)
{
    int sizeToRead = readSize > sizeof(socketBuffer) ? sizeof(socketBuffer) : readSize;
    iLen = recv(m_sConnected, socketBuffer, sizeToRead , NULL);
    memcpy(pBuffer, socketBuffer, iLen);
    pBuffer += iLen;
    readSize -= iLen;
}
// when the loop done, you shall have all data in bitmapBuffer.
....    
// I leave the remaining code to you.

Опять же, этот код предназначен только для демонстрации идеи.

person Ethan F.    schedule 04.05.2016
comment
Спасибо за помощь! Я попробую ваше предложение и расскажу вам о результате. - person Trung Nguyen; 04.05.2016
comment
Оно работает! ты обалденный. Однако нам нужно очищать socketBuffer после каждого рисования изображения, потому что клиент может щелкнуть другое изображение и вызвать сбой. В любом случае, большое спасибо за вашу помощь, я не могу поверить, что пропустил что-то настолько простое, как размер указателя в начале... Полезно учиться. - person Trung Nguyen; 04.05.2016
comment
Да, вы правы насчет уборки. Легко забыть. Пожалуйста, кстати :D - person Ethan F.; 04.05.2016
comment
Кстати, есть ли способ избежать зацикливания? так как это может сильно повредить производительности. Если клиент щелкает так быстро, серверная часть может зависнуть. - person Trung Nguyen; 04.05.2016
comment
int в Windows/Visual Studio составляет 4 байта. Он может представлять значения до 2^31-1. 64k-1 будет наибольшим представимым значением для беззнакового 16-битного типа данных (например, unsigned short). А указатель может быть 4 или 8 байт. Кроме того, ваш расчет размера неверен. Он полностью игнорирует значение бит на пиксель, а также заполнение строки сканирования. Это решение требует большой удачи для работы. - person IInspectable; 04.05.2016
comment
@IInspectable Спасибо за комментарий. Да, я упомянул об ограничении int. Размер указателя здесь не имеет значения, просто небрежная ошибка, допущенная ОП. Что касается размера изображения, я использовал BW (1 бит на пиксель) в качестве образца в комментарии, цветное изображение будет намного больше. Но в программе OP просто использует свойства CBitmap для расчета всего размера, что должно быть точным. - person Ethan F.; 04.05.2016
comment
@TrungNguyen У вас есть более подробная информация о зависании? Вы имеете в виду, что клиентская сторона продолжит отправку данных? В этом случае на стороне сервера была произведена небольшая модификация. В части чтения убедитесь, что он считывает точные байты длины пакета. Я изменю код выше. - person Ethan F.; 04.05.2016
comment
@ИтанФ. Ваше новое издание немного странное. Я попробовал это, и в результате растровое изображение отображается ВВЕРХ СТОРОНОЙ ВНИЗ, а затем VS выдает ошибку отладки, говорящую об обнаружении HEAP CORRUPTION, и показывает, что проблемная строка - это recv() внутри цикла. - person Trung Nguyen; 05.05.2016
comment
@ИтанФ. Что касается первого кода, это отладочное сообщение, в котором говорится о местоположении чтения нарушения доступа. И я понимаю, что iLen в цикле иногда равен -1 !! поэтому я делаю перенос if снаружи, чтобы разорвать цикл, если iLen возвращает ‹0. Картинка при этом не завершится, но, по крайней мере, не зависает весь диалог. Для отправки со стороны клиента я нахожу обходной путь для Sleep(1000) между каждым кликом, это поможет предотвратить переполнение данных со стороны клиента, но все же не решение... - person Trung Nguyen; 05.05.2016
comment
@TrungNguyen Да, вам нужно добавить кое-что для обработки ошибок. Мой код просто показывает идею. Возврат -1 означает ошибку сокета? Проверьте MSDN для более подробной информации. Сон — не лучшая идея. Я не понимаю, даже если клиент продолжает отправлять данные, это не должно вызывать сбоев, если сервер правильно обрабатывает данные. Поскольку я не знаю всей структуры кода, я не могу дать больше предложений. - person Ethan F.; 05.05.2016
comment
@TrungNguyen И да, во втором коде есть ошибка. Прежде чем этот readSize увеличивается, я забыл изменить код после того, как изменил его на уменьшение. Я сделал модификацию. - person Ethan F.; 05.05.2016
comment
@ИтанФ. Спасибо за ваш комментарий. До сих пор ваше предложение ответило на мой вопрос и указало на мою ошибку. Поэтому я не могу просить вас сделать больше. Я думаю, что с этого момента я должен решать все сам. Еще раз спасибо за помощь. Радость. - person Trung Nguyen; 06.05.2016
comment
@TrungNguyen Добро пожаловать. И у вас есть правильный путь. Ваши навыки будут значительно улучшены благодаря этой отладке. Ваше здоровье! - person Ethan F.; 06.05.2016