Debug Mlt Framework in VSCode

Debugging Mlt framework in VSCode

Crossing platform with built in tool chain

Coming up soon (maybe)

评论

Gitsoler

Gitsoler 1.1 - Split View supported: https://marketplace.visualstudio.com/items?itemName=gandalfliang.gitsoler

评论

【翻译】关于 WPF 透明窗口的内存占用

翻译自己的文章才是最骚的。。。 Origin Post


要实现一个透明的 WPF 窗口?
多么简单的一个任务啊!只要设置 AllowTransparency 和 WindowStyle,你可以在毫秒间完成这个任务。

AllowTransparency="True" WindowStyle="None" Background="Transparent"

正确吧?当然。
但是(你懂得),打开你的任务管理器看看,简单的任务通常会带来大量的内存占用,特别是4K分辨率的透明窗口。100+ MB的内存被浪费了,就只为了显示一个空白的透明窗口!这是不可接受的。一年前,如果你说:”谁关心内存呀,现在的内存条太便宜了”,你可能是对的。但是查查这一年内存条的价格走向,它们现在贵上天了。

WPF 透明窗口的有趣小真相

  • 内存占用随着窗口尺寸增大而增加
  • Win32 窗口没有这样的问题

等等,什么?窗口越大,内存消耗的更多?嗯。。。这看起来很熟悉嘛,就像一个Bitmap。知道现在,我们并不知道 WPF 是如何处理透明窗口的,但是这种症状显示它就好像直接将整个桌面作为一个位图,然后窗口用这张位图的重叠部分作为其背景来更新自己,让它看起来是“透明”的。多么聪明的做法呀。。。
在 WPF 刚刚发布的那些日子里,低分辨率的计算机屏幕占据主流位置,即使在今天,大多数的笔记本电脑依然带着一块1366*768分辨率的屏幕被推向市场(离谱吧)。让我们唾弃那些OEM厂商讲的毫无根据的废话并且思考一下运行在高分辨率下的程序的情况。

内存并不是免费的,不要浪费之

很显然,浪费100+MB的内存来显示一个4K的透明窗口是不可接受的,特别是和 Win32 窗口只占用10+MB的内存进行比较时。这差距让 WPF 看起来蠢透了。
抱怨已经够多了,想想对此我们能做什么呢?我可不想用C++和GDI将我的UI代码重写一遍,这太没效率并且也跟不上时代,况且,没人会为此“区区小事”去放弃他们漂亮、易于维护的Xaml UI代码。

使用 Win32 承载 WPF 内容

好吧,确实,没人愿意为了区区90MB内存去重写它们的UI。与使用C++重写UI所耗费的精力相比,这个内存的占用差距看起来是可以接受的(#笑脸)。但是请记住,我们一如既往的可以在 Win32 窗口中承载 WPF 的内容。
例如,我们想创建一个全屏、半透明背景带着非透明内容的对话框。为了规避 WPF 透明窗口的内存问题,我们可以使用 Win32 创建一个半透明的窗口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
DWORD Flags1 = WS_EX_LAYERED;
DWORD Flags2 = WS_POPUP;

HWND hWnd = CreateWindowEx(Flags1,szWindowClass, szTitle, Flags2,
CW_USEDEFAULT, 0, 3840,2160, nullptr, nullptr, hInstance, nullptr);

SetLayeredWindowAttributes(hWnd, RRR, (BYTE)125, LWA_ALPHA);
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);

case WM_ERASEBKGND:
RECT rect;
GetClientRect(hWnd, &rect);
FillRect((HDC)wParam, &rect, CreateSolidBrush(RGB(0, 0, 0)));
break;

通过启用 C++/CLI,我们可以直接访问 WPF 内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
namespace ManagedCode
{
using namespace System;
using namespace System::Windows;
using namespace System::Windows::Interop;
using namespace System::Windows::Media;

HWND GetHwnd(HWND parent, int x, int y, int width, int height) {
HwndSource^ source = gcnew HwndSource(
0, // class style
WS_VISIBLE | WS_CHILD, // style
0, // exstyle
x, y, width, height,
"hi", // NAME
IntPtr(parent) // parent window
);

UIElement^ page = gcnew ManagedContent::WPFContent();
source->RootVisual = page;
return (HWND)source->Handle.ToPointer();
}
}

