分类: C/C++
2008-04-23 22:01:58
C At Work 专栏...
禁用屏幕拷贝(Print Screen),调用派生的析构函数及其它......
原著:Paul DiLascia
翻译:,
下载源代码: (273KB)
原文出处:
我正在开发一个显示图形的程序,该图形具有知识产权,有没有什么办法禁用屏幕拷贝功能(Print Screen)以防止用户将图像拷贝到剪贴板上?
禁止屏幕拷贝的办法倒是有一个,但是我得告诉你,要阻止其它应用程序从你的窗口上复制像素内容是不可能的。许多第三方程序都能捕获屏幕内容,这种程序也不难写。要想截获屏幕上的像素,你只要用 BitBlt 从屏幕设备上下文中拷贝它们既可,例如:
CWindowDC dc(NULL); // 用 NULL 获取整个屏幕 CDC memdc; ... // 创建, 初始化 memdc memdc.BitBlt(..., &dc); // 拷贝屏幕内容若要复制当前活动窗口的内容,只要获取该窗口的 CWnd 指针,然后用它来构造一个 CWindowDC,即可从中提取内容。
// 热键的处理方法上述代码段展示了一个典型的 MFC CMainFrame 类实现。OnCreate/OnDestroy 函数用来注册/注销 IDHOT_SNAPDESKTOP 热键;OnActivate 函数用来在应用程序 处于激活/和非激活状态时注册/注销 IDHOT_SNAPWINDOW 热键。当你的窗口处于非激活状态时,通过重新启用 IDHOT_SNAPWINDOW,当别的应用程序拥有焦点时,用户仍然能用 Alt Print Screen 来复制屏幕。
// MainFrame.h #include "FolderFrame.h" #include "resource.h" //////////////// // Typical MFC Main frame window, override to disable PrintScreen. // class CMainFrame : public CFrameWnd { protected: ... afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct); // disable PrintScreen afx_msg void OnActivate(UINT nState, CWnd* pWndOther, BOOL bMinimized); afx_msg LRESULT OnHotKey(WPARAM wp, LPARAM lp); afx_msg void OnDestroy(); DECLARE_MESSAGE_MAP() }; MainFrame.cpp #include "StdAfx.h" #include "MainFrm.h" #include "View.h" IMPLEMENT_DYNCREATE(CMainFrame, CFrameWnd) BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd) ... // disable PrintScreen: ON_WM_CREATE() ON_WM_DESTROY() ON_WM_ACTIVATE() ON_MESSAGE(WM_HOTKEY, OnHotKey) END_MESSAGE_MAP() ... int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct) { ... RegisterHotKey(m_hWnd, IDHOT_SNAPDESKTOP, 0, VK_SNAPSHOT); return 0; } void CMainFrame::OnDestroy() { UnregisterHotKey(m_hWnd, IDHOT_SNAPDESKTOP); } ////////////////// // Handle hotkey: should be PrintScreen or Alt-PrintScreen. // Do nothing (bypass Windows screen capture) // LRESULT CMainFrame::OnHotKey(WPARAM wp, LPARAM) { UNREFERENCED_PARAMETER(wp); return 0; // ignore } ////////////////// // When window is activated/deactivated, disable/enable Alt-PrintScreen. // (IDHOT_SNAPWINDOW) // void CMainFrame::OnActivate(UINT nState, CWnd* pWndOther, BOOL bMinimized) { CFrameWnd::OnActivate(nState, pWndOther, bMinimized); if (nState) RegisterHotKey(m_hWnd, IDHOT_SNAPWINDOW, MOD_ALT, VK_SNAPSHOT); else UnregisterHotKey(m_hWnd, IDHOT_SNAPWINDOW); }
在 2004 年 11 月的专栏中,你谈到了在托管和非托管代码中调用虚拟函数的问题,参见“调用虚拟函数,持续化视图状态,POD
类型概念”。在 C 里,如果我想让派生类的析构函数在释放内存时被调用,我得在基类中将其声明为虚拟函数。那么在 Visual Studio 2005
中,对于某个派生类来说,即使它在基类中未被声明为虚拟的,其析构也会被调用吗?
如果你说的是托管类,那么它是成立的。如果该类是本地类,则标准 C
规则适用;如果它是托管类,则析构函数隐含为虚拟。理解析构函数行为最简单的方法是写点代码看看编译器对它做了些什么。Figure 2
示范了一个简单的托管控制台程序,该程序声明了两个托管类,CBase 和 CDerived。构造函数和析构函数用 printf
显示被调用时的诊断信息。如果用 /clr 编译此程序,你会在控制台窗口看到如下的信息:ctor: CBase ctor: CDerived dtor: CDerived dtor: CBase
这个信息说明了即使派生类和基类都不把析构函数声明为虚拟,派生类的析构都会被调用。构造函数和析构函数按期望的顺序被调用,先调用基类构造函数,最后是析构函数。
为什么要将托管析构函数声明为虚拟的呢?回想一下每一个托管类要么必须显式地从另一个托管类派生,要么隐式地从根基类 Object
派生。还要记住的是 C 编译器将托管析构转换为 Finalize 方法,该方法在 Object 类中是虚拟的。为了明白这一点,你只要用
ILDASM 反汇编器察看一下所编译的代码既可。

