应用分享相关功能+代码片段

获取窗口是否是最小/大化的:

	WINDOWPLACEMENT wp = { sizeof(WINDOWPLACEMENT) };
	for (auto wnd : wnds) {
		if (wnd == captured_wnd_) {
			continue;
		}

		if (GetWindowPlacement(wnd, &wp) && wp.showCmd != SW_HIDE && wp.showCmd != SW_MINIMIZE) {
			wnds_to_exclude.insert(wnd);
		}
	}

监控当前窗口创建和销毁

SetWinEventHook(EVENT_OBJECT_DESTROY, /*EVENT_OBJECT_SHOW*/EVENT_OBJECT_UNCLOAKED, nullptr,
        [](HWINEVENTHOOK eventHook, DWORD event, HWND hwnd, LONG objectId, LONG childId, DWORD eventThreadId, DWORD eventTimeInMilliseconds)
        {
            if (event == EVENT_OBJECT_DESTROY && childId == CHILDID_SELF)
            {
                WindowListForThread->RemoveWindow(WindowInfo(hwnd));
                return;
            }

            if (objectId == OBJID_WINDOW && childId == CHILDID_SELF && hwnd != nullptr && GetAncestor(hwnd, GA_ROOT) == hwnd &&
                GetWindowTextLengthW(hwnd) > 0 && (event == EVENT_OBJECT_SHOW || event == EVENT_OBJECT_UNCLOAKED))
            {
                auto window = WindowInfo(hwnd);

                if (IsCapturableWindow(window))
                {
                    WindowListForThread->AddWindow(window);
                }
            }
        }, 0, 0, WINEVENT_OUTOFCONTEXT);

相关资料:

Magnification API截取数据的回调函数

static MAGIMAGEHEADER mag_img_header;
static BYTE* s_captured_data = nullptr;
static int s_frame_width = 0;
static int s_frame_height = 0;
static int64_t s_frame_len = 0;

static BOOL MagImageScaling(HWND hwnd, void * srcdata, MAGIMAGEHEADER srcheader, void * destdata, MAGIMAGEHEADER destheader, RECT unclipped, RECT clipped, HRGN dirty)
{
    if (s_captured_data) {
        delete s_captured_data;
        s_captured_data = nullptr;
    }

    // RGB format
    s_frame_width = srcheader.width;
    s_frame_height = srcheader.height;
    s_frame_len = srcheader.cbSize;

    memcpy(&mag_img_header, &srcheader, sizeof(mag_img_header));
    s_captured_data = (BYTE*)malloc(srcheader.cbSize);
    memcpy(s_captured_data, srcdata, srcheader.cbSize);

    // bottom->top order convert to top->bottom
    auto bitcount = (WORD)((srcheader.cbSize / srcheader.height) / srcheader.width) * 8;
    LONG lineSize = srcheader.width * bitcount / 8;
    BYTE* pLineData = new BYTE[lineSize];
    BYTE* pStart;
    BYTE* pEnd;
    LONG lineStart = 0;
    LONG lineEnd = srcheader.height - 1;
    while (lineStart < lineEnd)
    {
        // Get the address of the swap line
        pStart = s_captured_data + (lineStart * lineSize);
        pEnd = s_captured_data + (lineEnd * lineSize);
        // Swap the top with the bottom
        memcpy(pLineData, pStart, lineSize);
        memcpy(pStart, pEnd, lineSize);
        memcpy(pEnd, pLineData, lineSize);

        // Adjust the line index
        lineStart++;
        lineEnd--;
    }
    delete pLineData;

    return TRUE;
}

放大镜API调用的问题:Magnification API - Setting up an include list for the Magnifier on Win7 is not working, Magnifier API on WoW64

Magnification的初始化,也必须在GUI线程中。

Vista之后窗口阴影,窗口大小,客户区域大小的区别:

RECT rc{ 0,0,1,1 };
if (FAILED(DwmGetWindowAttribute(captured_wnd_, DWMWA_EXTENDED_FRAME_BOUNDS, &rc, sizeof(RECT)))) {
    ::GetWindowRect(captured_wnd_, &rc);
}

但是我在Win10上得到的这个区域的大小与GetWindowRect的一样,不知道怎么回事。
后来的解决方案是直接获取client area,然后转换为桌面坐标:

RECT rc{ 0,0,1,1 };
::GetClientRect(captured_wnd_, &rc);
MapWindowPoints(captured_wnd_, HWND_DESKTOP, (LPPOINT)&rc, 2);

参考:

鼠标的显示隐藏

while (::ShowCursor(FALSE) >= 0);
// ... 执行需要隐藏鼠标的操作
while (::ShowCursor(TRUE) < 0);

参考:

What was the ShowCursor function intended to be used for?

判断WOW64

BOOL Is64BitWindows()
{
#if defined(_WIN64)
 return TRUE;  // 64-bit programs run only on Win64
#elif defined(_WIN32)
 // 32-bit programs run on both 32-bit and 64-bit Windows
 // so must sniff
 BOOL f64 = FALSE;
 return IsWow64Process(GetCurrentProcess(), &f64) && f64;
#else
 return FALSE; // Win64 does not support Win16
#endif
}

参考:

How to detect on C++ is windows 32 or 64 bit?

检查当前主题是否开启了Aero

BOOL enabled = FALSE;
if (SUCCEEDED(DwmIsCompositionEnabled(&enabled)) && enabled) {
    OutputDebugStringA("dwm enabled\n");
}
else {
    OutputDebugStringA("dwm not enabled\n");
}

参考:
Why can’t I use Magnifier in Full Screen or Lens mode?

另外发现,x86的程序使用放大镜接口来做应用取屏,结果在64bit的Win7上也可以运行。怀疑系统功能支持之后,官方文档没有更新,或者是理解的不对。

而且,Win7上我们的SDK demo在运行桌面取屏的时候会触发系统的兼容性提示,导致系统主题切换为Windows Basic,但是提示也说了,这个程序关闭之后,系统会恢复Aero主题。参考这个帖子 How to prevent the color scheme from automatically being changed when resources are low.,中Juke Chou的回答,如果dwm发现系统硬件不足以支持Aero,也会关闭Aero;可以做的是,关闭操作系统的兼容性助手。

