Microsoft MVP성태의 닷넷 이야기
VC++: 121. DXGI를 이용한 윈도우 화면 캡처 소스 코드(Visual C++) [링크 복사], [링크+제목 복사],
조회: 30925
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
(연관된 글이 2개 있습니다.)
(시리즈 글이 10개 있습니다.)
VC++: 121. DXGI를 이용한 윈도우 화면 캡처 소스 코드(Visual C++)
; https://www.sysnet.pe.kr/2/0/11385

.NET Framework: 705. C# - SharpDX + DXGI를 이용한 윈도우 화면 캡처 소스 코드
; https://www.sysnet.pe.kr/2/0/11400

.NET Framework: 706. C# - SharpDX + DXGI를 이용한 윈도우 화면 캡처 소스 코드 + Direct2D 출력
; https://www.sysnet.pe.kr/2/0/11401

.NET Framework: 712. C# - SharpDX + DXGI를 이용한 윈도우 화면 캡처 소스 코드 + Direct2D 출력 + OpenCV
; https://www.sysnet.pe.kr/2/0/11407

.NET Framework: 713. C# - SharpDX + DXGI를 이용한 윈도우 화면 캡처 소스 코드 + Direct2D 출력 + OpenCV (2)
; https://www.sysnet.pe.kr/2/0/11408

.NET Framework: 913. C# - SharpDX + DXGI를 이용한 윈도우 화면 캡처 라이브러리
; https://www.sysnet.pe.kr/2/0/12238

.NET Framework: 1123. C# - (SharpDX + DXGI) 화면 캡처한 이미지를 빠르게 JPG로 변환하는 방법
; https://www.sysnet.pe.kr/2/0/12889

.NET Framework: 1126. C# - snagit처럼 화면 캡처를 연속으로 수행해 동영상 제작
; https://www.sysnet.pe.kr/2/0/12895

.NET Framework: 1128. C# - 화면 캡처한 이미지를 ffmpeg(FFmpeg.AutoGen)로 동영상 처리
; https://www.sysnet.pe.kr/2/0/12897

.NET Framework: 1152. C# - 화면 캡처한 이미지를 ffmpeg(FFmpeg.AutoGen)로 동영상 처리 (저해상도 현상 해결)
; https://www.sysnet.pe.kr/2/0/12963




DXGI를 이용한 윈도우 화면 캡처 소스 코드(Visual C++)

화면 캡처 글을 검색해 보면 다양한 글이 나옵니다.

Various methods for capturing the screen
; http://www.codeproject.com/Articles/5051/Various-methods-for-capturing-the-screen

일단 위의 글에서는 3가지 방법을 소개하는데 그중 DirectX 예제만 따라 해 보았지만 Windows 10에서 동작하지 않았습니다. 왜냐하면 GetFrontBufferData 호출에서,

hr = g_pd3dDevice->GetFrontBufferData(0, pSurface);
// hr == 0x8876086c (D3DERR_INVALIDCALL)

D3DERR_INVALIDCALL 오류가 발생하는데 이유를 모르겠습니다. (혹시 아시는 분 덧글 부탁드립니다.)

게다가 d3dx9tex.h 헤더 파일 및 d3dx9.lib 파일이 없다고 오류가 발생하는데 이를 정상적으로 컴파일하기 위해서는 다음의 과정을 거쳐야 합니다.

DXSDK_Jun10.exe 설치 시 "Error Code: S1023" 오류 해결하는 방법
; https://www.sysnet.pe.kr/2/0/1195

귀찮으니 ^^; 다음 캡처 방식으로 넘어가 보겠습니다. Windows 8부터 지원하는 DXGI를 이용하면,

DXGI fast screen capture 
; http://www.pavelgurenko.com/2013/12/dxgi-outputs-enumeration-and-fast.html

The second way of performing capture is DirectX Graphics Infrastructure - you command video card to store the whole screen or one/several monitors's contents inside separate part of its memory (surface).


속도는 물론이고 일반적인 GDI API로는 할 수 없는 DirectX 게임 화면까지도 안정적으로 캡처할 수 있다고 합니다. 친절하게도 위의 블로그를 쓴 사람이 github에 소스 코드도 공개한 것이 있고,

