-1

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.

user3882856
  • 71
  • 1
  • 6
  • Try IntPtr : int hr = 0; IntPtr gData = IntPtr.Zero; IntPtr gSize = IntPtr.Zero; hr = ScreenCap(gData, gSize); – jdweng Aug 04 '17 at 06:47
  • thanks. i changed the byte[] to intptr in the dllimport and only the gData to IntPtr and it returns argumentnullexception – user3882856 Aug 04 '17 at 07:12
  • What size did you get? To get bytes you need to use Marshal : byte[] buffer = new byte[size]; Marshal.Copy(data, buffer, 0, size); – jdweng Aug 04 '17 at 09:03
  • i didn't get anything that way. only argumentnullexception – user3882856 Aug 04 '17 at 11:05
  • The size has to be correct. Check the size in c++ to see if it matches c#. A DWORD is unsigned so you may need to change int to uint in c#. – jdweng Aug 04 '17 at 11:54
  • @jdweng is giving you completely bogus advice as usual. Please disregard it. Unfortunately he doesn't understand pointers and repeatedly gives this bogus advice to pass a null pointer rather than the address of a variable. I cannot understand why he insists on commenting on these questions when he has not expertise in this subject. – David Heffernan Aug 04 '17 at 12:14
  • I've been doing mixed language programming for over 40 years and have a lot more experience then you will ever have. – jdweng Aug 04 '17 at 12:31
  • That's great @jdweng. Why do you keep making the same mistakes and leading these poor asker astray. How could passing null pointers to both of these parameters work? In reality your pride is stopping you from learning. – David Heffernan Aug 04 '17 at 12:56
  • @jdweng It's tedious to repeat this at question after question. This code: `*size = FileSize;`. You suggest executing this when `size == nullprtr`. Why do you keep making the same mistake? – David Heffernan Aug 04 '17 at 13:06
  • There's quite a design problem here. How can the caller allocate the byte array? How is it supposed to obtain the required size. You probably want to have the callee allocate the memory, with the attendent complications. Or return an HBITMAP. – David Heffernan Aug 04 '17 at 13:40
  • The c++ function is using the windows allocated method which means the data is not on the execution stack. Yes there can be a memory leak if you do not de-allocate the memory. But the c# doesn't need to allocate the memory until after the function is called and can use the size that is returned. I would of used int mySize = 0; Marshal.PtrToStruct(size, mySize); – jdweng Aug 04 '17 at 14:42
  • @jdweng Nope. Wrong again. – David Heffernan Aug 05 '17 at 11:29
  • So what would be right? – user3882856 Aug 05 '17 at 12:22

1 Answers1

1

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();
user3882856
  • 71
  • 1
  • 6