多线程与窗口

实现放大镜功能的时候发现必须运行在GUI线程。Windows允许判断当前线程是否为GUI线程,并且可以将非GUI线程切换为GUI线程。创建了窗口的线程,在等待的时候必须使用MsgWaitForMultipleObjects 或者 MsgWaitForMultipleObjectsEx等待。参考:IsGUIThreadCreating Windows in Threads

发现调用SetWinEventHook之后,结果任何事件都截获不到,看文档才发现,这个函数所运行的线程中必须有一个消息泵来接受事件。参考:How can I get notified when some other window is destroyed?SetWinEventHook

UnhookWinEvent的调用必须与调用SetWinEventHook的线程一致。

另外,关于线程还有一个问题:_beginthread返回的线程句柄不能用来调用CloseHandle,而是应该使用_endthread,参考:Why does my thread handle suddenly go bad? All I did was wait on it!

Preventing Hangs in Windows Applications 提到:

Do not:
Wait on any kernel object (like Event or Mutex) for more than a very short amount of time; if you have to wait at all, consider using MsgWaitForMultipleObjects(), which will unblock when a new message arrives

通过在子线程中创建窗口,你会很容易学习到:

  • 线程A创建的窗口不能在线程B中调用DestroyWindow,会失败的。这一点DestroyWindow function的Remarks中说的很清楚:A thread cannot use DestroyWindow to destroy a window created by a different thread.
  • RegisterClassEx注册窗口类之后,创建了窗口,在调用UnregisterClass反注册窗口类之前,必须销毁(DestroyWindow)全部该类型窗口实例,才能销毁成功,不然的话,GetLastError会报错:Class still has open window;而且dll中注册了窗口类,卸载dll的时候,不会自动调用反注册方法,所以必须是dll自己显式反注册窗口类,参考 RegisterClassExA function 的Remarks。
  • 如果调用两次UnregisterClass,其实第二次会返回错误的,告诉你:window class not exist.
  • 关闭一个窗口,有时候你会疑惑该使用CloseWindow,还是DestroyWindow,参考: Closing the Window,简单地说就是如果点击窗口的叉叉,或者Alt+F4,那么会给窗口发送WM_CLOSE,如果用户要销毁窗口,可以在WM_CLOSE中调用DestroyWindow;然后DestroyWindow会在窗口移出屏幕之后,但在实际窗口销毁之前,给窗口发送一个WM_DESTROY消息,这时候在处理WM_DESTROY的中可以调用PostQuitMessage(0),这个会触发给程序发送一个WM_QUIT消息,这样消息循环就退出了,一般来说,这时候程序也就退出了。

MSDN上的一个图

以上,如果你检查每个函数的返回值的话,会很容易发现问题并掌握知识点的。

还有一个帖子: What is the difference between WM_QUIT, WM_CLOSE, and WM_DESTROY in a windows program? 简而言之,WM_CLOSE会触发WM_DESTROY;WM_QUIT会推出程序,而不只是销毁窗口;所有子窗口都销毁之后,WM_NCDESTROY就会被发送。

PostMessage和GetMessage

测试下来,GetMessage只接受PostMessage过来的消息,SendMessage的消息直接被WndProc处理了。
参考 GetMessage function

从消息队列中取消息的顺序是:

  1. Sent messages
  2. Posted messages
  3. Input (hardware) messages and system internal events
  4. Sent messages (again)
  5. WM_PAINT messages
  6. WM_TIMER messages

什么是Shell Window

参考的一个代码中,在获取Magnification API屏蔽的窗口的时候,过滤掉了GetShellWindow返回的句柄,查了一下,这个Shell Window是Windows中包含桌面,任务栏,开始菜单,任务切换器等的GUI程序。参考:Windows shell。这样看就是所谓的桌面。

获取正确的Windows操作系统版本

void DetectWindowsVersion()
{
    OSVERSIONINFOEX* pOSversion = &osver_;

    // Function pointer to driver function
    NTSTATUS(WINAPI *pRtlGetVersion)(PRTL_OSVERSIONINFOW lpVersionInformation) = NULL;

    // load the System-DLL
    HINSTANCE hNTdllDll = LoadLibraryA("ntdll.dll");

    // successfully loaded?
    if (hNTdllDll != NULL)
    {
        // get the function pointer to RtlGetVersion
        pRtlGetVersion = (NTSTATUS(WINAPI *)(PRTL_OSVERSIONINFOW))
            GetProcAddress(hNTdllDll, "RtlGetVersion");

        // if successfull then read the function
        if (pRtlGetVersion != NULL)
            pRtlGetVersion((PRTL_OSVERSIONINFOW)pOSversion);

        // free the library
        FreeLibrary(hNTdllDll);
    } // if (hNTdllDll != NULL)

    // if function failed, use fallback to old version
    //if (pRtlGetVersion == NULL)
    //    GetVersionEx((OSVERSIONINFO*)pOSversion);

    /// TODO: need to do something (lgy)
}

参考:Operating System Version 查看操作系统的版本信息。

获取父窗口/Owner窗口

To obtain a window’s owner window, instead of using GetParent, use GetWindow with the GW_OWNER flag. To obtain the parent window and not the owner, instead of using GetParent, use GetAncestor with the GA_PARENT flag.

参考: GetParent function

有owner就有owned,owned窗口窗口有3个特点:

  1. 一定会位于owner上方
  2. owner销毁就会销毁
  3. owner窗口最小化,owned窗口会隐藏

参考:Owned Windows

获取进程

#include <cstdio>
#include <windows.h>
#include <tlhelp32.h>

int main( int, char *[] )
{
    PROCESSENTRY32 entry;
    entry.dwSize = sizeof(PROCESSENTRY32);

    HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL);

    if (Process32First(snapshot, &entry) == TRUE)
    {
        while (Process32Next(snapshot, &entry) == TRUE)
        {
            if (stricmp(entry.szExeFile, "target.exe") == 0)
            {  
                HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, entry.th32ProcessID);

                // Do stuff..

                CloseHandle(hProcess);
            }
        }
    }

    CloseHandle(snapshot);

    return 0;
}