最后

1
2
//managed content
ManagedCode::GetHwnd(hWnd, 0, 0, 200, 200);

由于 WPF 和 GDI 背后的技术不尽相同,还有更多的工作需要做来解决不可避免的透明通道问题,但是,为了方便,你始终可以使用 Popup 来实现你的目标。


Macbook Pro 2016 的键盘真垃圾。。。。。。

评论

Light Weighted DropshadowEffect

Let’s create a light weighted wpf drop shadow effect, considering that the origin one performs badly in some special occasions.
As I mentioned before (check it out), the original WPF DropShadow Effect can cause severe preformance problem. Due to the “flaw” M$FT brought to the HLSL support for WPF, the Effect class that implements the visual effect creates and destroy GPU resource each frame, which is the worst thing you could do with GPU resources. So, what about implementing a custom shadow effect to avoid it? This sounds interesting.

– 春节补充

评论

关于 WPF 透明窗口的内存占用

要实现一个透明的 WPF 窗口?
What an easy task! By setting AllowTransparency and WindowStyle, you could finish it in seconds.

AllowTransparency="True" WindowStyle="None" Background="Transparent"

Correct? Of course.

However (you know this is coming), look at your task manager, easy task comes with large memory consumption, especially for 4K transparent window. 100+ MB ram are wasted for just showing an empty, transparent window! That’s unacceptable. A year ago, you might be right saying “Who cares about RAM, they are cheap as hell”, but check out the price they’ve grown over this year, they are expensive as hell now.

Fun fact of WPF transparent window

  • RAM usage increase as window size enlarge
  • Win32 window has no such problem

Wait, what? The larger the window is, the more RAM it consumes? Hmmmmm… this looks familiar, just like a Bitmap. For now, we don’t know how WPF handles transparent window, but the symptom shows that it’s like using the whole screen as a bitmap and the window updating itself with portion of that bitmap, making it “transparent”. What a smart move…
Back in the days when WPF was first released, low screen resolution was the main stream. Even today, most laptops still are shipping with a monitor of 1366*768 (ridiculous, right?). Let’s despise the nonsense the OEM told us and think about program running in computers with higher screen resolution.

RAM is not free, do not waste it

Obviously, costing 100+ mb of ram for showing a transparent window in 4K is unacceptable, especially compared with Win32 transparent window, which costs only 10+ mb. The gap between them makes WPF look dump.
Enough complaining, what can we do about it? I don’t want to write UI code with GDI using C++, that’s inefficient and not modern, plus, no one would abandon their beautiful, easy to maintain xaml UI code for this.

Hosting WPF content in Win32 Window

Well, indeed, no one would rewrite their UI code for just about 90mb of RAM. Compared with the work needed to rewrite C++ UI code, the RAM consumption gap seems acceptable (#smile face). But please remember, we can always host WPF content in win32 window.
Let say, we want to create a full screen notification dialog with semi-transparent background and apaque notication content in the center. To avoid the WPF ram problem, we create a semi-transparent window using win32:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
DWORD Flags1 = WS_EX_LAYERED;
DWORD Flags2 = WS_POPUP;

HWND hWnd = CreateWindowEx(Flags1,szWindowClass, szTitle, Flags2,
CW_USEDEFAULT, 0, 3840,2160, nullptr, nullptr, hInstance, nullptr);

SetLayeredWindowAttributes(hWnd, RRR, (BYTE)125, LWA_ALPHA);
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);

case WM_ERASEBKGND:
RECT rect;
GetClientRect(hWnd, &rect);
FillRect((HDC)wParam, &rect, CreateSolidBrush(RGB(0, 0, 0)));
break;