pgurenko/DXGICaptureSample 
; https://github.com/pgurenko/DXGICaptureSample

또한 다음과 같은 식으로 사용하면, 3840 * 2160 해상도에서 30 fps로 캡처할 수 있었다고 합니다.

// https://github.com/pgurenko/DXGICaptureSample/issues/2

#include "stdafx.h"
#include "DXGIManager.h"
#include <time.h>

DXGIManager g_DXGIManager;

int capture(RECT& rcDim, vector<BYTE>& buf, CComPtr<IWICImagingFactory>& spWICFactory) {
  DWORD dwWidth = rcDim.right - rcDim.left;
  DWORD dwHeight = rcDim.bottom - rcDim.top;
  DWORD dwBufSize = buf.size();

  HRESULT hr = g_DXGIManager.GetOutputBits(buf.data(), rcDim);
  if (FAILED(hr))
  {
    printf("GetOutputBits failed with hr=0x%08x\n", hr);
    return hr;
  }
  return 0;
}

int _tmain(int argc, _TCHAR* argv[]) {
  CoInitialize(NULL);

  g_DXGIManager.SetCaptureSource(CSDesktop);

  RECT rcDim;
  g_DXGIManager.GetOutputRect(rcDim);

  DWORD dwWidth = rcDim.right - rcDim.left;
  DWORD dwHeight = rcDim.bottom - rcDim.top;

  printf("dwWidth=%d dwHeight=%d\n", dwWidth, dwHeight);

  DWORD dwBufSize = dwWidth*dwHeight * 4;

  vector<BYTE> buf(dwBufSize);

  CComPtr<IWICImagingFactory> spWICFactory = NULL;
  HRESULT hr = spWICFactory.CoCreateInstance(CLSID_WICImagingFactory);
  if (FAILED(hr))
    return hr;

  clock_t t1 = clock();
  int i;
  int iterations = 100;  
  for (i = 0; i < iterations; i++) {
    capture(rcDim, buf, spWICFactory);
  }
  clock_t t2 = clock();
  printf("%d iterations: %0.0f fps\n", iterations, iterations / ((double)(t2 - t1) / CLOCKS_PER_SEC));

  return 0;
}

하지만 실제로 해보면 iterations 중에 capture 함수 내의 "GetOutputBits failed with hr=0x887a0027" 호출 실패가 있기 때문에 이를 감안하면 성능이 더 낮을 것입니다. 그래도 이 정도면 훌륭하죠. ^^


여기서 한 가지 중요한 점은, GetOutputBits 함수 내에서 호출하는 IDXGIOutputDuplication::AcquireNextFrame의 동작 방식입니다.

AcquireNextFrame은, 캡처 대상이 되는 화면에서 변화가 있어야 그것을 이미지 데이터로 반환합니다. 만약 지정한 시간 내에 변화가 없으면 DXGI_ERROR_WAIT_TIMEOUT 값을 반환합니다. 상당히 효율적이죠? ^^

실제로, 30fps 동영상을 바탕화면에서 재생하면서 IDXGIOutputDuplication::AcquireNextFrame을 호출하면 1초에 30번 정도 호출이 됩니다. 참고로, 바탕화면의 변화에는 (실제 캡처된 이미지에는 나오지 않지만) 마우스 커서 움직임도 포함이 됩니다.

DXGIOutputDuplication::AcquireNextFrame 내의 AcquireNextFrame 호출을 좀 더 살펴볼까요?

HRESULT DXGIOutputDuplication::AcquireNextFrame(IDXGISurface1** pDXGISurface, DXGIPointerInfo*& pDXGIPointer)
{
    DXGI_OUTDUPL_FRAME_INFO fi;
    CComPtr<IDXGIResource> spDXGIResource;

    HRESULT hr = m_DXGIOutputDuplication->AcquireNextFrame(50, &fi, &spDXGIResource);
    if(FAILED(hr))
    {
        __L_INFO("m_DXGIOutputDuplication->AcquireNextFrame failed with hr=0x%08x", hr);
        return hr;
    }

    // ... [생략] ...
}