// Also, if you'd like to use PROCESS_ALL_ACCESS in OpenProcess, you could try this:

#include <cstdio>
#include <windows.h>
#include <tlhelp32.h>

void EnableDebugPriv()
{
    HANDLE hToken;
    LUID luid;
    TOKEN_PRIVILEGES tkp;

    OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken);

    LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &luid);

    tkp.PrivilegeCount = 1;
    tkp.Privileges[0].Luid = luid;
    tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;

    AdjustTokenPrivileges(hToken, false, &tkp, sizeof(tkp), NULL, NULL);

    CloseHandle(hToken); 
}

int main( int, char *[] )
{
    EnableDebugPriv();

    PROCESSENTRY32 entry;
    entry.dwSize = sizeof(PROCESSENTRY32);

    HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL);

    if (Process32First(snapshot, &entry) == TRUE)
    {
        while (Process32Next(snapshot, &entry) == TRUE)
        {
            if (stricmp(entry.szExeFile, "target.exe") == 0)
            {  
                HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, entry.th32ProcessID);

                // Do stuff..

                CloseHandle(hProcess);
            }
        }
    }

    CloseHandle(snapshot);

    return 0;
}

参考:How can I get a process handle by its name in C++?

微软文档中还有获取所有线程,获取所有模块,获取所有heap的代码:Using the Tool Help Functions

获取当前窗口的大小(阴影窗口)

RECT rc;
if (FAILED(DwmGetWindowAttribute(captured_wnd_, DWMWA_EXTENDED_FRAME_BOUNDS, &rc, sizeof(RECT)))) {
    ::GetWindowRect(captured_wnd_, &rc);
}

ref: GetWindowRect returns a size including “invisible” borders,诚如这个帖子里面一个人说的,这个方法获取的数据不准。

操作系统主题:Visual Styles

更多信息参考:GetWindowRect function 的Remarks部分。

窗口是否最大化

webrtc中的代码

bool IsWindowMaximized(HWND window, bool* result) {
  WINDOWPLACEMENT placement;
  memset(&placement, 0, sizeof(WINDOWPLACEMENT));
  placement.length = sizeof(WINDOWPLACEMENT);
  if (!::GetWindowPlacement(window, &placement)) {
    return false;
  }

  *result = (placement.showCmd == SW_SHOWMAXIMIZED);
  return true;
}

std::bind vs lambda

Scott Meyers说C++14中没有std::bind什么事了:Why use std::bind over lambdas in C++14?

这篇文章介绍了lambda表达式与仿函数的异同:C++ Lambdas Under The Hood

Lambdas Part 2: Capture Lists and Stateful Closures

C++11 lambda implementation and memory model

Lambda vs Bind

获取多显示器分辨率

// 得到所有显示器句柄
std::vector<HMONITOR> monitors;
EnumDisplayMonitors(nullptr, nullptr, [](HMONITOR hmon, HDC, LPRECT, LPARAM lparam)
    {
        auto& monitors = *reinterpret_cast<std::vector<HMONITOR>*>(lparam);
        monitors.push_back(hmon);

        return TRUE;
    }, reinterpret_cast<LPARAM>(&monitors));
    

// 得到主显示器分辨率
RECT rc_max{ 0,0,1,1 };
SystemParametersInfo(SPI_GETWORKAREA, sizeof(RECT), &rc_max, 0);

// 得到窗口所在显示器的分辨率
HMONITOR cur = MonitorFromWindow(captured_wnd_, MONITOR_DEFAULTTONEAREST);
MONITORINFOEX minfo;
minfo.cbSize = sizeof(MONITORINFOEX);
GetMonitorInfo(cur, &minfo);
auto rc_work = minfo.rcWork;

Win7+系统窗口阴影控制

注册表中有记录的开关:Computer\HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Advanced

参考:Windows 7: Visual Effects Settings - Change

内存泄露

CRT的内存泄露检测,参考:Find memory leaks with the CRT library

使用Windbg检测内存泄露:Memory Leak Detection Using Windbg
基于WinDbg的内存泄漏分析

看一下微软的crt中new的实现,代码地址: C:\Program Files (x86)\Microsoft Visual Studio\2017\Professional\VC\Tools\MSVC\14.16.27023\crt
代码结构信息参考:Why is the Visual Studio runtime library source code stored in two directories?

VS自带的内存泄露检测工具:Measure memory usage in Visual Studio

Memory Leak Detection in Microsoft Visual Studio

How to check for memory leaks in Visual Studio?

Top 20+ Memory Leak Detection Tools For Java, C++ On Linux And Windows

介绍了几款工具:Finding memory leaks in a C++ application with Visual Studio

Deleaker

Visual Leak Detector

如何排查死锁:Debug Tutorial Part 7: Locks and Synchronization Objects

win7上使用取屏的方法触发了兼容性助手

右下角弹出框提示:

The color scheme has been changed to Windows 7 Basic
A running program isn't compatible with certain visual elements of Windows.
Click here for more infomation.

点开之后,提示内容:

[Window Title]
Windows

[Main Instruction]
The color scheme has been changed

[Content]
The following program has performed an action that requires Windows to temporarily change the color scheme to Windows 7 Basic.

Program: nertc_sdk_demo_d.exe
Publisher: <Unavailable>
Process identifier (PID): 3120

Windows will automatically change the color scheme back to Windows Aero when this program or other programs performing similar actions are no longer running.

[ ] Don't show me this again.  [OK]

[Footer]
Why are some visual elements being automatically turned off?

帮助:

Why are some visual elements being automatically turned off?

If you receive a message that some visual elements, such as window frame transparency, have been turned off, or if you receive a message that the theme has been changed to Basic theme, one of the following might have happened:

A program that you're running is incompatible with Aero themes. When this happens, some visual elements are automatically turned off. When the program is no longer running, the visual elements that were turned off are turned on again automatically.

Your laptop might be running low on battery power. Windows might turn off Aero themes or window transparency to save battery power.

Your computer's hardware configuration or screen resolution was changed. If you changed your screen resolution, video card, or monitor setup, your computer might no longer meet the minimum recommendations for running Aero themes.