By enabling C++/CLI, we can access WPF content directly

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
namespace ManagedCode
{
using namespace System;
using namespace System::Windows;
using namespace System::Windows::Interop;
using namespace System::Windows::Media;

HWND GetHwnd(HWND parent, int x, int y, int width, int height) {
HwndSource^ source = gcnew HwndSource(
0, // class style
WS_VISIBLE | WS_CHILD, // style
0, // exstyle
x, y, width, height,
"hi", // NAME
IntPtr(parent) // parent window
);

UIElement^ page = gcnew ManagedContent::WPFContent();
source->RootVisual = page;
return (HWND)source->Handle.ToPointer();
}
}

and finally

1
2
//managed content
ManagedCode::GetHwnd(hWnd, 0, 0, 200, 200);

Due to the different technologies behind WPF and GDI, more work needed to be done for the unavoidable alpha blending problem, but, you can use wpf popup to achieve your goal for short.

评论

使用 OpenCL 实现图片高斯模糊

高斯模糊( https://zh.wikipedia.org/wiki/%E9%AB%98%E6%96%AF%E6%A8%A1%E7%B3%8A )是一种常见的图像处理算法,使用高斯分布与图像做卷积,得到模糊的效果。其二维定义:

σ是正态分布的标准偏差。在应用的时候,假设σ为2.5。对于模糊半径为1,则高斯矩阵为3*3的一个矩阵,以[1,1]为中心,带入公式计算高斯矩阵的值,得到:

0.0216996633 0.0235069655 0.0216996633
0.0235069655 0.0254647918 0.0235069655
0.0216996633 0.0235069655 0.0216996633

他们的和为 0.206291318,我们需要他们的和为1,因此与总和相除得到:

0.105189413 0.113950342 0.105189413
0.113950342 0.123440929 0.113950342
0.105189413 0.113950342 0.105189413

根据这个矩阵,对图像的每个像素点进行计算,计算的九个点的各rgb分量之和就是最终像素的rgb分量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
//计算高斯矩阵
private void ComputeWeightMatrix()
{
var center = Radius;
var conBase = 2 * Math.Pow(Variance, 2);
var conRoot = 1 / (Math.PI * conBase);

float sum = 0f;
for (int x = -Radius; x <= Radius; x++)
{
for (int y = Radius; y >= -Radius; y--)
{
var weight = conRoot * Math.Pow(Math.E, -(x * x + y * y) / conBase);
_matrix[GridPosToArrayIndex(x, y, center, Radius)] = (float)weight;
sum += (float)weight;
}
}
for (int i = 0; i < _matrix.Length; i++)
{
_matrix[i] /= sum;
}
}

//Compute
public void Compute(string imageFile)
{
using (var bitmap = new Bitmap(imageFile))
{
var datas = bitmap.LockBits(new Rectangle(new Point(), new Size(bitmap.Width, bitmap.Height)),ImageLockMode.ReadOnly,bitmap.PixelFormat);
var dataSize = datas.Stride * datas.Height;
var argbs = new byte[dataSize];
var dsts = new byte[dataSize];
int matrixWidth = Radius * 2 + 1;
Marshal.Copy(datas.Scan0, argbs, 0, dataSize);

Stopwatch sw=Stopwatch.StartNew();
for (int y = 0; y < bitmap.Height; y++)
{
for (int x = 0; x < bitmap.Width; x++)
{
float sumA = 0;
float sumR = 0;
float sumG = 0;
float sumB=0;
for (int i = 0; i < _matrix.Length; i++)
{
var pos = transform_pos(x, y, matrixWidth, bitmap.Width, bitmap.Height, Radius, i);
var position = pos.Y * datas.Stride + pos.X*4;
sumR += argbs[position] * _matrix[i];
sumG += argbs[position + 1] * _matrix[i];
sumB += argbs[position + 2] * _matrix[i];
sumA += argbs[position + 3] * _matrix[i];
}
var dstPos = y * datas.Stride + x * 4;
dsts[dstPos] = (byte)sumR;
dsts[dstPos+1] = (byte)sumG;
dsts[dstPos+2] = (byte)sumB;
dsts[dstPos+3] = (byte)sumA;
}
}
bitmap.UnlockBits(datas);

var elapse = sw.Elapsed;
Console.WriteLine($"Costing: {elapse}");
Debug.WriteLine($"Costing: {elapse}");

var handle = GCHandle.Alloc(dsts, GCHandleType.Pinned);
using (var dstBmp = new Bitmap(datas.Width, datas.Height, datas.Stride, bitmap.PixelFormat,
handle.AddrOfPinnedObject()))
{
dstBmp.Save("processed_normal.bmp");
}

handle.Free();
}
}

当然,这样能完成工作,但是耗时太长,对于3000*1920尺寸的图片处理需要2分51秒(Intel Core i7-4770),这显然是不可接受的。
对于这种分别计算每个像素,且各像素间互不干扰的问题,使用OpenCL可以大幅降低时间消耗。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
/*
OpenCL 高斯模糊代码
Copyright Gandalfliang
*/
inline int2 transform_pos(int centerX,int centerY,int matrixWidth,int radius,int index)
{
int x=index%matrixWidth;
int offsetX=x-(radius+1);
int y=index/matrixWidth;
int offsetY=radius-y;
return (int2)(centerX+offsetX,centerY-offsetY);
};

const sampler_t sampler_img=CLK_NORMALIZED_COORDS_FALSE|CLK_ADDRESS_CLAMP_TO_EDGE;

//opencl kernel 代码
kernel void gaussian_blur(
read_only image2d_t src,
global write_only char* dst,
global read_only float* matrix,
read_only int radius,
read_only int width)
{
int x=get_global_id(0);
int y=get_global_id(1);

float sumR,sumG,sumB,sumA;
int matrixWidth=radius*2+1;
int matrix_size=pow(matrixWidth,2);
for(int i=0;i<matrix_size;i++)
{
int2 pix=transform_pos(x,y,matrixWidth,radius,i);
uint4 rgba = read_imageui(src,sampler_img,pix);
sumR+=rgba.x*matrix[i];
sumG+=rgba.y*matrix[i];
sumB+=rgba.z*matrix[i];
sumA+=rgba.w*matrix[i];
}

int loc=y*width*4+x*4;
dst[loc]=sumR;
dst[loc+1]=sumG;
dst[loc+2]=sumB;
dst[loc+3]=sumA;
}

Host代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
public void Compute_cl(string imageFile)
{
//选取设备
var platform = ComputePlatform.Platforms.FirstOrDefault();
var device = platform.Devices.FirstOrDefault();
//设置相关上下文
var properties = new ComputeContextPropertyList(platform);
var context = new ComputeContext(new[] {device}, properties, null, IntPtr.Zero);
//命令队列,用于控制执行的代码
ComputeCommandQueue commands = new ComputeCommandQueue(context, context.Devices[0],
ComputeCommandQueueFlags.None);
//读取opencl代码
var code = File.ReadAllText(@"gaussianblur.cl");
//编译
var program = new ComputeProgram(context, code);
try
{
program.Build(new[] {device}, null, null, IntPtr.Zero);
}
catch (Exception ex)
{
throw;
}

var images = CreateImageFromBitmap(imageFile, context,
ComputeMemoryFlags.ReadWrite | ComputeMemoryFlags.CopyHostPointer);

//创建核心代码,就是cl代码中以kernel标识的函数
var kernel = program.CreateKernel("gaussian_blur");
//矩阵规模
//储存计算结果的数组

//创建的核心代码函数以这种方式来传参
var resultBuffer=new ComputeBuffer<char>(context,ComputeMemoryFlags.WriteOnly, dstBytes.Length);
kernel.SetMemoryArgument(0, images);
kernel.SetMemoryArgument(1, resultBuffer);
kernel.SetMemoryArgument(2, new ComputeBuffer<float>(context,ComputeMemoryFlags.ReadOnly|ComputeMemoryFlags.CopyHostPointer,_matrix));
kernel.SetValueArgument(3, Radius);
kernel.SetValueArgument(4, (int)images.Width);
Console.WriteLine($"运行平台: {platform.Name}\n运行设备: {device.Name}\n");
Stopwatch sw = Stopwatch.StartNew();
var climg = images;

//执行代码
commands.Execute(kernel, null, new long[] {climg.Width, climg.Height}, null, null);

//read data
char[] resultArray = new char[dstBytes.Length];
var arrHandle = GCHandle.Alloc(resultArray, GCHandleType.Pinned);
commands.Read(resultBuffer, true, 0, dstBytes.Length, arrHandle.AddrOfPinnedObject(), null);
//commands.ReadFromImage(images.Item2, processeddata.Scan0, true, null);

var resultHandle = GCHandle.Alloc(resultArray, GCHandleType.Pinned);
var bmp=new Bitmap(climg.Width,climg.Height, climg.Width*4, PixelFormat.Format32bppArgb, resultHandle.AddrOfPinnedObject());
var elapsed = sw.Elapsed;
Console.WriteLine($"耗时: {elapsed.TotalMilliseconds} ms\n");
kernel.Dispose();

bmp.Save("processed_cl.bmp");
}

相同尺寸的图片处理,使用 Intel Core i7-4770 自带的核显 HD4600 处理,耗时只需要164毫秒。

以下是相关测试结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
运行平台: Intel(R) OpenCL
运行设备: Intel(R) HD Graphics 4600
处理图片尺寸:790*501
OpenCL处理耗时: 13.6597 ms

处理图片尺寸:790*501
常规方法耗时: 11482.9402 ms

运行平台: Intel(R) OpenCL
运行设备: Intel(R) HD Graphics 4600
处理图片尺寸:1339*693
OpenCL处理耗时: 33.0095 ms

处理图片尺寸:1339*693
常规方法耗时: 26908.9926 ms

运行平台: Intel(R) OpenCL
运行设备: Intel(R) HD Graphics 4600
处理图片尺寸:1920*1080
OpenCL处理耗时: 51.3885 ms

处理图片尺寸:1920*1080
常规方法耗时: 60147.3815 ms

当然,常规方法都只使用了单线程,还未发挥多核CPU的威力,然而,可以预见的是,即使是使用多线程,提升也是有限的。

原图:

高斯模糊:

代码: https://github.com/gandalfliang/cloo_netstandard/tree/temp

Update: 在nVidia的环境下会导致处理后的图片出现花屏现象,估计是cl代码的问题,又或者是nVidia的驱动有问题?下次再更新
评论

.NET Standard CLOO

鉴于 .NET Standard 2.0 已经支持大量的.NET api,移植CLOO已经是毫无难度的一件事情 Github

CLOO使用p/invoke方式调用opencl api,但是对于不同平台下,opencl 的名称并不一致,例如在linux下为libOpenCL.so,Windows下为OpenCL.dll,且 .NET Standard 没有提供 Mono 类似的 dllmap 模式,因此,现在来说还不能达到用一个package,在所有平台引用的目的。

评论

Gitsoler

Gitsoler - a Visual Studio extension, now goes public: https://marketplace.visualstudio.com/items?itemName=gandalfliang.gitsoler

评论

“阻断疗法” - 拯救 WPF 启动过程中发生设备热插拔导致触摸失效问题


如果你在WPF程序启动过程中进行设备热插拔(例如,插入一个U盘,一个USB摄像头),那么你的WPF程序很有可能失去所有触摸消息响应,通过 Tablet.TabletDevices.Count 检查当前程序的挂靠触摸设备,发现为0。有趣的是,如果你将触摸线重新插拔后,程序恢复正常。所以,这是WPF的Bug,微软的锅。那么这个锅的根本原因是啥?有兴趣的可以调试 .net framework 源码,这里没有深究。
如上面讲到,触摸线重新插拔就可以解决这个问题,但是,导致这个问题的热插拔设备也不是触摸设备啊,只是一个普通的U盘,反过来想,如果导致问题的不是触摸设备热插拔,反而触摸设备的热插拔能够修复这个问题,那我能不能“模拟”一下触摸设备的热插拔事件呢?在这篇文章里描述怎样模拟触摸设备移除事件来达到禁用WPF触摸的效果,反过来试试,通过 OnTabletAdded 事件看看能不能发生奇迹。然而,奇迹并没有发生,所以这个方法不行。
既然模拟设备添加事件的方法不行,那我从源头阻挡这个问题的发生:启动过程中不要处理设备变动事件。那么问题来了,我想要阻断 win32 WM 事件通知,必须要拿到一个窗口句柄呀,但是在 mainwindow show 出来的时候,这个问题已经发生了,这个时候的阻断已经没有效果了,一定要程序启动一开始做阻断。进一步搜索,这里https://stackoverflow.com/questions/38642479/how-to-disable-wpf-tablet-support-in-surface-4-pro 是一个突破口:

WPF does not register to these messages on the applications MainWindow, but through a hidden windows named “SystemResources…” which is created for each application instance. So handling those messages on the MainWindow (which would be easy) does not help here.

相信看到这里,聪明的你已经知道怎么做了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
public class WPFTouchUtil
{
//添加钩子,阻断设备改动消息
public static void HandleDeviceChangedWM()
{
// hook into internal class SystemResources to keep it from updating the TabletDevices on system events
object hwndWrapper = GetSystemResourcesHwnd();
if (hwndWrapper != null)
{
// invoke hwndWrapper.AddHook( .. our method ..)
var internalHwndWrapperType = hwndWrapper.GetType();
// if the delegate is already set, we have already added the hook.
if (_handleAndHideMessageDelegate == null)
{
// create the internal delegate that will hook into the window messages
// need to hold a reference to that one, because internally the delegate is stored through a WeakReference object
var internalHwndWrapperHookDelegate = internalHwndWrapperType.Assembly.GetType("MS.Win32.HwndWrapperHook");
var handleAndHideMessagesHandle = typeof(WPFTouchUtil).GetMethod(nameof(HandleAndHideMessages), BindingFlags.Static | BindingFlags.NonPublic);
_handleAndHideMessageDelegate = Delegate.CreateDelegate(internalHwndWrapperHookDelegate, handleAndHideMessagesHandle);
// add a delegate that handles WM_TABLET_ADD
internalHwndWrapperType.InvokeMember("AddHook",
BindingFlags.InvokeMethod | BindingFlags.Instance | BindingFlags.Public,
null, hwndWrapper, new object[] { _handleAndHideMessageDelegate });
}
}
}

//移除钩子,恢复状态
public static void RestoreDeviceChangedWM()
{
object hwndWrapper = GetSystemResourcesHwnd();
if (hwndWrapper != null)
{
var internalHwndWrapperType = hwndWrapper.GetType();

internalHwndWrapperType.InvokeMember("RemoveHook",
BindingFlags.InvokeMethod | BindingFlags.Instance | BindingFlags.Public,
null, hwndWrapper, new object[] {_handleAndHideMessageDelegate});
}
}

private static Delegate _handleAndHideMessageDelegate = null;

private static object GetSystemResourcesHwnd()
{
var internalSystemResourcesType = typeof(Application).Assembly.GetType("System.Windows.SystemResources");

// get HwndWrapper from internal property SystemRessources.Hwnd;
var hwndWrapper = internalSystemResourcesType.InvokeMember("Hwnd",
BindingFlags.GetProperty | BindingFlags.Static | BindingFlags.NonPublic,
null, null, null);
return hwndWrapper;
}

private static IntPtr HandleAndHideMessages(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
if (msg == (int)WindowMessage.WM_DEVICECHANGE)
{
handled = true;
}
return IntPtr.Zero;
}

enum WindowMessage : int
{
WM_DEVICECHANGE = 0x0219
}
}

在程序刚启动的时候添加“阻断”,启动流程过后,不要忘了恢复状态。
缺陷,如果程序启动过程中,真的发生了触摸设备变动,也会被阻断。

评论

Timelapse#2

Lens: Nikkor 24-70mm F2.8
f2.8 70mm ISO400

评论