인자로 전달한 50은 50ms 시간 내에서 다음 화면 데이터를 얻어내라는 것입니다. 만약 그 시간 내에 데이터를 얻을 수 없었다면 hr == 0x887a0027 (The timeout value has elapsed and the resource is not yet available) 오류가 발생합니다. 따라서 저 시간 값을 너무 낮추면 (변화가 없는 화면인 경우) 호출에 대한 오류가 쓸데없이 많이 발생할 수 있습니다.

그 외에, 모니터 3대인 경우에는 CSMonitor3 스위치 문을 다음과 같이 DXGIManager::GetOutputDuplication 함수에 추가하면 됩니다.

case CSMonitor3:
{
    int secondary = 0;
    // Return the first with !IsPrimary
    for (vector<DXGIOutputDuplication>::iterator iter = m_vOutputs.begin();
        iter != m_vOutputs.end();
        iter++)
    {
        DXGIOutputDuplication& out = *iter;
        if (!out.IsPrimary())
        {
            if (secondary == 0)
            {
                secondary = 1;
                continue;
            }

            outputs.push_back(out);
            break;
        }
    }
}
break;




위의 소개 글에서 pgurenko/DXGICaptureSample로 공개된 캡처 예제를 버퍼 그대로 Bitmap으로 바꿔 화면에 출력하는 코드를 다음과 같이 작성해 봤습니다.

#include "stdafx.h"
#include "DXGIManager.h"
#include <time.h>

DXGIManager g_DXGIManager;

LRESULT WINAPI MsgProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);

WNDCLASSEX g_wc = { sizeof(WNDCLASSEX), CS_CLASSDC, MsgProc, 0L, 0L, GetModuleHandle(NULL), NULL, NULL, NULL, NULL, L"Foo", NULL };
HWND g_hWnd;
HBITMAP g_hbm;
int hDC;
int g_screenX, g_screenY;

int _tmain(int argc, _TCHAR* argv[])
{
    CoInitialize(NULL);

    g_DXGIManager.SetCaptureSource(CaptureSource::CSMonitor1);

    RECT rcDim;
    g_DXGIManager.GetOutputRect(rcDim);

    DWORD dwWidth = rcDim.right - rcDim.left;
    DWORD dwHeight = rcDim.bottom - rcDim.top;

    g_screenX = dwWidth;
    g_screenY = dwHeight;

    RegisterClassEx(&g_wc);
    g_hWnd = CreateWindow(L"Foo", L"Foo", WS_OVERLAPPEDWINDOW, 0, 0, dwWidth, dwHeight, NULL, NULL, g_wc.hInstance, NULL);
    ::ShowWindow(g_hWnd, SW_SHOW);

    printf("dwWidth=%d dwHeight=%d\n", dwWidth, dwHeight);

    HDC hdcWin = GetDC(g_hWnd);
    RECT rc = { 0, 0, dwWidth, dwHeight };
    BITMAPINFO bmi = { 0 };
    bmi.bmiHeader.biSize = sizeof(bmi.bmiHeader);
    bmi.bmiHeader.biWidth = dwWidth;
    bmi.bmiHeader.biHeight = -dwHeight;
    bmi.bmiHeader.biPlanes = 1;
    bmi.bmiHeader.biBitCount = 32;
    bmi.bmiHeader.biCompression = BI_RGB;
    RGBQUAD *prgbBits;
    g_hbm = CreateDIBSection(hdcWin, &bmi, DIB_RGB_COLORS, &reinterpret_cast<void*&>(prgbBits), NULL, 0);

    DWORD dwBufSize = dwWidth*dwHeight * 4;

    vector<BYTE> buf(dwBufSize);
    BYTE *pBuf = buf.data();

    MSG msg;

    while (1)
    {
        if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE) == TRUE)
        {
            if (msg.message == WM_QUIT)
            {
                break;
            }

            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
        else
        {
            HRESULT hr = g_DXGIManager.GetOutputBits(pBuf, rcDim);

            if (hr != S_OK)
            {
                continue;
            }

            memcpy(prgbBits, pBuf, dwWidth * dwHeight * 4);
            ::InvalidateRect(g_hWnd, nullptr, TRUE);
        }
    }

    CoUninitialize();

    return msg.wParam;
}

