BitBlt 转换为字节数组并从 c++ 解析为 c#
Posted
技术标签:
【中文标题】BitBlt 转换为字节数组并从 c++ 解析为 c#【英文标题】:BitBlt convert to byte array and parse from c++ to c# 【发布时间】:2018-01-11 23:16:58 【问题描述】:我正在尝试在 C++ 中创建屏幕捕获 DLL,并将生成的字节数组发送到 C#。
我能够将大小返回给 C#,但字节数组始终为空。
这是 C++ 代码(由我在互联网上找到的部分组成)
__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;
这是我使用“ref”关键字的 C# 代码。
[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;
我认为我的 GetDIBits 可能存在问题,但我不确定。我希望你们中的一些大师可以让我走上正确的道路。谢谢。
* 编辑 *
好的,非常感谢 jdweng 和 filip 为我指明了正确的方向。我现在将屏幕作为 IntPtr 接收并能够将其渲染到我的 C# 应用程序。这是现在可以工作的代码(我会在底部解释)
__declspec(dllexport) char* ScreenCap(DWORD * size)
尝试 // 获取屏幕尺寸 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;
和 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);
不确定我现在面临的问题是否是由于这样做造成的,但我现在从 C++ dll 中得到了严重的内存泄漏。如果这是另一个问题,我需要创建一个新线程吗?
** 编辑 **
通过在 C++ dll 中创建一个新函数来删除返回的 char 数组来解决问题。可能不优雅,但很有效。
我要非常感谢 Filip,他的 cmets 帮助我查看了各种地方并看到了其他东西。 Jdweng,你的 IntPtr 是上帝派来的。不知道我如何将其设置为 Jdweng 的回答。很乐意为你们俩颁奖。
【问题讨论】:
尝试 IntPtr : int hr = 0; IntPtr gData = IntPtr.Zero; IntPtr gSize = IntPtr.Zero; hr = ScreenCap(gData, gSize); 谢谢。我在 dllimport 中将 byte[] 更改为 intptr,仅将 gData 更改为 IntPtr,它返回 argumentnullexception 你买了什么尺寸的?要获取字节,您需要使用 Marshal : byte[] buffer = new byte[size]; Marshal.Copy(data, buffer, 0, size); 我没有得到任何东西。只有参数nullexception 尺寸必须正确。检查 c++ 中的大小以查看它是否与 c# 匹配。 DWORD 是无符号的,因此您可能需要在 c# 中将 int 更改为 uint。 【参考方案1】:如果有人正在寻找相同的东西,这对我有用。我要非常感谢 Filip Kocica 和 jdweng,他们的建议给了我一些想法并帮助了我很多。非常感谢和感激。
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();
【讨论】:
以上是关于BitBlt 转换为字节数组并从 c++ 解析为 c#的主要内容,如果未能解决你的问题,请参考以下文章
如何将从 C++ 发送的 cv::MAT 字节数组转换为 Java 中的位图?