Your computer does not have enough memory to run all of the programs that you have open and also run an Aero theme. If Windows automatically changed your theme to Basic theme, and you want to change it back to an Aero theme, close some windows to free up memory, and then follow the steps below.

To run the Aero troubleshooter
To try to get visual elements like transparency running again, use the Aero troubleshooter, which can find and fix problems automatically.

Click to open the Aero troubleshooter.  If you are prompted for an administrator password or confirmation, type the password or provide confirmation.

 To use an Aero theme
If the troubleshooter doesn't fix the problem, try changing the theme back to an Aero theme.

Click to open Personalization. 

Under Aero Themes, click a theme to apply it to your desktop.

通过再Windows 7 Home basic上下载一个第三方的Personalize的插件 Winaero,查看它位于(C:\Windows\Resources\Ease of Access Themes)的配置,可以看到配置信息(文件的格式参考微软的文档 Theme File Format):

classic:

; Copyright © Microsoft Corp.

[Theme]
; Windows Classic - IDS_THEME_DISPLAYNAME_CLASSIC
DisplayName=@%SystemRoot%\System32\themeui.dll,-2016
SetLogonBackground=0

; Computer - SHIDI_SERVER
[CLSID\{20D04FE0-3AEA-1069-A2D8-08002B30309D}\DefaultIcon]
DefaultValue=%SystemRoot%\System32\imageres.dll,-109

; UsersFiles - SHIDI_USERFILES
[CLSID\{59031A47-3F72-44A7-89C5-5595FE6B30EE}\DefaultIcon]
DefaultValue=%SystemRoot%\System32\imageres.dll,-123

; Network - SHIDI_MYNETWORK
[CLSID\{F02C1A0D-BE21-4350-88B0-7367FC96EF3C}\DefaultIcon]
DefaultValue=%SystemRoot%\System32\imageres.dll,-25

; Recycle Bin - SHIDI_RECYCLERFULL SHIDI_RECYCLER
[CLSID\{645FF040-5081-101B-9F08-00AA002F954E}\DefaultIcon]
Full=%SystemRoot%\System32\imageres.dll,-54
Empty=%SystemRoot%\System32\imageres.dll,-55

[Control Panel\Cursors]
Arrow=
Help=
AppStarting=
Wait=
NWPen=
No=
SizeNS=
SizeWE=
Crosshair=
IBeam=
SizeNWSE=
SizeNESW=
SizeAll=
UpArrow=
DefaultValue=Windows default
DefaultValue.MUI=@themeui.dll,-2043

[Control Panel\Desktop]
Wallpaper=
TileWallpaper=0
WallpaperStyle=10
Pattern=

[Control Panel\Desktop\WindowMetrics]

[Metrics]
CaptionFont=@themeui.dll,-2037
SmCaptionFont=@themeui.dll,-2038
MenuFont=@themeui.dll,-2039
StatusFont=@themeui.dll,-2040
MessageFont=@themeui.dll,-2041
IconFont=@themeui.dll,-2042

[VisualStyles]
Path=
ColorStyle=@themeui.dll,-854
Size=@themeui.dll,-2019
Transparency=0

[boot]
SCRNSAVE.EXE=

[MasterThemeSelector]
MTSM=DABJDKT

[Sounds]
; IDS_SCHEME_DEFAULT
SchemeName=@%SystemRoot%\System32\mmres.dll,-800

basic:

; Copyright © Microsoft Corp.

[Theme]
; Windows 7 - IDS_THEME_DISPLAYNAME_BASIC
DisplayName=@%SystemRoot%\System32\themeui.dll,-2014
SetLogonBackground=0

; Computer - SHIDI_SERVER
[CLSID\{20D04FE0-3AEA-1069-A2D8-08002B30309D}\DefaultIcon]
DefaultValue=%SystemRoot%\System32\imageres.dll,-109

; UsersFiles - SHIDI_USERFILES
[CLSID\{59031A47-3F72-44A7-89C5-5595FE6B30EE}\DefaultIcon]
DefaultValue=%SystemRoot%\System32\imageres.dll,-123

; Network - SHIDI_MYNETWORK
[CLSID\{F02C1A0D-BE21-4350-88B0-7367FC96EF3C}\DefaultIcon]
DefaultValue=%SystemRoot%\System32\imageres.dll,-25

; Recycle Bin - SHIDI_RECYCLERFULL SHIDI_RECYCLER
[CLSID\{645FF040-5081-101B-9F08-00AA002F954E}\DefaultIcon]
Full=%SystemRoot%\System32\imageres.dll,-54
Empty=%SystemRoot%\System32\imageres.dll,-55

[Control Panel\Cursors]
AppStarting=%SystemRoot%\cursors\aero_working.ani
Arrow=%SystemRoot%\cursors\aero_arrow.cur
Crosshair=
Hand=%SystemRoot%\cursors\aero_link.cur
Help=%SystemRoot%\cursors\aero_helpsel.cur
IBeam=
No=%SystemRoot%\cursors\aero_unavail.cur
NWPen=%SystemRoot%\cursors\aero_pen.cur
SizeAll=%SystemRoot%\cursors\aero_move.cur
SizeNESW=%SystemRoot%\cursors\aero_nesw.cur
SizeNS=%SystemRoot%\cursors\aero_ns.cur
SizeNWSE=%SystemRoot%\cursors\aero_nwse.cur
SizeWE=%SystemRoot%\cursors\aero_ew.cur
UpArrow=%SystemRoot%\cursors\aero_up.cur
Wait=%SystemRoot%\cursors\aero_busy.ani
DefaultValue=Windows Aero
DefaultValue.MUI=@main.cpl,-1020

[Control Panel\Desktop]
Wallpaper=%SystemRoot%\web\wallpaper\Windows\img0.jpg
TileWallpaper=0
WallpaperStyle=10
Pattern=

[VisualStyles]
Path=%ResourceDir%\Themes\Aero\Aero.msstyles
Composition=0
ColorStyle=NormalColor
Size=NormalSize
ColorizationColor=0x6B74B8FC
Transparency=1

[boot]
SCRNSAVE.EXE=

[MasterThemeSelector]
MTSM=DABJDKT