LRESULT WINAPI MsgProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    if (msg == WM_CREATE)
    {
        return 0;
    }

    if (msg == WM_PAINT)
    {
        PAINTSTRUCT paint;
        HDC hDC = ::BeginPaint(hWnd, &paint);

        HDC hdcMem = CreateCompatibleDC(hDC);
        HBITMAP hbmPrev = (HBITMAP)::SelectObject(hdcMem, g_hbm);

        BitBlt(hDC, 0, 0, g_screenX, g_screenY, hdcMem, 0, 0, SRCCOPY);
        
        SelectObject(hdcMem, hbmPrev);
        DeleteDC(hdcMem);

        ::EndPaint(hWnd, &paint);
        return 0;
    }

    return DefWindowProc(hWnd, msg, wParam, lParam);
}

첨부 파일은 위의 예제 코드를 포함합니다.

실제로 실행해서 프로그램을 돌려보면... 부드러운 캡처 성능에 반하실 것입니다. ^^




시간 있으시면 다음의 글도 보시면 좋겠지요. ^^

Desktop Screen Capture on Windows via Windows Desktop Duplication API with Drawing of Cursor's Image
; https://www.codeproject.com/Tips/1116253/Desktop-Screen-Capture-on-Windows-via-Windows-Desk

DXGI desktop duplication sample
; https://code.msdn.microsoft.com/windowsdesktop/Desktop-Duplication-Sample-da4c696a

The reboot of Coding4Fun
; https://blogs.windows.com/windowsdeveloper/2015/12/21/the-reboot-of-coding4fun/




[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]

[연관 글]






[최초 등록일: ]
[최종 수정일: 5/10/2023]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer at outlook.com

비밀번호

댓글 작성자
 



2018-06-17 05시05분
[지팡이] 잘 읽고갑니당
[guest]
2019-03-27 04시08분
[여우] DXGIManager에서 형변환 오류가 뜨는데 어떻게 해야하나요..?
2017 사용중입니다
[guest]
2019-03-27 09시27분
DXGIManager의 어느 코드에서 형 변환 오류가 뜬다는 거죠? 그리고 이 글에 첨부한 예제 코드로 실행했을 때 발생하는 건가요?
정성태
2022-03-22 11시51분
[홍길동] 비쥬얼스튜디오 2022 버전에서 소스를 복사하여 실행하면
DXGIManager.cpp에서
CComQIPtr<ID3D11Texture2D> spTextureResource = spDXGIResource;
이 부분이 에러가 납니다.

심각도    코드    설명    프로젝트    파일    줄    비표시 오류(Suppression) 상태
오류(활성)    E0312    "ATL::CComPtr<IDXGIResource>"에서 "ATL::CComQIPtr<ID3D11Texture2D, &__uuidof(ID3D11Texture2D)>"(으)로의 사용자 정의 변환이 적절하지 않습니다.    DXGICaptureSample    D:\[1]개발자파일\소스파일\기타\C++\샘플\DXGICaptureSample\DXGICaptureSample\DXGICaptureSample\DXGIManager.cpp    74
[guest]
2022-03-22 07시09분
@홍길동 "실행하면" 에러가 나는 것이 아니고 "컴파일하면" 에러가 나는 것이죠?

만약 오류 메시지를 올려 주시지 않았다면 ^^; 안 믿었을 것 같습니다. 위에서 @여우 님이란 분도 비슷한 오류를 경험한 듯한데, 그런 걸로 봐서는 분명히 컴파일 오류가 발생하고 있는 것 같지만... 개인적으로는 잘 이해가 안 되는 오류입니다. 왜냐하면, 해당 코드는 QueryInterface의 특성상 상속 구조를 밝힐 수 있는 것이 아니므로 형변환 관련한 체크가 관여할 수 없기 때문입니다. C++ 컴파일러가 저 변환이 적절하지 않다는 것을 도대체 무슨 근거로 판단한 것인지... ^^; 이해가 안 됩니다.

참고로, 제 컴퓨터에서는 (저 당시에도 컴파일이 잘 됐고) 현재 Visual Studio 2022에서 정상적으로 컴파일이 잘 됩니다. (물론 실행도 잘 됩니다.)

