C++ at Work

Disabling Print Screen, Calling Derived Destructors, and More

Paul DiLascia

Code download available at:CatWork0511.exe (176 KB)

Q I'm building an application that displays proprietary graphics. Is there some way I can disable the Print Screen function to prevent users from copying images to the clipboard?

Q I'm building an application that displays proprietary graphics. Is there some way I can disable the Print Screen function to prevent users from copying images to the clipboard?

Martin Cruz

A There is a way to disable Print Screen, but I should warn you that there's no way to prevent another application from grabbing the pixels from your window. Many third-party programs are available that can capture the screen, and it's also easy to write such a program. All you have to do to capture the pixels on the screen is copy them from the screen device context using BitBlt. For example:

CWindowDC dc(NULL);   // use NULL to get whole screen
CDC memdc;
... // create, initialize memdc
memdc.BitBlt(..., &dc); // copy screen contents

A There is a way to disable Print Screen, but I should warn you that there's no way to prevent another application from grabbing the pixels from your window. Many third-party programs are available that can capture the screen, and it's also easy to write such a program. All you have to do to capture the pixels on the screen is copy them from the screen device context using BitBlt. For example:

CWindowDC dc(NULL);   // use NULL to get whole screen
CDC memdc;
... // create, initialize memdc
memdc.BitBlt(..., &dc); // copy screen contents

To copy the active window, you'd construct your CWindowDC from a pointer to the CWnd whose pixels you want to grab.

So the bottom line is there's no way you can prevent an application from capturing your window's pixels. That said, if all you want to do is disable Print Screen, or intercept it to do something else, it's fairly easy. Windows implements Print Screen using a registered hotkey. In my December 2000 column, I showed how to use RegisterHotKey to register a hot key for your app (see C++ Q&A: Sending Messages in Windows, Adding Hot Keys to your Application). Windows uses the predefined hotkeys IDHOT_SNAPDESKTOP and IDHOT_SNAPWINDOW to handle Print Screen. These correspond to Print Screen, which captures the entire screen, and Alt+Print Screen, which captures only the active window. To disable these functions all you have to do is register the hotkeys, which causes Windows to send your app a WM_HOTKEY message when the user presses either hotkey. Your implementation can ignore the message to bypass the default screen-capture behavior. A good place to do it is in your mainframe class.

Figure 1 shows the code for a typical MFC CMainFrame class implemented with MFC. The OnCreate/OnDestroy handlers register and unregister the hotkey IDHOT_SNAPDESKTOP; the OnActivate handler registers and unregisters IDHOT_SNAPWINDOW when the app is activated or deactivated. By reenabling IDHOT_SNAPWINDOW when your window is inactive, users can still use Alt+Print Screen when a different app has focus.

Figure 1 Handling Hotkeys

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);
}

You might think you could prevent screen capture by registering your window class using the style CS_OWNDC (which causes Windows to allocate a private device context (DC) for your window class) but that won't work. Windows still copies the bits from your window's private DC to the screen DC, so any app that accesses the screen DC can see your pixels.

Q In your November 2004 column, you discussed calling virtual functions in managed and unmanaged code (see C++ Q&A: Calling Virtual Functions, Persisting View State, POD Type). Is it true that in Visual Studio® 2005, the destructor will be called for a derived class even if it's not declared virtual in the base class? In C++, if I want my derived class destructor to be called whenever the memory is deleted, I have to declare that destructor virtual in the base class.

Q In your November 2004 column, you discussed calling virtual functions in managed and unmanaged code (see C++ Q&A: Calling Virtual Functions, Persisting View State, POD Type). Is it true that in Visual Studio® 2005, the destructor will be called for a derived class even if it's not declared virtual in the base class? In C++, if I want my derived class destructor to be called whenever the memory is deleted, I have to declare that destructor virtual in the base class.

Jigar Mehta

A The short answer is yes, if you're talking about managed classes. If the class is native, the standard C++ rules apply; if it's managed, the destructor is implicitly virtual.

A The short answer is yes, if you're talking about managed classes. If the class is native, the standard C++ rules apply; if it's managed, the destructor is implicitly virtual.