[Sounds]
; IDS_SCHEME_DEFAULT
SchemeName=@%SystemRoot%\System32\mmres.dll,-800

Win7和Win10上,Aero主题都位于 C:\Windows\Resources\Themes 下。

关于视觉样式参考:Visual Styles

所以,上面程序运行之后出现的提示可以被解释为:由于程序不支持Aero的某些特性,导致Windows不得已切换到Windows Basic模式;但也有可能是由于系统配置不够高,没有足够的资源运行程序,所以需要切换到低配的Windows Basic模式下运行。切换到basic模式之后就会少了一些Aero的特性,比如半透明效果。

所以,这个问题就是在于到底是Windows7系统性能不够,还是程序不支持Aero呢?

发现原因都不是上面提到的,原因是WebRTC中,有一个选项DesktopCaptureOptions::set_disable_effects,如果设置了true(默认就是true),那么就会在ScreenCapturerWinGdi::Start的时候关闭Aero(调用DwmEnableComposition(0)),并且在ScreenCapturerWinGdi析构的时候打开Aero(调用DwmEnableComposition(1)),并且也不管原来是什么主题设置,中间用户有没有重新设置主题。只要调用DesktopCaptureOptions::set_disable_effects(false)就可以屏蔽这个选项。

WebRTC中这样设置的原因是源于一个bug:crbug.com/124018。估计是由于现在Win7系统与硬件更加兼容了,没有这个问题了,但是WebRTC中这个默认设置没有修改一下,需要用户手动修改。

所以,也就是说调用DwmEnableComposition(0)会触发Win7的兼容性助手。

获取当前窗口DPI的一个方法,摘自webrtc

  HDC window_dc = GetWindowDC(window_);
  if (!window_dc) {
    RTC_LOG(LS_WARNING) << "Failed to get window DC: " << GetLastError();
    callback_->OnCaptureResult(Result::ERROR_TEMPORARY, nullptr);
    return;
  }

  DesktopSize window_dc_size;
  if (GetDcSize(window_dc, &window_dc_size)) {
    // The |window_dc_size| is used to detect the scaling of the original
    // window. If the application does not support high-DPI settings, it will
    // be scaled by Windows according to the scaling setting.
    // https://www.google.com/search?q=windows+scaling+settings&ie=UTF-8
    // So the size of the |window_dc|, i.e. the bitmap we can retrieve from
    // PrintWindow() or BitBlt() function, will be smaller than
    // |original_rect| and |cropped_rect|. Part of the captured desktop frame
    // will be black. See
    // bug https://bugs.chromium.org/p/webrtc/issues/detail?id=8112 for
    // details.

    // If |window_dc_size| is smaller than |window_rect|, let's resize both
    // |original_rect| and |cropped_rect| according to the scaling factor.
    const double vertical_scale =
        static_cast<double>(window_dc_size.width()) / original_rect.width();
    const double horizontal_scale =
        static_cast<double>(window_dc_size.height()) / original_rect.height();
    original_rect.Scale(vertical_scale, horizontal_scale);
    cropped_rect.Scale(vertical_scale, horizontal_scale);
  }

窗口重绘

控件自绘

窗口绘制:

要检测系统主题theme变化,参考:Detect system theme change in WPF
需要关注两个消息

  • WM_DWMCOMPOSITIONCHANGED
  • WM_THEMECHANGED

Raymond Chen在The Old New Thing上关于LockWindowUpdate和WM_SETREDRAW的文章:

想要禁用窗口重绘可以这样处理:

// Disable window updates
SendMessage(hWnd, WM_SETREDRAW, FALSE, 0);

// Perform your layout here
// ...

// Re-enable window updates
SendMessage(hWnd, WM_SETREDRAW, TRUE, 0);

设置一个窗口为layered window的代码 Using Layered Windows

// Set WS_EX_LAYERED on this window
SetWindowLong(hwnd,
    GWL_EXSTYLE,
    GetWindowLong(hwnd, GWL_EXSTYLE) | WS_EX_LAYERED);

// Make this window 100% alpha
SetLayeredWindowAttributes(hwnd, 0, 255, LWA_ALPHA);

【什么是画面撕裂?垂直同步,G-sync,Freesync到底有啥用?】

多显示器下,取到所有桌面的显示数据

参考 :multiple screen capture with MSDN library

使用VS几行代码为控件添加适配当前主题的样式

参考:Enabling Visual Styles

获取进程窗口缩略图

参考 Capturing Minimized Window: A Kid’s Trick

Capturing an image from a minimized window

DesktopWindow 与 Monitor的关系

GetDesktopWindow function (winuser.h)的说明中可以看出,DesktopWindow就是所有接到计算机上的显示器的拼接。

参考相关定义 The Virtual Screen

webrtc中获取所有显示设备的代码:

bool GetScreenList(DesktopCapturer::SourceList* screens,
                   std::vector<std::string>* device_names /* = nullptr */) {
  RTC_DCHECK_EQ(screens->size(), 0U);
  if (device_names) {
    RTC_DCHECK_EQ(device_names->size(), 0U);
  }

  BOOL enum_result = TRUE;
  for (int device_index = 0;; ++device_index) {
    DISPLAY_DEVICE device;
    device.cb = sizeof(device);
    enum_result = EnumDisplayDevices(NULL, device_index, &device, 0);

    // |enum_result| is 0 if we have enumerated all devices.
    if (!enum_result)
      break;

    // We only care about active displays.
    if (!(device.StateFlags & DISPLAY_DEVICE_ACTIVE))
      continue;

    screens->push_back({device_index, std::string()});
    if (device_names) {
      device_names->push_back(rtc::ToUtf8(device.DeviceName));
    }
  }
  return true;
}

使用的是EnumDisplayDevices,具体要通过一个从0开始的数字递增低取值。

Windows中可以设置显示器的扩展方式:

  1. 复制显示器
  2. 扩展显示器
  3. 只显示一个显示器

这些设置变化时,都会以 WM_DISPLAYCHANGE 消息的方式通知出来最新的设置。

Windows上将其他程序窗口置前

What are the differences between BringWindowToTop, SetForegroundwindow, SetWindowPos etc.?