일단, 저 코드에서 오류가 발생한다면, 다음과 같이 코드를 바꿔보세요.

-----------------------------------------------------------------------
// 아래의 주석 코드와 그다음 QueryInterface를 호출하는 코드는 같은 역할을 합니다.

// CComQIPtr<ID3D11Texture2D> spTextureResource = spDXGIResource;
    
ID3D11Texture2D* pTextureResource;
hr = spDXGIResource->QueryInterface(__uuidof(ID3D11Texture2D), (LPVOID *)&pTextureResource);
if (hr == S_OK)
{
    printf("spDXGIResource 0x%p", pTextureResource);
    pTextureResource->Release();
}
-----------------------------------------------------------------------

CComQIPtr을 풀어 쓰면 위와 같이 QueryInterface로 처리하는 것과 같습니다. 암튼... 역시나 이해가 안 되는군요. C++ 컴파일러가 저 아래의 코드와 같이 수행하는 것을 변환이 적절하지 않다고 판단하는 건데... ^^;
정성태
2022-03-23 01시07분
[홍길동] 답변 감사합니다.
첨부파일을 받아 컴파일하면 에러가 나지않습니다.
2022버전에서 프로젝트를 새로 만든 다음 소스를 옮겨 컴파일하면 총 4곳이 에러가 납니다.

CComQIPtr<ID3D11Texture2D> spTextureResource = spDXGIResource;
CComQIPtr<IDXGISurface1> spDXGISurface = spD3D11Texture2D;
CComQIPtr<IDXGIDevice1> spDXGIDevice = spD3D11Device;
CComQIPtr<IDXGIDevice1> spDXGIDevice = spD3D11Device;

에러 내용은 같습니다. 원인이 궁금한데 이유를 모르겠습니다. ㅠㅠ
[guest]
2022-03-23 01시54분
@홍길동 그렇게 만든 프로젝트를 첨부 파일로 올려주세요. 질문/답변 게시판에서 올리시면 됩니다.

업데이트) 아래의 글에서 컴파일 에러 현상에 대해 설명했고, 현재 변경된 소스 코드로 첨부 파일을 업데이트했습니다.

CComPtr/CComQIPtr과 Conformance mode 옵션의 충돌
; https://www.sysnet.pe.kr/2/0/13014
정성태
2022-03-31 08시30분
[소울코더] 안녕하세요. 좋을 정보 감사합니다.

혹시
Window Handle 혹은 DC로 Window 하나만 타켓팅하여 DXGI로 캡쳐할 수 있는 방법이 있을까요?
[guest]
2022-03-31 10시05분
@소울코더 일단 DXGI는 특정 window만을 타게팅하여 캡처할 수는 없습니다. 윈도우 캡처를 위해서는 고전적인 BitBlt를 사용해야 합니다. 단지 UWP 응용 프로그램을 만든다면 Windows 10 1803 버전부터 가능한 Windows.Graphics.Capture 방법을 사용할 수 있습니다. 아래는 관련 문서입니다.

Screen capture
; https://learn.microsoft.com/en-us/windows/uwp/audio-video-camera/screen-capture

이후 Windows 10 1903부터는 Win32 응용 프로그램에서도 Windows.Graphics.Capture 방법을 사용할 수 있게 지원을 하는데, 이와 관련해서는 다음의 예제가 제공되고 있습니다.

Windows.UI.Composition-Win32-Samples/cpp/ScreenCaptureforHWND/
; https://github.com/microsoft/Windows.UI.Composition-Win32-Samples/tree/master/cpp/ScreenCaptureforHWND

유명한 OBS Studio 같은 프로그램도 윈도우 버전에 따라 저 방식을 이용해 윈도우 캡처를 지원합니다.

(언제 저도 시간나면 이 글의 예제에 윈도우 캡처 예제도 추가해야겠군요. ^^)
정성태
2022-05-07 08시07분
[곰팡이] 이코드에서 파일을 이미지로 저장하거나, 리소스로 사용하고싶은데 어떻게하면되나요
[guest]
2022-05-07 01시39분
@곰팡이 소스 코드를 보면, 캡처 화면을 HBITMAP으로 저장하고 있습니다. 따라서, 그것을 이용해 BMP 파일과 같은 형식으로 저장하시면 될 것입니다. 관련 소스 코드는 아래의 글에 있는 CreateBitmapInfoStruct 함수를 참고하세요.

