Search code examples
c#c++arraysscreenshotbitblt

BitBlt convert to byte array and parse from c++ to c#


I'm trying to create a Screen Capture DLL in C++ and send the resulting Byte Arrays to C#.

I'm able to get the size returned to C# but the byte array is always null.

Here's the C++ code (made up of bits i found on the internet)

    __declspec(dllexport) int ScreenCap(BYTE* *data, DWORD *size)
{
    try
    {
    //BITMAP bmpScreen;
    HWND DesktopHwnd = GetDesktopWindow();
    RECT DesktopParams;
    HDC DevC = GetDC(DesktopHwnd);
    GetWindowRect(DesktopHwnd,&DesktopParams);
    DWORD Width = DesktopParams.right - DesktopParams.left;
    DWORD Height = DesktopParams.bottom - DesktopParams.top;

    DWORD FileSize = sizeof(BITMAPFILEHEADER)+sizeof(BITMAPINFOHEADER)+(sizeof(RGBTRIPLE)+1*(Width*Height*4));
    *size = FileSize;
    char *BmpFileData = (char*)GlobalAlloc(0x0040,FileSize);

    PBITMAPFILEHEADER BFileHeader = (PBITMAPFILEHEADER)BmpFileData;
    PBITMAPINFOHEADER  BInfoHeader = (PBITMAPINFOHEADER)&BmpFileData[sizeof(BITMAPFILEHEADER)];

    BFileHeader->bfType = 0x4D42; // BM
    BFileHeader->bfSize = sizeof(BITMAPFILEHEADER);
    BFileHeader->bfOffBits = sizeof(BITMAPFILEHEADER)+sizeof(BITMAPINFOHEADER);

    BInfoHeader->biSize = sizeof(BITMAPINFOHEADER);
    BInfoHeader->biPlanes = 1;
    BInfoHeader->biBitCount = 24;
    BInfoHeader->biCompression = BI_RGB;
    BInfoHeader->biHeight = Height;
    BInfoHeader->biWidth = Width;

    RGBTRIPLE *Image = (RGBTRIPLE*)&BmpFileData[sizeof(BITMAPFILEHEADER)+sizeof(BITMAPINFOHEADER)];
    RGBTRIPLE color;

    HDC CaptureDC = CreateCompatibleDC(DevC);
    HBITMAP CaptureBitmap = CreateCompatibleBitmap(DevC,Width,Height);
    SelectObject(CaptureDC,CaptureBitmap);
    BOOL bRet = BitBlt(CaptureDC,0,0,Width,Height,DevC,0,0,SRCCOPY|CAPTUREBLT);
    //GetDIBits(CaptureDC,CaptureBitmap,0,Height,Image,(LPBITMAPINFO)BInfoHeader, DIB_RGB_COLORS);
    //GetObject(CaptureBitmap,sizeof(BITMAPFILEHEADER),&bmpScreen);
    //BYTE* lpPixels = new BYTE[sizeof((LPBITMAPINFO)BInfoHeader)];
    GetDIBits(CaptureDC, CaptureBitmap, 0, Height, *data, (LPBITMAPINFO)BInfoHeader, DIB_RGB_COLORS);
    //DWORD Junk;
    //DIBSECTION dib;
    //GetObject(CaptureBitmap, sizeof(dib), (LPVOID)&dib);

    //HANDLE FH = CreateFileA(BmpName,GENERIC_WRITE,FILE_SHARE_WRITE,0,CREATE_ALWAYS,0,0);
    //WriteFile(FH,BmpFileData,FileSize,&Junk,0);
    //CloseHandle(FH);
        GlobalFree(BmpFileData); 


        return 1;
    }
    catch(char *p)
    {
        return 0;
    }
}