Sharing an input queue takes what used to be asynchronous and makes it synchronous, like focus changes

枚举进程&枚举进程中加载的模块

Enumerating All Modules For a Process

Enumerating All Processes

Enumerating All Device Drivers in the System

Collecting Memory Usage Information For a Process

NVidia

Programmatically selecting integrated graphics in nVidia Optimus

How to programmatically get the voltage with Nvidia graphics card?

How to Fix Geforce Experience Black Screen [Fixed Completely]

某一个进程的内存使用情况统计信息

GetProcessMemoryInfo

Process Working Set

保存BMP格式图片

// 仅限于Windows
bool SaveImage(const std::string& szPathName, const std::vector<char>& lpBits, int w, int h) {
    // Create a new file for writing
    std::ofstream pFile(szPathName, std::ios_base::binary);
    if (!pFile.is_open()) {
        return false;
    }

    BITMAPINFOHEADER bmih;
    bmih.biSize = sizeof(BITMAPINFOHEADER);
    bmih.biWidth = w;
    bmih.biHeight = h;
    bmih.biPlanes = 1;
    bmih.biBitCount = 24;
    bmih.biCompression = BI_RGB;
    bmih.biSizeImage = w * h * 3;

    BITMAPFILEHEADER bmfh;
    int nBitsOffset = sizeof(BITMAPFILEHEADER) + bmih.biSize;
    LONG lImageSize = bmih.biSizeImage;
    LONG lFileSize = nBitsOffset + lImageSize;
    bmfh.bfType = 'B' + ('M' << 8);
    bmfh.bfOffBits = nBitsOffset;
    bmfh.bfSize = lFileSize;
    bmfh.bfReserved1 = bmfh.bfReserved2 = 0;

    // Write the bitmap file header
    pFile.write((const char*)&bmfh, sizeof(BITMAPFILEHEADER));
    UINT nWrittenFileHeaderSize = pFile.tellp();

    // And then the bitmap info header
    pFile.write((const char*)&bmih, sizeof(BITMAPINFOHEADER));
    UINT nWrittenInfoHeaderSize = pFile.tellp();

    // Finally, write the image data itself
    //-- the data represents our drawing
    pFile.write(&lpBits[0], lpBits.size());
    UINT nWrittenDIBDataSize = pFile.tellp();
    pFile.close();

    return true;
}

// 跨平台保存RGBA数据为bmp图片的方法
#include <fstream>
void SaveAsBmp(std::string path, int w, int h, uint8_t* pbits) {
    int filesize = 54 + 3*w*h;

    unsigned char bmpfileheader[14] = {'B','M', 0,0,0,0, 0,0, 0,0, 54,0,0,0};
    unsigned char bmpinfoheader[40] = {40,0,0,0, 0,0,0,0, 0,0,0,0, 1,0, 32,0};
    unsigned char bmppad[4] = {0,0,0,0};

    bmpfileheader[ 2] = (unsigned char)(filesize    );
    bmpfileheader[ 3] = (unsigned char)(filesize>> 8);
    bmpfileheader[ 4] = (unsigned char)(filesize>>16);
    bmpfileheader[ 5] = (unsigned char)(filesize>>24);

    bmpinfoheader[ 4] = (unsigned char)(       w    );
    bmpinfoheader[ 5] = (unsigned char)(       w>> 8);
    bmpinfoheader[ 6] = (unsigned char)(       w>>16);
    bmpinfoheader[ 7] = (unsigned char)(       w>>24);
    bmpinfoheader[ 8] = (unsigned char)(       h    );
    bmpinfoheader[ 9] = (unsigned char)(       h>> 8);
    bmpinfoheader[10] = (unsigned char)(       h>>16);
    bmpinfoheader[11] = (unsigned char)(       h>>24);

    FILE *f;
    f = fopen(path.c_str(),"wb");
    if (!f) {
        return;
    }
    auto ret = fwrite(bmpfileheader,1,14,f);
    ret = fwrite(bmpinfoheader,1,40,f);
    // directly save pbits will get bottom-to-up image.
    // fwrite(pbits, 1, w * h * 4, f);
    // reverse image data as row.
    for(int i=0; i<h; i++) {
        ret = fwrite(pbits+(w*(h-i-1)*4),4,w,f);
        ret = fwrite(bmppad,1,(4-(w*4)%4)%4,f);
    }
    fclose(f);
}

// from: https://blog.csdn.net/xiaolong1126626497/article/details/104861606
void YUV420P_to_RGB24(unsigned char *data, unsigned char *rgb, int width, int height)
{
    int index = 0;
    unsigned char *ybase = data;
    unsigned char *ubase = &data[width * height];
    unsigned char *vbase = &data[width * height * 5 / 4];
    for (int y = 0; y < height; y++) {
        for (int x = 0; x < width; x++) {
            //YYYYYYYYUUVV
            u_char Y = ybase[x + y * width];
            u_char U = ubase[y / 2 * width / 2 + (x / 2)];
            u_char V = vbase[y / 2 * width / 2 + (x / 2)];
            rgb[index++] = Y + 1.402 * (V - 128); //R
            rgb[index++] = Y - 0.34413 * (U - 128) - 0.71414 * (V - 128); //G
            rgb[index++] = Y + 1.772 * (U - 128); //B
        }
    }
}

Mac上保存图片到本地目录的话,又一点需要注意,必须开启app的本地路径访问权限,比如Downloads目录……

保存DX surface图像到本地

source code ref: https://github.com/Microsoft/graphics-driver-samples/blob/master/render-only-sample/rostest/util.cpp#L244