Save HBITMAP to *.bmp file using only Win32
; https://stackoverflow.com/questions/24720451/save-hbitmap-to-bmp-file-using-only-win32
정성태
2022-05-07 07시49분
[곰팡이] #include "stdafx.h"
#include "DXGIManager.h"
#include <time.h>
#include <iostream>
#include <vector>
DXGIManager g_DXGIManager;

#include <stdio.h>
#include <crtdbg.h>
#include <Windows.h>
#include <fstream>
#if _DEBUG
#define new new(_NORMAL_BLOCK, __FILE__, __LINE__)
#define malloc(s) _malloc_dbg(s, _NORMAL_BLOCK, __FILE__, __LINE__)
#endif

#define BIT_COUNT 24;

BOOL SaveHBITMAPToFile(HBITMAP hBitmap, LPCSTR lpszFileName)
{
    // output stream 선언
    ofstream stream;
    // stream open (바이너리 모드)
    stream.open(lpszFileName, ios::binary);
    // stream이 열리지 않으면 에러.
    if (!stream.is_open())
    {
        cout << "File open error!!" << endl;
        return FALSE;
    }
    // 저장할 bitmap 선언
    BITMAP bitmap;
    // hBitmap으로 bitmap을 가져온다.
    GetObject(hBitmap, sizeof(bitmap), (LPSTR)&bitmap);
    // Bitmap Header 정보 설정
    BITMAPINFOHEADER bi;
    bi.biSize = sizeof(BITMAPINFOHEADER);
    bi.biWidth = bitmap.bmWidth;
    bi.biHeight = bitmap.bmHeight;
    bi.biPlanes = 1;
    bi.biBitCount = BIT_COUNT;
    bi.biCompression = BI_RGB;
    bi.biSizeImage = 0;
    bi.biXPelsPerMeter = 0;
    bi.biYPelsPerMeter = 0;
    bi.biClrUsed = 0;
    bi.biClrImportant = 0;
    // 컬러 사이즈
    int PalSize = (bi.biBitCount == 24 ? 0 : 1 << bi.biBitCount) * sizeof(RGBQUAD);
    int Size = bi.biSize + PalSize + bi.biSizeImage;
    BITMAPFILEHEADER fh;
    fh.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + PalSize;
    fh.bfReserved1 = 0;
    fh.bfReserved2 = 0;
    fh.bfSize = Size + sizeof(BITMAPFILEHEADER);
    fh.bfType = 0x4d42;
    // 파일에 bitmap 해더 작성
    stream.write((LPSTR)&fh, sizeof(BITMAPFILEHEADER));
    // DC 취득
    HDC hDC = GetDC(NULL);
    // 메모리 할당 (bitmap header)
    BITMAPINFO* header = (BITMAPINFO*)malloc(bi.biSize + PalSize);
    header->bmiHeader = bi;
    // hBitmap으로 부터 해더를 가져온다.
    GetDIBits(hDC, hBitmap, 0, bitmap.bmHeight, NULL, header, DIB_RGB_COLORS);
    // 이미지 전체 사이즈를 취득한다.
    bi = header->bmiHeader;
    if (bi.biSizeImage == 0)
    {
        // 해더 사이즈 설정이 안되면 강제 계산 설정
        bi.biSizeImage = ((bitmap.bmWidth * bi.biBitCount + 31) & ~31) / 8 * bitmap.bmHeight;
    }
    // 이미지 영역 메모리 할당
    Size = bi.biSize + PalSize + bi.biSizeImage;
    void* body = malloc(header->bmiHeader.biSizeImage);
    // hBitmap의 데이터를 저장
    GetDIBits(hDC, hBitmap, 0, header->bmiHeader.biHeight, body, header, DIB_RGB_COLORS);
    // 데이터 작성
    stream.write((LPSTR)&header->bmiHeader, sizeof(BITMAPINFOHEADER));
    stream.write((LPSTR)body, Size);
    // DC 해제
    ReleaseDC(NULL, hDC);
    // stream 해제
    stream.close();
    // 메모리 해제
    delete header;
    delete body;
}