The simplest way to understand what goes on with destructors is to write some code and see what the compiler does with it. Figure 2 shows a simple managed console app that declares two managed classes, CBase and CDerived. The constructors and destructors display printf diagnostics to show when they're called. If you compile this program using /clr, you'll see the following messages in your console window:

ctor: CBase
ctor: CDerived
dtor: CDerived
dtor: CBase

Figure 2 vdtor.cpp

// To compile:
//
//    cl /clr vdtor.cpp
//
#include <tchar.h>
#include <stdio.h>

#using <mscorlib.dll>
using namespace System;

//////////////////
// Managed base class.
//
public __gc class CBase {
public:
   CBase()
   {
      printf("ctor: CBase\n");
   }

   // virtual keyword unnecessary for managed class because CBase is
   // implicitly derived from Object and the dtor is converted to a 
   // Finalize method, which is virtual.

   /*virtual*/ ~CBase()
   {
      printf("dtor: CBase\n");
   }
};

//////////////////
// Managed derived class.
//
public __gc class CDerived : public CBase {
public:
   CDerived()
   {
      printf("ctor: CDerived\n");
   }
   ~CDerived()
   {
      printf("dtor: CDerived\n");
   }
};

//////////////////
// Program entry point.
//
int _tmain()
{
   // Create object: note pointer is declared as CBase* but actually 
   // points to instance of derived class.
   CBase* pBase = new CDerived();

   // Explicitly delete to see which dtor is called...?
   delete pBase;

   return 0;
}

This shows that the derived destructor gets called even though neither the derived nor base class destructor is declared virtual. The constructors and destructors are called in the expected order, with the base class constructed first and destructed last.

Why don't you have to declare managed destructors virtual? Recall that every managed class must either be explicitly derived from another managed class, or else it's implicitly derived from the root base class, Object. The other thing you have to remember is that the C++ compiler converts your managed destructor to a Finalize method, which is virtual in the base Object class. To see this, all you have to do is examine the compiled code using the disassembler ILDASM.

Figure 3 Disassembling vdtor.exe

Figure 3** Disassembling vdtor.exe **

Figure 3 shows the disassembled code for vdtor.cpp. Both CBase and CDerived have Finalize methods; Figure 4 shows the Finalize method for the derived class, CDerived. It also shows that the compiler creates a special __dtor method for each class. This method is invoked when you call delete. If you examine the Microsoft® intermediate language (IL) code for the main entry point (main function), you'll see the following lines:

// delete pBase;
IL_0008:  ldloc.0
IL_0009:  call instance void CBase::__dtor()

Figure 4 IL for Finalize Method

.method family virtual instance void  Finalize() cil managed {
  // Code size       18 (0x12)
  .maxstack  1
  IL_0000:  ldsflda    valuetype $ArrayType$0x6e2836de modopt([Microsoft.VisualC]Microsoft.VisualC.IsConstModifier) ??_C@_0BA@PPNICPAI@dtor?3?5CDerived?6?$AA@
  IL_0005:  call       vararg int32 modopt([mscorlib]System.Runtime.CompilerServices.CallConvCdecl) printf(int8 modopt([Microsoft.VisualC]Microsoft.VisualC.NoSignSpecifiedModifier) modopt([Microsoft.VisualC]Microsoft.VisualC.IsConstModifier)*)
  IL_000a:  pop
  IL_000b:  ldarg.0
  IL_000c:  call       instance void CBase::Finalize()
  IL_0011:  ret
} // end of method CDerived::Finalize

When you delete a managed object, the compiler generates a call to the special __dtor method. But which __dtor method does the compiler call? Since I declared pBase as a pointer-to-CBase (CBase*), the compiler calls CBase::__dtor, as the preceding snippet shows. This would seem to imply that CDerived is bypassed during destruction, until you look at the implementation for 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

The __dtor function calls Finalize using callvirt which, even if you've never heard of IL, you can guess is the instruction to call a method virtually. The common language runtime (CLR) calls the Finalize method for whichever class the object actually is—in this case, CDerived. To enforce the expected C++ destructor semantics, each Finalize method explicitly calls its base class Finalize method, as you can see if you examine CDerived::Finalize:

// in CDerived::Finalize()
IL_000b:  ldarg.0
IL_000c:  call instance void CBase::Finalize()

Here the compiler generates a normal call, not callvirt, instruction. Otherwise your program would recurse until it ran off the end of its stack.

You'll notice that CBase::__dtor calls SuppressFinalize before calling your Finalize method. Why is that? Because in C++, when you delete a managed object, the system doesn't free that object's storage. At least, not immediately. The object's memory isn't freed until the garbage collector runs. SuppressFinalize is required to avoid Finalizing the object twice—first when you call delete and again when the garbage collector runs. Got it?

Q Is there a way to call an MFC extension DLL from a Microsoft .NET Framework assembly? I know how to use P/Invoke to call a regular DLL or to call a COM DLL, but I don't know how to deal with an MFC extension DLL.

Q Is there a way to call an MFC extension DLL from a Microsoft .NET Framework assembly? I know how to use P/Invoke to call a regular DLL or to call a COM DLL, but I don't know how to deal with an MFC extension DLL.

Ali Zamurad

A I'm afraid the short answer is: don't go there. In theory, it should be possible to call an MFC extension DLL from managed code; in practice it would be extremely difficult, if not impossible. MFC extension DLLs hook into a lot of MFC state, which is difficult to create from managed code. For example, when you call AfxGetApp to get the application object, this presumes a pointer to your CWinApp has been initialized as a global in MFC's module state. If you've written COM objects in MFC, you know that you need AFX_MANAGE_STATE or METHOD_PROLOGUE macros at the beginning of every entry point to initialize MFC's state.

A I'm afraid the short answer is: don't go there. In theory, it should be possible to call an MFC extension DLL from managed code; in practice it would be extremely difficult, if not impossible. MFC extension DLLs hook into a lot of MFC state, which is difficult to create from managed code. For example, when you call AfxGetApp to get the application object, this presumes a pointer to your CWinApp has been initialized as a global in MFC's module state. If you've written COM objects in MFC, you know that you need AFX_MANAGE_STATE or METHOD_PROLOGUE macros at the beginning of every entry point to initialize MFC's state.

MFC extension DLLs share a CWinApp-derived global with the main EXE or DLL that hosts the extension. So if you create a new EXE (even a native one), there's no way to load an MFC extension DLL without an application object. The main EXE has to be an MFC app (possibly with some parts compiled using /clr); or you have to rewrite your DLL to not be an extension DLL. If you have back-end code that implements business logic or algorithms, you should be able to isolate it in C extern functions that require no state, which you can call using P/Invoke; or, alternatively, wrap your logic in managed classes (see my September 2005 column at C++ At Work: Copy Constructors, Assignment Operators, and More or the .NET documentation for details). As for user-interface code, the Redmondtonians advise against it. There's just no reasonable way to invoke MFC from managed code. Sorry.

Q I have a class library that's written in C++ and I'm in the process of exposing it to .NET using the Managed Extensions. Some of my functions use uint (unsigned integer), which corresponds to UInt32 in .NET. The other day I was reading the fine print and I noticed that UInt32 is "not CLS-compliant." What exactly does this mean, and should I worry about it?

Q I have a class library that's written in C++ and I'm in the process of exposing it to .NET using the Managed Extensions. Some of my functions use uint (unsigned integer), which corresponds to UInt32 in .NET. The other day I was reading the fine print and I noticed that UInt32 is "not CLS-compliant." What exactly does this mean, and should I worry about it?

Dave Layton

A The .NET CLR is the runtime system that loads and executes managed assemblies. The CLR is what lets you write .NET-based applications in any language you want, provided there's an IL compiler for it. But different languages support different features. For example, C++ has unsigned int and C# has uint, but Visual Basic® .NET currently has no equivalent built-in type for unsigned integers (it gets supports for them in Visual Basic 2005). If you want your objects to fully interact with other objects regardless of which language they're implemented in, you must restrict yourself to a smaller subset of the full type system. This subset is defined by the Common Language Specification (CLS), which you can find in the topic "Cross-Language Interoperability" in the .NET Framework documentation. Among many other things, the CLS specifies which built-in types are CLS-compliant.