// DX11
void SaveTextureToBmp (PCWSTR FileName, ID3D11Texture2D* Texture)
{
    HRESULT hr;

    // First verify that we can map the texture
    D3D11_TEXTURE2D_DESC desc;
    Texture->GetDesc(&desc);

    // translate texture format to WIC format. We support only BGRA and ARGB.
    GUID wicFormatGuid;
    switch (desc.Format) {
    case DXGI_FORMAT_R8G8B8A8_UNORM:
        wicFormatGuid = GUID_WICPixelFormat32bppRGBA;
        break;
    case DXGI_FORMAT_B8G8R8A8_UNORM:
        wicFormatGuid = GUID_WICPixelFormat32bppBGRA;
        break;
    default:
        throw MyException::Make(
            HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED),
            L"Unsupported DXGI_FORMAT: %d. Only RGBA and BGRA are supported.",
            desc.Format);
    }

    // Get the device context
    ComPtr<ID3D11Device> d3dDevice;
    Texture->GetDevice(&d3dDevice);
    ComPtr<ID3D11DeviceContext> d3dContext;
    d3dDevice->GetImmediateContext(&d3dContext);

    // map the texture
    ComPtr<ID3D11Texture2D> mappedTexture;
    D3D11_MAPPED_SUBRESOURCE mapInfo;
    mapInfo.RowPitch;
    hr = d3dContext->Map(
            Texture,
            0,  // Subresource
            D3D11_MAP_READ,
            0,  // MapFlags
            &mapInfo);

    if (FAILED(hr)) {
        // If we failed to map the texture, copy it to a staging resource
        if (hr == E_INVALIDARG) {
            D3D11_TEXTURE2D_DESC desc2;
            desc2.Width = desc.Width;
            desc2.Height = desc.Height;
            desc2.MipLevels = desc.MipLevels;
            desc2.ArraySize = desc.ArraySize;
            desc2.Format = desc.Format;
            desc2.SampleDesc = desc.SampleDesc;
            desc2.Usage = D3D11_USAGE_STAGING;
            desc2.BindFlags = 0;
            desc2.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
            desc2.MiscFlags = 0;

            ComPtr<ID3D11Texture2D> stagingTexture;
            hr = d3dDevice->CreateTexture2D(&desc2, nullptr, &stagingTexture);
            if (FAILED(hr)) {
                throw MyException::Make(hr, L"Failed to create staging texture");
            }

            // copy the texture to a staging resource
            d3dContext->CopyResource(stagingTexture.Get(), Texture);

            // now, map the staging resource
            hr = d3dContext->Map(
                    stagingTexture.Get(),
                    0,
                    D3D11_MAP_READ,
                    0,
                    &mapInfo);
            if (FAILED(hr)) {
                throw MyException::Make(hr, L"Failed to map staging texture");
            }

            mappedTexture = std::move(stagingTexture);
        } else {
            throw MyException::Make(hr, L"Failed to map texture.");
        }
    } else {
        mappedTexture = Texture;
    }
    auto unmapResource = Finally([&] {
        d3dContext->Unmap(mappedTexture.Get(), 0);
    });

    ComPtr<IWICImagingFactory> wicFactory;
    hr = CoCreateInstance(
            CLSID_WICImagingFactory,
            nullptr,
            CLSCTX_INPROC_SERVER,
            __uuidof(wicFactory),
            reinterpret_cast<void**>(wicFactory.GetAddressOf()));
    if (FAILED(hr)) {
        throw MyException::Make(
            hr,
            L"Failed to create instance of WICImagingFactory");
    }

    ComPtr<IWICBitmapEncoder> wicEncoder;
    hr = wicFactory->CreateEncoder(
            GUID_ContainerFormatBmp,
            nullptr,
            &wicEncoder);
    if (FAILED(hr)) {
        throw MyException::Make(hr, L"Failed to create BMP encoder");
    }

    ComPtr<IWICStream> wicStream;
    hr = wicFactory->CreateStream(&wicStream);
    if (FAILED(hr)) {
        throw MyException::Make(hr, L"Failed to create IWICStream");
    }

    hr = wicStream->InitializeFromFilename(FileName, GENERIC_WRITE);
    if (FAILED(hr)) {
        throw MyException::Make(hr, L"Failed to initialize stream from file name");
    }

    hr = wicEncoder->Initialize(wicStream.Get(), WICBitmapEncoderNoCache);
    if (FAILED(hr)) {
        throw MyException::Make(hr, L"Failed to initialize bitmap encoder");
    }

    // Encode and commit the frame
    {
        ComPtr<IWICBitmapFrameEncode> frameEncode;
        wicEncoder->CreateNewFrame(&frameEncode, nullptr);
        if (FAILED(hr)) {
            throw MyException::Make(hr, L"Failed to create IWICBitmapFrameEncode");
        }

        hr = frameEncode->Initialize(nullptr);
        if (FAILED(hr)) {
            throw MyException::Make(hr, L"Failed to initialize IWICBitmapFrameEncode");
        }


        hr = frameEncode->SetPixelFormat(&wicFormatGuid);
        if (FAILED(hr)) {
            throw MyException::Make(
                hr,
                L"SetPixelFormat(%s) failed.",
                StringFromWicFormat(wicFormatGuid));
        }

        hr = frameEncode->SetSize(desc.Width, desc.Height);
        if (FAILED(hr)) {
            throw MyException::Make(hr, L"SetSize(...) failed.");
        }

        hr = frameEncode->WritePixels(
                desc.Height,
                mapInfo.RowPitch,
                desc.Height * mapInfo.RowPitch,
                reinterpret_cast<BYTE*>(mapInfo.pData));
        if (FAILED(hr)) {
            throw MyException::Make(hr, L"frameEncode->WritePixels(...) failed.");
        }

        hr = frameEncode->Commit();
        if (FAILED(hr)) {
            throw MyException::Make(hr, L"Failed to commit frameEncode");
        }
    }

    hr = wicEncoder->Commit();
    if (FAILED(hr)) {
        throw MyException::Make(hr, L"Failed to commit encoder");
    }
}

DirectX 9的代码参考: Generating Debug Bitmaps for DirectX,以及问题:How to capture the screen in DirectX 9 to a raw bitmap in memory without using D3DXSaveSurfaceToFile

替换IAT的相关代码