int capture(RECT& rcDim, vector<BYTE>& buf, CComPtr<IWICImagingFactory>& spWICFactory) {
    DWORD dwWidth = rcDim.right - rcDim.left;
    DWORD dwHeight = rcDim.bottom - rcDim.top;
    DWORD dwBufSize = buf.size();

    while (1) {
        HRESULT hr = g_DXGIManager.GetOutputBits(buf.data(), rcDim);
        if (!FAILED(hr))
        {
            printf("succ\n");
            return hr;
        }
        else {
            printf("GetOutputBits failed with hr=0x%08x\n", hr);
        }
    }
    return 0;
}

int _tmain(int argc, _TCHAR* argv[]) {
    CoInitialize(NULL);

    g_DXGIManager.SetCaptureSource(CSDesktop);

    RECT rcDim;
    g_DXGIManager.GetOutputRect(rcDim);

    DWORD dwWidth = rcDim.right - rcDim.left;
    DWORD dwHeight = rcDim.bottom - rcDim.top;

    printf("dwWidth=%d dwHeight=%d\n", dwWidth, dwHeight);

    DWORD dwBufSize = dwWidth * dwHeight * 4;

    vector<BYTE> buf(dwBufSize);

    int buf1[80] = {0,};

    CComPtr<IWICImagingFactory> spWICFactory = NULL;
    HRESULT hr = spWICFactory.CoCreateInstance(CLSID_WICImagingFactory);
    if (FAILED(hr))
        return hr;

    int t1 = clock();
    int i;
    int iterations = 1;
    for (i = 0; i < iterations; i++) {
        capture(rcDim, buf, spWICFactory);

        printf("%d" ,buf.begin());

        vector<BYTE>::iterator p= buf.begin();
    
        BITMAPINFO bmi = { 0 };
        bmi.bmiHeader.biSize = sizeof(bmi.bmiHeader);
        bmi.bmiHeader.biWidth = dwWidth;
        bmi.bmiHeader.biHeight = -dwHeight;
        bmi.bmiHeader.biPlanes = 1;
        bmi.bmiHeader.biBitCount = 32;
        bmi.bmiHeader.biCompression = BI_RGB;

 /* HWND m_hwnd= GetDesktopWindow();
        HDC hdc = GetDC(m_hwnd);*/
        HDC hdc = CreateCompatibleDC(NULL);
        HBITMAP bitmap1 = CreateDIBitmap(hdc, &bmi, CBM_INIT, buf.data(), NULL, DIB_RGB_COLORS);
        ///
        SaveHBITMAPToFile(bitmap1, "C:\\배포용\\img\\test.bmp");
    }

    int t2 = clock();
    printf(" %dms \n", (t2 - t1));

    return 0;
}


다음과같이 vector정보를 바로 비트맵으로 저장하는걸 구현하고싶은데
혹시 어디가 오류인지 봐주실수있나요?
[guest]
2022-05-07 07시53분
[곰팡이] CreateDIBitmap 함수를쓰는데
두번째인자 &bmi에서
BITMAPINFO* 형식의 인수가 constBITMPAINFOHEADER*의 형식과 맞지않다는데...

그리고 이방법이 유효할까요?
[guest]
2022-05-07 08시40분
결과를 내려고 조급하게 서두르지 마시고, 소스 코드를 찬찬히 뜯어보면서 각 라인들이 어떤 역할을 하는지 잘 이해해 보세요. 할 수 있습니다. ^^

BITMAPINFO와 BITMAPINFOHEADER도 각각의 구조체가 어떻게 정의되어 있는지를 보면 알 수 있습니다.
정성태
2022-05-10 08시59분
[곰팡이] 감사합니다!
근데 ctrl alt delete 누르거나 모니터 연결끊어졌다연결되면 검은화면출력되는데
프로그램다시 안껏다키고 다시 나오게 할 수있나요?
[guest]
2022-05-10 09시09분
저도 그 부분은 세세하게 테스트하지는 않았는데, 아마도 관련 DXGI 인터페이스를 새롭게 초기화를 해야 할 것입니다. (혹시나 좀 더 나은 방법을 발견하시게 되거든 공유 부탁드립니다. ^^)
정성태