A The .NET CLR is the runtime system that loads and executes managed assemblies. The CLR is what lets you write .NET-based applications in any language you want, provided there's an IL compiler for it. But different languages support different features. For example, C++ has unsigned int and C# has uint, but Visual Basic® .NET currently has no equivalent built-in type for unsigned integers (it gets supports for them in Visual Basic 2005). If you want your objects to fully interact with other objects regardless of which language they're implemented in, you must restrict yourself to a smaller subset of the full type system. This subset is defined by the Common Language Specification (CLS), which you can find in the topic "Cross-Language Interoperability" in the .NET Framework documentation. Among many other things, the CLS specifies which built-in types are CLS-compliant.

Figure 5 shows part of the specification that deals with integer types. As you can see, the unsigned types are not CLS-compliant. That's because at the time the Framework was developed, Visual Basic did not have a built-in unsigned type.

Figure 5 Integral Types

CLR Class Description CLS-Compliant Visual Basic C# C++
Int16 A 16-bit signed integer. Yes Short short short
Int32 A 32-bit signed integer. Yes Integer int int , long
Int64 A 64-bit signed integer. Yes Long long __int64
UInt16 A 16-bit unsigned integer. No UInt16. No built-in type. ushort unsigned short
UInt32 A 32-bit unsigned integer. No UInt32. No built-in type. uint unsigned int,unsigned long
UInt64 A 64-bit unsigned integer. No UInt64. No built-in type. ulong unsigned __int64
IntPtr A signed integer whose size depends on the underlying platform (a 32-bit value on a 32-bit platform and a 64-bit value on a 64-bit platform). Yes IntPtr. No built-in type. IntPtr. No built-in type. IntPtr. No built-in type.
UIntPtr An unsigned integer whose size depends on the underlying platform (a 32- bit value on a 32-bit platform and a 64-bit value on a 64-bit platform). No UIntPtr. No built-in type. UIntPtr. No built-in type. UIntPtr. No built-in type.

What does it mean that UInt32 is not CLS-compliant? In particular, what does it mean for your library port? First of all, the CLS rules apply only to the classes and methods your assembly exposes to the outside world. Internally, you can use whatever types your language supports. Second, UInt32 is a full-fledged CLR class, which means any .NET-targeted language, including Visual Basic, can compile and link with your code, even if it exposes methods that use UInt32. If you have a method that returns an UInt32, either as a return value or [out] parameter, any program can then pass that value back to another method as an input parameter. But some languages like Visual Basic may not be able to create an unsigned integer. This may or may not be a problem, depending on your application.

A Visual Basic program can always pass a negative Integer, and when it gets to your library, it'll be treated as unsigned—but the Visual Basic program can't do arithmetic properly, since it treats large unsigned values as negative integers. If you need arithmetic and the full 32-bit range of an unsigned integer, you should expose your parameter as Int64 instead of UInt32.

How do you know if your app is CLS-compliant? If you're writing in C#, you can use the CLSCompliant attribute to have the compiler check your code for CLS compliance. You can apply CLSCompliant to your entire assembly or a particular class or method. For example:

// mark entire assembly as CLS-compliant
[assembly:CLSCompliant(true)];

To mark a particular class or method as CLS-compliant, you'd apply the attribute to the class/method.

Alas, while the C++ compiler recognizes the CLSCompliant attribute, it doesn't check for compliance. That is, the C++ compiler won't complain of noncompliant code, even if you mark it as compliant. There's a separate standalone tool called FxCop that's like lint for managed assemblies (for those of you who remember what lint is), but while FxCop will complain about all sorts of things (such as empty destructors, class names that begin with "C", and variable names that aren't composed of English words) it doesn't check for CLS compliancy, which you would think is more useful. So I'm afraid as of this writing, there's no easy way to automatically check managed C++ programs for CLS compliancy.

Happy programming!

Send your questions and comments for Paul to  cppqa@microsoft.com.

Paul DiLascia is a freelance software consultant and Web/UI designer-at-large. He is the author of Windows++: Writing Reusable Windows Code in C++ (Addison-Wesley, 1992). In his spare time, Paul develops PixieLib, an MFC class library available at www.dilascia.com.