Figure 3 反汇编后的 vdtor.exe
Figure 3 展示了 vdtor.cpp 的反汇编代码。CBase 和 CDerived 都有 Finalize 方法;Figure 4
是派生类 CDerived 的 Finalize 方法。它还说明了编译器为每个类创建了一个特殊的 __dtor 方法。该方法在你调用
delete 时被调用。如果你检查一下主入口函数 main 的微软中间语言(IL)代码,你会看到如下的代码行:
// delete pBase; IL_0008: ldloc.0 IL_0009: call instance void CBase::__dtor()
当你 delete 某个托管对象时,编译器产生一个对 __dtor 方法的调用。但编译器调用的是哪个 __dtor 方法呢?因为我将 pBase 声明为一个 CBase 指针(CBase*),编译器便调用 CBase::__dtor,正如前面的代码段所示。这似乎就是说 CDerived 在析构期间被旁路掉了,直到你看到 CBase::__dtor 实现:
// in CBase::__dtor() IL_0000: ldarg.0 IL_0001: call void [mscorlib]System.GC::SuppressFinalize(object) IL_0006: ldarg.0 IL_0007: callvirt instance void CBase::Finalize() IL_000c: ret
__dtor 函数用 callvirt 调用 Finalize,即使你从没听说过IL,也能猜到那是一个调用虚拟方法的指令。无论实际对象是哪个,公共语言运行时(CLR)都调用 Finalize 方法——此处是 CDerived。为了强制期望的 C 析构语义,每个 Finalize 方法显式地调用其基类的 Finalize 方法,正像你在 CDerived::Finalize 所看到的那样:
// in CDerived::Finalize() IL_000b: ldarg.0 IL_000c: call instance void CBase::Finalize()
这里编译器产生一个常规调用指令,而非 callvirt。否则你的程序将出现死循环直到耗尽堆栈。
你会注意到在调用 Finalize 方法之前,CBase::__dtor 调用 SuppressFinalize,为什么呢?因为在 C
里,当你清除某个托管对象时,系统不会释放那个对象的存储区。至少不会立即释放。在垃圾收集器运行之前,该对象的内存不会被释放。必须用 SuppressFinalize
来避免对象被终结两次——第一次是调用 delete 时,再一次是垃圾收集器运行的时候。明白吗?
有没有办法从 .NET 框架程序集中调用 MFC 扩展 DLL?我知道如何用 P/Invoke 来调用常规的 DLL 或 COM
DLL,但是不知道如何处理 MFC 扩展 DLL。
我的回答恐怕是:不要往那儿走。理论上,从托管代码中调用 MFC 扩展 DLL 是可能的;但实际上那是极其困难的。MFC 扩展 DLL
关系到大量 MFC 状态,很难在托管代码中建立这些状态。例如,当你调用 AfxGetApp 获取应用程序对象时,它假定 CWinApp 指针在
MFC 的模块中已被初始化为一个全局指针。如果你用 MFC 编写 COM 对象,你知道要在每一个初始化 MFC 状态的入口处使用
AFX_MANAGE_STATE 或 METHOD_PROLOGUE 宏。
我有一个用 C 写的类库,我用托管扩展将它们暴露给 .NET。某些函数使用 uint (无符整型),它与 .NET 中的 UInt32
对应。后来我阅读了一些权威资料后发现 UInt32 不是“ CLS 兼容的”。这到底是什么意思啊,我要考虑这些问题吗?
.NET CLR 是运行时系统,它加载并执行托管程序集。CLR 是一个让你可以用任何你想要的语言编写基于 .NET
应用程序的系统,只要为这些语言提供了 IL 编译器。例如,C 有 unsigned int,C# 有 uint,但 Visual Basic
.NET 现在没有等同的与无符整型对应的内建类型(到了 Visual Basic 2005
将会支持这种类型)。如果你想让对象完全与其它对象交互,而不管它们是用哪种语言实现的,那么就必须将自己约束在整个类型系统的较小的子集中。该子集由
CLS (Common Language Specification——公共语言规范)定义,在 .NET
框架文档中,你可以在“Cross-Language Interoperability——跨语言户用性”中找到这个主题,CLS 规定哪种内建类型是
CLS 兼容的。// mark entire assembly as CLS-compliant [assembly:CLSCompliant(true)];
为了标记特定的类或方法是 CLS 兼容的,你得对类/方法应用这个属性。
唉!虽然 C 编译器识别 CLSCompliant 属性,但它无法检查兼容性。也就是说,即使你标记了它们的兼容性,C
编译器对于非兼容代码不会报错。有一个单独的工具叫 FxCop,这个工具就像托管程序集的绷带(应该有人知道绷带是什么东西吧),然而,FxCop
虽然功能很强,会检查并报错(比如,空析构函数,以“C”开头的类名以及变量名包含非英文字符等),但也它不检查 CLS
兼容性,检查兼容性对你来说更有用。所以在我写此文时,恐怕没有什么好办法自动检查托管 C 程序的 CLS 兼容性。
顺祝编程愉快!
您的提问和评论可发送到 Paul 的信箱:.