void CAPIHook::ReplaceIATEntryInAllMods(PCSTR pszCalleeModName, 
   PROC pfnCurrent, PROC pfnNew) {

   HMODULE hmodThisMod = ExcludeAPIHookMod 
      ? ModuleFromAddress(ReplaceIATEntryInAllMods) : NULL;

   // Get the list of modules in this process
   CToolhelp th(TH32CS_SNAPMODULE, GetCurrentProcessId());

   MODULEENTRY32 me = { sizeof(me) };
   for (BOOL bOk = th.ModuleFirst(&me); bOk; bOk = th.ModuleNext(&me)) {

      // NOTE: We don't hook functions in our own module
      if (me.hModule != hmodThisMod) {

         // Hook this function in this module
         ReplaceIATEntryInOneMod(
            pszCalleeModName, pfnCurrent, pfnNew, me.hModule);
      }
   }
}


void CAPIHook::ReplaceIATEntryInOneMod(PCSTR pszCalleeModName, 
   PROC pfnCurrent, PROC pfnNew, HMODULE hmodCaller) {

   // Get the address of the module's import section
   ULONG ulSize;

   // An exception was triggered by Explorer (when browsing the content of 
   // a folder) into imagehlp.dll. It looks like one module was unloaded...
   // Maybe some threading problem: the list of modules from Toolhelp might 
   // not be accurate if FreeLibrary is called during the enumeration.
   PIMAGE_IMPORT_DESCRIPTOR pImportDesc = NULL;
   __try {
      pImportDesc = (PIMAGE_IMPORT_DESCRIPTOR) ImageDirectoryEntryToData(
         hmodCaller, TRUE, IMAGE_DIRECTORY_ENTRY_IMPORT, &ulSize);
   } 
   __except (InvalidReadExceptionFilter(GetExceptionInformation())) {
      // Nothing to do in here, thread continues to run normally
      // with NULL for pImportDesc 
   }
   
   if (pImportDesc == NULL)
      return;  // This module has no import section or is no longer loaded


   // Find the import descriptor containing references to callee's functions
   for (; pImportDesc->Name; pImportDesc++) {
      PSTR pszModName = (PSTR) ((PBYTE) hmodCaller + pImportDesc->Name);
      if (lstrcmpiA(pszModName, pszCalleeModName) == 0) {

         // Get caller's import address table (IAT) for the callee's functions
         PIMAGE_THUNK_DATA pThunk = (PIMAGE_THUNK_DATA) 
            ((PBYTE) hmodCaller + pImportDesc->FirstThunk);

         // Replace current function address with new function address
         for (; pThunk->u1.Function; pThunk++) {

            // Get the address of the function address
            PROC* ppfn = (PROC*) &pThunk->u1.Function;

            // Is this the function we're looking for?
            BOOL bFound = (*ppfn == pfnCurrent);
            if (bFound) {
               if (!WriteProcessMemory(GetCurrentProcess(), ppfn, &pfnNew, 
                    sizeof(pfnNew), NULL) && (ERROR_NOACCESS == GetLastError())) {
                  DWORD dwOldProtect;
                  if (VirtualProtect(ppfn, sizeof(pfnNew), PAGE_WRITECOPY, 
                     &dwOldProtect)) {

                     WriteProcessMemory(GetCurrentProcess(), ppfn, &pfnNew, 
                        sizeof(pfnNew), NULL);
                     VirtualProtect(ppfn, sizeof(pfnNew), dwOldProtect, 
                        &dwOldProtect);
                  }
               }
               return;  // We did it, get out
            }
         }
      }  // Each import section is parsed until the right entry is found and patched
   }
}


void CAPIHook::ReplaceEATEntryInOneMod(HMODULE hmod, PCSTR pszFunctionName, 
   PROC pfnNew) {

   // Get the address of the module's export section
   ULONG ulSize;

   PIMAGE_EXPORT_DIRECTORY pExportDir = NULL;
   __try {
      pExportDir = (PIMAGE_EXPORT_DIRECTORY) ImageDirectoryEntryToData(
         hmod, TRUE, IMAGE_DIRECTORY_ENTRY_EXPORT, &ulSize);
   } 
   __except (InvalidReadExceptionFilter(GetExceptionInformation())) {
      // Nothing to do in here, thread continues to run normally
      // with NULL for pExportDir 
   }
   
   if (pExportDir == NULL)
      return;  // This module has no export section or is unloaded

   PDWORD pdwNamesRvas = (PDWORD) ((PBYTE) hmod + pExportDir->AddressOfNames);
   PWORD pdwNameOrdinals = (PWORD) 
      ((PBYTE) hmod + pExportDir->AddressOfNameOrdinals);
   PDWORD pdwFunctionAddresses = (PDWORD) 
      ((PBYTE) hmod + pExportDir->AddressOfFunctions);

   // Walk the array of this module's function names 
   for (DWORD n = 0; n < pExportDir->NumberOfNames; n++) {
      // Get the function name
      PSTR pszFuncName = (PSTR) ((PBYTE) hmod + pdwNamesRvas[n]);

      // If not the specified function, try the next function
      if (lstrcmpiA(pszFuncName, pszFunctionName) != 0) continue;

      // We found the specified function
      // --> Get this function's ordinal value
      WORD ordinal = pdwNameOrdinals[n];

      // Get the address of this function's address
      PROC* ppfn = (PROC*) &pdwFunctionAddresses[ordinal];
      
      // Turn the new address into an RVA
      pfnNew = (PROC) ((PBYTE) pfnNew - (PBYTE) hmod);

      // Replace current function address with new function address
      if (!WriteProcessMemory(GetCurrentProcess(), ppfn, &pfnNew, 
         sizeof(pfnNew), NULL) && (ERROR_NOACCESS == GetLastError())) {
         DWORD dwOldProtect;
         if (VirtualProtect(ppfn, sizeof(pfnNew), PAGE_WRITECOPY, 
            &dwOldProtect)) {
            
            WriteProcessMemory(GetCurrentProcess(), ppfn, &pfnNew, 
               sizeof(pfnNew), NULL);
            VirtualProtect(ppfn, sizeof(pfnNew), dwOldProtect, &dwOldProtect);
         }
      }
      break;  // We did it, get out
   }
}

WGC 资料

WGC分享时,目标窗口有一个难看的黄色边框

参考这里 GraphicsCaptureSession.IsCursorCaptureEnabled Property,微软的这个边框高亮的实现,看网上被骂了很久之后才考虑添加控制的。因此需要较高版本的Windows SDK。需要Windows环境升级到 Build 20384 版本以上。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值