... 121  122  123  124  125  126  127  128  129  130  131  [132]  133  134  135  ...
NoWriterDateCnt.TitleFile(s)
1755정성태9/22/201434237오류 유형: 241. Unity Web Player를 설치해도 여전히 설치하라는 화면이 나오는 경우 [4]
1754정성태9/22/201424561VC++: 80. 내 컴퓨터에서 C++ AMP 코드가 실행이 될까요? [1]
1753정성태9/22/201420594오류 유형: 240. Lync로 세미나 참여 시 소리만 들리지 않는 경우 [1]
1752정성태9/21/201441056Windows: 100. 윈도우 8 - RDP 연결을 이용해 VNC처럼 사용자 로그온 화면을 공유하는 방법 [5]
1751정성태9/20/201438911.NET Framework: 464. 프로세스 간 통신 시 소켓 필요 없이 간단하게 Pipe를 열어 통신하는 방법 [1]파일 다운로드1
1750정성태9/20/201423827.NET Framework: 463. PInvoke 호출을 이용한 비동기 파일 작업파일 다운로드1
1749정성태9/20/201423728.NET Framework: 462. 커널 객체를 위한 null DACL 생성 방법파일 다운로드1
1748정성태9/19/201425377개발 환경 구성: 238. [Synergy] 여러 컴퓨터에서 키보드, 마우스 공유
1747정성태9/19/201428384오류 유형: 239. psexec 실행 오류 - The system cannot find the file specified.
1746정성태9/18/201426066.NET Framework: 461. .NET EXE 파일을 닷넷 프레임워크 버전에 상관없이 실행할 수 있을까요? - 두 번째 이야기 [6]파일 다운로드1
1745정성태9/17/201423027개발 환경 구성: 237. 리눅스 Integration Services 버전 업그레이드 하는 방법 [1]
1744정성태9/17/201431029.NET Framework: 460. GetTickCount / GetTickCount64와 0x7FFE0000 주솟값 [4]파일 다운로드1
1743정성태9/16/201420980오류 유형: 238. 설치 오류 - Failed to get size of pseudo bundle
1742정성태8/27/201426945개발 환경 구성: 236. Hyper-V에 설치한 리눅스 VM의 VHD 크기 늘리는 방법 [2]
1741정성태8/26/201421326.NET Framework: 459. GetModuleHandleEx로 알아보는 .NET 메서드의 DLL 모듈 관계파일 다운로드1
1740정성태8/25/201432491.NET Framework: 458. 닷넷 GC가 순환 참조를 해제할 수 있을까요? [2]파일 다운로드1
1739정성태8/24/201426487.NET Framework: 457. 교착상태(Dead-lock) 해결 방법 - Lock Leveling [2]파일 다운로드1
1738정성태8/23/201422040.NET Framework: 456. C# - CAS를 이용한 Lock 래퍼 클래스파일 다운로드1
1737정성태8/20/201419736VS.NET IDE: 93. Visual Studio 2013 동기화 문제
1736정성태8/19/201425563VC++: 79. [부연] CAS Lock 알고리즘은 과연 빠른가? [2]파일 다운로드1
1735정성태8/19/201418147.NET Framework: 455. 닷넷 사용자 정의 예외 클래스의 최소 구현 코드 - 두 번째 이야기
1734정성태8/13/201419799오류 유형: 237. Windows Media Player cannot access the file. The file might be in use, you might not have access to the computer where the file is stored, or your proxy settings might not be correct.
1733정성태8/13/201426326.NET Framework: 454. EmptyWorkingSet Win32 API를 사용하는 C# 예제파일 다운로드1
1732정성태8/13/201434450Windows: 99. INetCache 폴더가 다르게 보이는 이유
1731정성태8/11/201427046개발 환경 구성: 235. 점(.)으로 시작하는 파일명을 탐색기에서 만드는 방법
1730정성태8/11/201422136개발 환경 구성: 234. Royal TS의 터미널(Terminal) 연결에서 한글이 깨지는 현상 해결 방법
... 121  122  123  124  125  126  127  128  129  130  131  [132]  133  134  135  ...