And here's the C# code i'm using with the "ref" keyword.

        [DllImport("consoleapplication1.dll", CallingConvention = CallingConvention.Cdecl)]
        static extern int ScreenCap(ref byte[] data, ref int size);

        public static void CapScreen()
    {
        try
        {
            int hr = 0;
            byte[] gData = null;
            int gSize = 0;
            hr = ScreenCap(ref gData, ref gSize);
            int a = 1;
        }
        catch(Exception ex)
        {
            int a = 1;
        }

I'm thinking there may be an issue with my GetDIBits but i'm not sure. I hope some of you guru's can set me on the right path. Thanks.

* EDIT *

OK Big thanks to both jdweng and filip for pointing me in the right direction. i'm now receiving the screen as an IntPtr and able to render it to my C# app. here's the code that now works (kind of i'll explain at the bottom)

__declspec(dllexport) char* ScreenCap(DWORD * size)

{ try { // Get screen dimensions int nScreenWidth = GetSystemMetrics(SM_CXSCREEN); int nScreenHeight = GetSystemMetrics(SM_CYSCREEN);

    *size = ((((24 * nScreenWidth + 31)&(~31)) / 8)*nScreenHeight);

    BITMAPINFO MyBMInfo = {0};
    MyBMInfo.bmiHeader.biSize = sizeof(MyBMInfo.bmiHeader); 

    // Create compatible DC, create a compatible bitmap and copy the screen using BitBlt()
    HDC hdcScreen = GetDC(GetDesktopWindow());
    HDC hdcCompatible  = CreateCompatibleDC(hdcScreen);
    HBITMAP hBmp = CreateCompatibleBitmap(hdcScreen, nScreenWidth, nScreenHeight);
    //HGDIOBJ hOldBmp = SelectObject(hdcCompatible, hBmp); 
    HGDIOBJ hOldBmp = (HGDIOBJ) SelectObject(hdcCompatible, hBmp);

    BOOL bOK = BitBlt(hdcCompatible,0,0,nScreenWidth, nScreenHeight, hdcScreen,0,0,SRCCOPY|CAPTUREBLT); 

    SelectObject(hdcCompatible, hOldBmp); // always select the previously selected object once done
    // Get the BITMAPINFO structure from the bitmap
    GetDIBits(hdcScreen, hBmp, 0, 0, NULL, &MyBMInfo, DIB_RGB_COLORS);

    // create the bitmap buffer
    char* lpPixels = new char[MyBMInfo.bmiHeader.biSizeImage];

    MyBMInfo.bmiHeader.biCompression = BI_RGB; 
    MyBMInfo.bmiHeader.biBitCount = 24;  

    // get the actual bitmap buffer
    GetDIBits(hdcScreen, hBmp, 0, MyBMInfo.bmiHeader.biHeight, (LPVOID)lpPixels, &MyBMInfo, DIB_RGB_COLORS);

    //Clean Up
    DeleteDC(hdcCompatible);
    ReleaseDC(GetDesktopWindow(), hdcScreen);
    DeleteDC(hdcScreen);
    DeleteObject(hBmp);
    //DeleteObject(hOldBmp);

    return lpPixels;
}
catch(char *p)
{
    return 0;
}

}

and the C#

        [DllImport("consoleapplication1.dll", CallingConvention = CallingConvention.Cdecl)]
    static extern IntPtr ScreenCap(ref int size);
                while(true)
            {
                int size = 0;
                IntPtr ptr = ScreenCap(ref size);
                byte[] result = new byte[size];
                Marshal.Copy(ptr, result, 0, size);
                Marshal.Release(ptr);
                ptr = IntPtr.Zero;
                MainWindow.Dispatcher.Invoke(new Action(
                () =>
                    {
                        System.Windows.Media.ImageSource ThumbnailImage = System.Windows.Media.Imaging.BitmapSource.Create(1920, 1080, 96, 96, System.Windows.Media.PixelFormats.Bgr24, null, result, 1920 * 3);
                        MainWindow.canvasMain.Background = new System.Windows.Media.ImageBrush(ThumbnailImage);
                        result = null;
                        GC.Collect();
                        GC.WaitForPendingFinalizers();
                    }
                ), null);
            }

Not sure if the problem i now face is due to doing it this way but i'm getting a major memory leak from the C++ dll now. If this is another issue all together, do i need to create a new thread?

** Edit **

Problem solved by creating a new function in the C++ dll to delete the returned char array. may not be elegant but it works.

i'd like to give a big thanks to Filip who's comments helped me to look in all sorts of places and see other things. Jdweng, your IntPtr was a god send. Not sure how i set this as answered by Jdweng. would love to award to you both.


Solution

  • Here is what worked for me if anyone is looking for the same. I'd like to send a big thanks to both Filip Kocica and jdweng who's suggestions gave me some ideas and helped very much. Thanks and appreciated very much.

    C++

        char* lpPixels;
    __declspec(dllexport) BOOL ScreenCapClean()
    {
        delete[] lpPixels;
        return 1;
    }
    
    __declspec(dllexport) char* ScreenCap(DWORD * size)
    {
        try
        {
            // Get screen dimensions
            int nScreenWidth = GetSystemMetrics(SM_CXSCREEN);
            int nScreenHeight = GetSystemMetrics(SM_CYSCREEN);
    
            *size = ((((24 * nScreenWidth + 31)&(~31)) / 8)*nScreenHeight);
    
            BITMAPINFO MyBMInfo = {0};
            MyBMInfo.bmiHeader.biSize = sizeof(MyBMInfo.bmiHeader);
            MyBMInfo.bmiHeader.biWidth = nScreenWidth;
            MyBMInfo.bmiHeader.biHeight = -nScreenHeight;
            MyBMInfo.bmiHeader.biPlanes = 1;
            MyBMInfo.bmiHeader.biBitCount = 24;
            MyBMInfo.bmiHeader.biCompression = BI_RGB;
            MyBMInfo.bmiHeader.biSizeImage = 0;
            MyBMInfo.bmiHeader.biXPelsPerMeter = 0;
            MyBMInfo.bmiHeader.biYPelsPerMeter = 0;
            MyBMInfo.bmiHeader.biClrUsed = 0;
            MyBMInfo.bmiHeader.biClrImportant = 0;
    
            // Create compatible DC, create a compatible bitmap and copy the screen using BitBlt()
            HDC hdcScreen = GetDC(0);
            HDC hdcCompatible  = CreateCompatibleDC(hdcScreen);
            HBITMAP hBmp = CreateCompatibleBitmap(hdcScreen, nScreenWidth, nScreenHeight);
            HGDIOBJ hOldBmp = (HGDIOBJ) SelectObject(hdcCompatible, hBmp);
    
            BOOL bOK = BitBlt(hdcCompatible,0,0,nScreenWidth, nScreenHeight, hdcScreen,0,0,SRCCOPY|CAPTUREBLT); 
    
            SelectObject(hdcCompatible, hOldBmp); // always select the previously selected object once done
            // Get the BITMAPINFO structure from the bitmap
            GetDIBits(hdcScreen, hBmp, 0, 0, NULL, &MyBMInfo, DIB_RGB_COLORS);
    
            // create the bitmap buffer
            lpPixels = new char[MyBMInfo.bmiHeader.biSizeImage];
    
            MyBMInfo.bmiHeader.biCompression = BI_RGB; 
            MyBMInfo.bmiHeader.biBitCount = 24;
    
            // get the actual bitmap buffer
            GetDIBits(hdcScreen, hBmp, 0, -MyBMInfo.bmiHeader.biHeight, (LPVOID)lpPixels, &MyBMInfo, DIB_RGB_COLORS);
    
            //Clean Up
            ReleaseDC(0, hdcScreen);
            ReleaseDC(0, hdcCompatible);
            DeleteDC(hdcCompatible);
            DeleteDC(hdcScreen);
            DeleteObject(hBmp);
            DeleteObject(hOldBmp);
    
            return lpPixels;
        }
        catch(char *p)
        {
            return 0;
        }
    }
    

    C#

        [DllImport("consoleapplication1.dll", CallingConvention = CallingConvention.Cdecl)]
        static extern IntPtr ScreenCap(ref int size);
        [DllImport("consoleapplication1.dll", CallingConvention = CallingConvention.Cdecl)]
        static extern bool ScreenCapClean();
    
        int size = 0;
        IntPtr ptr = ScreenCap(ref size);
        byte[] result = new byte[size];
        Marshal.Copy(ptr, result, 0, size);
        Marshal.Release(ptr);
        ptr = IntPtr.Zero;
        //After doing what's required with the byte array
        bool hr = ScreenCapClean();