Monday, October 19, 2009

Extended Interfaces (#import)


3 4



Extended Interfaces (#import)


Given a type library, the compiler's #import directive generates, on the fly, a set of artifacts representing the type library's content, which make the objects and interfaces contained in the type library accessible to C++ client code. This by itself is a very useful feature. In the old days, C++ programmers had to manually invoke tools that would build these artifacts for them. If you were developing the project represented by the type library and the client access code simultaneously, things could easily get out of sync and access violations would result: clients would make a call to method A, but instead end up calling method B, and other fun problems. Through its automatic tie-in with the compilation process, #import solves all this. But it can do more than that: it can extend the binary compatibility contract between object and client, the interface, both syntactically as well as semantically. Let's examine these extensions in detail.



UUID Type Binding



Interface and coclass IDs are attached directly to their respective types. The #import directive uses the compiler's uuid attribute for __declspec to accomplish this. The ID of any interface or coclass so adorned can then be read using the __uuidof keyword. Have you ever wondered how CComQIPtr can work even when you omit the second template instantiation parameter? When provided, this parameter indicates the ID of the interface given as the first instantiation parameter. How can operator = be implemented if we don't know what interface ID to query for? The answer lies in the use of __uuidof. Both smart pointer template varieties rely on this feature.2



Exceptions



Every nonlocal method or property in a COM+ interface must return an HRESULT, indicating, among other things, invocation success or failure. As a rule, you must check the return value of each COM+ interface method invocation and act appropriately if the call failed. But such recoveries often mean merely that you terminate the execution of your client code and propagate the error to your caller. Doing this in-line with every method call can be tedious, error-prone, and make for hard-to-read source code. Wouldn't it be nice if COM+ methods could throw exceptions on failure, just like C++ functions? Well, now they can: by default, #import generates high-level method and property wrapper functions that extend the semantics of the raw method. One such extension features the addition of code that tests the return code for failure and generates an exception of type _com_error if failure was detected. The exception object then contains the error code.



Be sure to remember, however, that while an exception generated by a high-level wrapper function can be propagated to a C++ caller, it must never be allowed to cross an interface boundary. Have a look at Chapter 1, "Error Handling," for techniques that you can use to preserve the power of the exception-programming model without compromising the ease of its application.



Return Values



As I already mentioned, every nonlocal method or property in a COM+ interface must return an HRESULT. While an IDL parameter attribute retval exists and is encoded in type libraries, this is of no use to callers who use the low-level, binary compatibility interface class to access methods and properties. Another semantic extension performed by #import's high-level wrappers changes the method signature so that it moves that parameter designated with retval in the type library into the return value position. The HRESULT is then hidden, but the wrapper still throws an exception containing this value if it is a failure code.



As an added bonus, a smart pointer, which is itself returned by value, wraps this return value when its type is that of an interface pointer. That means that you can ignore the return value if you like, and you will not create a leak. The smart pointer is constructed on return from the wrapper in a manner that permits the compiler to perform return value optimization3, and so the underlying object or proxy does not experience an extraneous AddRef/Release pair of calls. These remarks do not, however, apply to other [out] parameters and you should pass the return value of the & operator of a smart pointer object in their positions.



Finally, let's take a look at the declaration and implementation of such a wrapper:





//�Created�by�Microsoft�(R)�C/C++�Compiler�Version�12.00.8472.0�(5e97cf11).
//
//�c:\program�files\microsoft�visual�studio\myprojects\returndemo\debug\
//�ReturnDemo.tlh
//
//�C++�source�equivalent�of�Win32�type�library�ReturnDemo.tlb
//�compiler-generated�file�created�03/10/00�at�20:24:04�-�DO�NOT�EDIT!

#pragma�once
#pragma�pack(push,�8)

#include�<comdef.h>

namespace�RETURNDEMOLib�{

//
//�Forward�references�and�typedefs
//

struct�/*�coclass�*/�Foo;
struct�__declspec(uuid("339e317c-42fe-4020-ab15-27a87a7e07e5"))
/*�dual�interface�*/�IFoo;
struct�__declspec(uuid("2980513a-412f-42aa-8bb4-dda58235163e"))
/*�dual�interface�*/�IBar;
struct�/*�coclass�*/�Bar;

//
//�Smart�pointer�typedef�declarations
//

_COM_SMARTPTR_TYPEDEF(IFoo,�__uuidof(IFoo));
_COM_SMARTPTR_TYPEDEF(IBar,�__uuidof(IBar));

//
//�Type�library�items
//

struct�__declspec(uuid("60047dbe-0276-4e07-94bb-aceae99843d6"))
Foo;
����//�[�default�]�interface�IFoo

struct�__declspec(uuid("339e317c-42fe-4020-ab15-27a87a7e07e5"))
IFoo�:�IDispatch
{
����//
����//�Wrapper�methods�for�error-handling
����//

����IBarPtr�GetBar�(�);

����//
����//�Raw�methods�provided�by�interface
����//

����virtual�HRESULT�__stdcall�raw_GetBar�(
��������struct�IBar�*�*�Result�)�=�0;
};

struct�__declspec(uuid("2980513a-412f-42aa-8bb4-dda58235163e"))
IBar�:�IDispatch
{};

struct�__declspec(uuid("eba409ab-1f62-4ee3-a19a-a914eb16bc44"))
Bar;
����//�[�default�]�interface�IBar

//
//�Wrapper�method�implementations
//

#include�"c:\program�files\microsoft�visual�studio\myprojects\returndemo\debug\
ReturnDemo.tli"

}�//�namespace�RETURNDEMOLib

#pragma�pack(pop)

//�Created�by�Microsoft�(R)�C/C++�Compiler�Version�12.00.8472.0�(5e97cf11).
//
//�c:\program�files\microsoft�visual�studio\myprojects\returndemo\debug\
//�ReturnDemo.tli
//
//�Wrapper�implementations�for�Win32�type�library�ReturnDemo.tlb
//�compiler-generated�file�created�03/10/00�at�20:24:04�-�DO�NOT�EDIT!

#pragma�once

//
//�interface�IFoo�wrapper�method�implementations
//

inline�IBarPtr�IFoo::GetBar�(�)�{
����struct�IBar�*�_result;
����HRESULT�_hr�=�raw_GetBar(&_result);
����if�(FAILED(_hr))�_com_issue_errorex(_hr,�this,�__uuidof(this));
����return�IBarPtr(_result,�false);
}





Smart Pointers and [in, out] Parameters


Smart pointers interact well with parameters in COM+ interface methods or properties whose directional attribute is either [in] or [out]: the smart pointer can be passed by value in the former case (its operator Interface* will take care of the implicit conversion), and the return value of operator & can be passed in the latter, ensuring that no leak occurs by overwriting an existing non-NULL value in the encapsulated raw pointer. The [in, out] directional attribute, however, is an entirely different story: the smart pointers' design resists these semantics because it requires giving callees control over the encapsulated interface pointer. The [in, out] directional attribute, however, calls for this kind of control.

The ATL templates give you a solution for this problem: pass the address of the encapsulated interface pointer—that is, write &ciSmart.p instead of &ciSmart. The _com_ptr_t class, however, resists all attempts at temporarily disabling its protective umbrella. Your best bet is to assign it to a type-compatible template instantiation of CComPtr, use the preceding trick of passing the encapsulated interface pointer, and then assign the result back to _com_ptr_t after the call.





Syntactic Properties



Accessor and mutator methods can be designated as properties in an IDL interface or type library. But again, to consumers of interface classes molded in the image of the binary interface contract such an attribute is of no relevance. C++ code going after the low-level interface class sees the raw accessor and mutator (also called get and put) functions. Enter the property attribute for __declspec: it instructs the compiler to convert all access to a data member to accessor and mutator method invocations or both instead. (No memory is reserved for such a syntactical data member.) The #import directive generates such a property declaration for each property found in a type library interface, and (when the property has an interface pointer type) has the high-level accessor wrapper function return this value wrapped by a smart pointer, as is the case with all return values. Array syntax is supported as well, where successive indices are passed to the accessor and mutator as
subsequent parameters. Exceptions are thrown on accessor and mutator invocation failure as expected.



The syntactic property feature has sparked some debate in the community of seasoned C++ programmers, where it may at times cause confusion rather than serve as a programming model simplification. Accessing a data member and having such access converted to method invocations by the compiler behind the scenes does appear somewhat—shall we say magical?—at first. But let's remember that this feature is all about ease of implementation, convenience, readability, and maintainability. Take a look at these equivalent code fragments and decide what you would rather write.





HRESULT�hResult;
int�nValue;

if�(FAILED(hResult�=�piFoo->GetMyValue(&nValue)))
����return�hResult;

nValue�+=�7;

if�(FAILED(hResult�=�piFoo->PutMyValue(nValue)))
����return�hResult;



or





ciFoo->MyValue�+=�7;



I cheated a little here, in that I took advantage of the exception semantics at the same time that I illustrated the property syntax. The former would have been available separately, even without using the property syntax. In any case, at best we are talking about one declaration and three other lines of code or else nested function calls without the property syntax. Therefore, four lines against one says that syntactic properties are a good thing.



Take a look once again at the code fragment in Listing 2-1. Let's rewrite this fragment using all techniques we have surveyed.





const�MyLib::IFooPtr�ciFoo(__uuidof(MyLib::Foo));
int�nMagic�=�ciFoo->Magic;

std::vector<int>�cMagicList;
cMagicList.push_back(nMagic);

if�(nMagic�<�CONST_MAGIC)
����return;

ComputeMagic(cMagicList);
ciFoo->Magic�=�cMagicList.front();



That's 8 lines of code compared to the original 24. But look at the difference: not only is it less, it is now also readable. The developer can finally focus on the task at hand, instead of struggling with COM+ plumbing every step of the way. This is what smart pointers are all about.



7.4 The Security Manager











 < Day Day Up > 





7.4 The Security Manager



A Java environment can be subjected to four levels of attack:



  1. System modification, in which a program gets read/write access and makes some changes to the system

  2. Privacy invasion, in which a program gets read access and steals restricted information from the system

  3. Denial of service, in which a program uses up system resources without being invited

  4. Impersonation, in which a program masquerades as the real user of the system



The default Java 2 SecurityManager, in package java.lang, enforces restrictions based on security policy statements that are designed to prevent the first two types of attack and, to some extent, the last. In this section, we look at what the SecurityManager does and how it does it. Finally, we briefly consider the tricks that a program can use to perform the nuisance attacks�denial of service and impersonation.



7.4.1 What the SecurityManager Does



In the Java 2 platform, SecurityManager is a concrete class, whose implementation supports the policy-driven security model discussed in Chapter 8 on page 253. Previously, SecurityManager was an abstract class that application developers, such as JVM and WAS manufacturers, were forced to extend to implement a set of access controls. Although the class was abstract, it did implement a set of check methods: for example, checkRead(), checkWrite(), and checkConnect(). The intent was for the application developer to override these methods with something that answered the question, "Is the applet allowed to do this?" either by quietly returning to the caller an implicit yes or by throwing a SecurityException, an emphatic no. As shipped, each method did have a default behavior in case the application did not override the method; it simply said no by throwing a SecurityException.



With the Java 2 platform, java.lang.SecurityManager is a fully functional, resource-level, access-control facility. Application developers need call only one method, checkPermission(), which takes a Permission object as a parameter and forwards the Permission checking to the checkPermission() method in class java.security.AccessController. AccessController is the class that enforces the security policy configuration imposed by the Java system administrator, which we introduced in Section 7.2.1 on page 207 (see Figure 7.14).



Figure 7.14. SecurityManager, AccessController, and the Current Security Policy




Even though the Java 2 SecurityManager offers a checkPermission() method taking a Permission object as a parameter, the other check methods are still available for backward compatibility. However, they now answer the question using the Java 2 permission model by turning the request into a Permission and calling SecurityManager.checkPermission(). All the check methods can still be overridden, if necessary.



Figure 7.15 summarizes which system resources are protected by the default SecurityManager, the methods that are invoked to check whether the rights to access the resources have been granted, and the Permission types that are passed to checkPermission() by each check method. This is also the Permission type to pass to checkPermission() when you call it directly.



Figure 7.15. Default SecurityManager Controls




The default SecurityManager automatically grants a class file the java.io.FilePermission necessary to read to all files contained in the class's directory and all its subdirectories recursively, as illustrated in Figure 7.16. No explicit file read Permission is necessary in this case. However, this rule does not apply to class files contained in JAR files.



Figure 7.16. Automatic File Read Permission Granted to a Class File




Another Permission that is automatically granted by the default SecurityManager is the java.net.SocketPermission that allows a remote code to connect to, accept, and resolve the local host and the host the code is loaded from, including the host name of the local system, if loaded locally, as shown in Figure 7.17. No explicit SocketPermission is required in this case.



Figure 7.17. SocketPermission Automatically Granted to Remote Code




7.4.2 Operation of the SecurityManager



Although any Java program, such as a servlet, an enterprise bean, or an application, can instantiate a new SecurityManager, the JVM will allow only one SecurityManager to be active at a time. To make a SecurityManager active programmatically, you have to call the static method System.setSecurityManager() and pass it an instance of the desired SecurityManager.



In the Java 2 platform, the SecurityManager can be activated for local applications as well, even though this is not the default option, and an explicit command-line flag must be specified.[13] In other words, local applications can now, on demand, be subjected to the access-control restrictions of the SecurityManager, which enforces the security policy configuration imposed by the system administrator. This was not true prior to the Java 2 platform. The reason for this security enforcement is that today's application distribution may be performed through FTP or by shipping a CD-ROM in the mail. An application that has been obtained in one of these ways will be run locally, even though its origin is external to the local file system. It is therefore very important to be able to restrict, if necessary, the system resources that an application can have access to.

[13] In the J2SE reference implementation, you can set the system property java.security.manager as an option on the java command. The -Djava.security.manager command line option will activate the default SecurityManager, which, as we said, is the one in package java.lang. The option -Djava.security.manager= CustomSecurityManager will load and make class CustomSecurityManager the active SecurityManager. Note that CustomSecurityManager must be a SecurityManager subclass and must be reachable through one of the active class search paths (see Section 7.2.2 on page 207).



Once a SecurityManager is active, it cannot be replaced unless the program that attempts the replacement has the authority to create an instance of SecurityManager and set a SecurityManager instance as the active SecurityManager. These two rights translate into two java.lang.RuntimePermissions that the Java system administrator has to grant to the code attempting the SecurityManager replacement. The target parameters for these two RuntimePermissions are, respectively, "createSecurityManager" and "setSecurityManager".



The reason a Permission is required to set a SecurityManager instance as active should be obvious; a SecurityManager instance could choose to authorize any operation that running programs attempt to perform. However, it is not so obvious why a Permission is required even to simply instantiate a SecurityManager, regardless of the fact that the new SecurityManager instance might never be set as the current SecurityManager of the Java system. The reason for this restriction is that any SecurityManager instance, whether active or inactive, can be used to inspect the current execution stack and obtain security-sensitive information about the current execution environment.



Once installed, a SecurityManager is active only on request; it does not check anything unless one of its check methods is called by other system functions. Figure 7.18 illustrates the flow for a specific restricted operation: establishing a network connection. The calling code creates a new java.net.Socket class, using one of the constructor methods it provides. This method invokes the checkConnect() method of the local SecurityManager subclass instance, passing a java.net.SocketPermission object as a parameter.



Figure 7.18. SecurityManager Operation




The SecurityManager has a very simple question to answer when one of its check methods is invoked: "Is this program allowed to perform the security-sensitive operation?" In Figure 7.18, this question becomes, "Can the application code connect to the specified host on the specified port?" In order to answer this question, the SecurityManager relies on the underlying AccessController class to check whether the running code has been granted the Permission entry necessary to perform the socket connection. If the connection is allowed, a Socket instance is created and made available to the application code. Otherwise, AccessController throws a SecurityException.



7.4.3 Types of Attack



Although we do not describe any attacks in detail, it is worth summarizing some of the security holes that have been discovered in previous Java releases. All the bugs reported in this section were found in vendor implementations of the JVM. If application developers and JVM implementors use the fully functional Java 2 SecurityManager as a base for their work, the number and variations of security implementations, and therefore possibilities for error, will be greatly reduced.



Flaws and the security exposures they might create are inevitable. However, the Java platform receives a great deal of attention by a wide audience. An encouraging consequence is that most of the flaws found to date were identified by field researchers attempting to find and close all holes. Fixes were provided rapidly by Sun Microsystems and application vendors. All this experience has influenced the evolution of the Java 2 security architecture.



7.4.3.1 Infiltrating Local Classes


Prior to the Java 2 platform, David Hopwood, once a student at Oxford and then a Netscape employee, discovered a vendor JVM implementation bug that allowed an applet to load a class from any directory on the browser system. This bug was quickly fixed.



Downloading code packages from the Internet has become a part of everyday life for many people. Any of those packages could have been modified to plant a Trojan horse class file along with their legitimate payload. Of course, this is not just a Java problem but more like a new form of computer virus. One solution lies in signed content, so that you know that the package you download comes from a trusted source and is not likely to have been tampered with.



Fully trusted classes are those that the JVM assumes and depends on being correct and well behaved. Java 2 fully trusted classes are limited to those on the boot class path; all other classes are subject to verification and security policy restrictions. Protecting the Java 2 trusted classes is a matter of limiting access to the directories and files on the boot class path. As part of the boot class path, those files are automatically considered fully trusted. The class-loading mechanism gives them the highest loading priority, as they are loaded by the primordial class loader and are exempted from class file verification and security policy restrictions. The underlying operating system should be configured to restrict writing access to the directories pointed to by the boot class path.



The extension framework, which we described on page 212, also offers a back door to hackers. Because extension classes are by default granted full access to the system resources, it is highly recommended that system administrators allow only trusted users to add extensions to the runtime environment. An alternative is for system administrators to change the policy configuration and reduce the set of authorizations granted to the extensions.



7.4.3.2 Type Confusion


The Java platform goes to great lengths to ensure that objects of a particular type are dealt with consistently. We see this both in the compiler and later in the third pass of the class file verifier. It is crucial that the class of an object and the level of access it allows, as specified by the private, protected, or public keywords, are preserved.



If, somehow, an attacker can create an object reference that is not of the type it claims to be, there is a possibility of breaking down the protection. Several examples have shown ways to achieve type confusion by taking advantage of various JVM implementation flaws, such as:



  • A bug that allowed a ClassLoader to be created but avoided calling the ClassLoader constructor, which normally invokes SecurityManager.checkCreateClassLoader(), as shown in Figure 7.15 on page 240

  • Flaws in JVM access checking that allowed a method or an object defined as private in one class to be accessed by another class as public

  • A JVM bug that failed to distinguish between two classes with the same name but loaded by different class loaders



7.4.3.3 Network Loopholes


The first security-related JVM flaw to get worldwide attention was a failure to check the source IP address of a remote program rigorously enough. This was exploited by abusing the DNS, a network service responsible for resolving names to addresses and vice versa, to fool the SecurityManager into allowing the remote program to connect to a host that would normally have been invisible to the server from which the program was loaded. In this way, the attacker could access a system that would normally be safe behind a firewall.



7.4.3.4 JavaScript Back Doors


A series of JavaScript exploits allowed a script to persist after the Web page it was invoked from had been exited. This flaw was used to track the user's Web accesses. The problem was fixed but then reappeared when Netscape introduced LiveConnect, which allows a JavaScript to create Java objects and invoke Java methods. Both Java and JavaScript have strict limitations on what they are allowed to do, but the limitations are different. By combining them, it was possible to get a union of the two protection schemes.



7.4.4 Malicious Code



Setting the rules for a program's environment is always a question of striking a balance. The program needs some system and/or network resources; otherwise, it will not be useful at all. On the other hand, it must not be allowed to have free reign over the system, especially if this program has been downloaded from a remote site. The need to find a compromise between allowing a program to access some system resources and restricting its access to other resources at the same time poses the conditions for some security exposures.



So far, we have talked about system modification and privacy invasion. What about denial of service and impersonation? These last two categories of exposure are allowed by the Java security framework because they cannot harm a system in a permanent way. However, they can still be annoying or damaging.



In theory, there is also another type of malice that is not Java specific. This is based on deception, that is, the attempt to trick users into entering information that they would not normally give away. This sort of thing is not specific to Java. In fact, there are much easier ways to do the same thing by using scripting languages or simple HTML forms, so we will not consider them further here.



7.4.4.1 Cycle Stealing


Denial-of-service attacks have long been a scourge of the Internet. Denial of service implies that the user can no longer use the system, because a server or even a whole site has been taken down. Cycle stealing is much more subtle: a cycle-stealing program is any program that consumes resources, whether computer or human, without the user's permission. The most extreme form of these are denial-of-service programs, but the most insidious ones may not be detected by their victim at all.



There are obvious denial-of-service attacks. For example, a program could try to create an infinite number of windows or could sit in a tight loop, using up CPU cycles. These attacks are very annoying and can have a real impact: for example, if the user has to reboot the machine to recover.



The key to this kind of program lies in persistent background threads. Every implementation of the JVM supports threads, implemented as Thread objects, and the Java language makes it very easy to use them. Normally, when a Java program stops, it will also stop any Threads it created. However, there is nothing to assist the program in this task or to enforce that this is done. Indeed, if a Java program fails, intentionally or unintentionally, to explicitly stop the Threads it created, they will continue to run until they end on their own or the application�the J2EE container, for instance�ends.



The attack described here is fairly benign. The attacker has obtained free use of machine cycles on your system. What sort of thing might he or she want to do with them?



One example might be to do brute-force cipher cracking. A feature of any good symmetric-key encryption algorithm is a uniform key space. That is, if you want to crack the code, there is no mathematical shortcut to finding the key; you simply have to try all possible keys until you find one that works. Several recent encryption challenges have been solved by using spare cycles on a large number of computers working as a loosely coupled complex, each being delegated a range of keys to try, under the direction of a central coordinator.



A number of other attacks along the same lines have been demonstrated, such as programs that kill the Threads of other programs executing concurrently. This type of attack can be prevented by controlling which programs are allowed to run on the Java platform.



7.4.4.2 Impersonation


Internet e-mail is based on the Simple Mail Transfer Protocol (SMTP). Mail messages are passed from one SMTP gateway to another, using sessions on TCP/IP port 25. Abusing these connections to send bogus e-mail is an established nuisance of the Internet. A hacker can create mail messages that appear to come from someone else, which can be used to embarrass or annoy the receiver of the mail and the apparent sender.



Mail that has been forged in this way is not impossible to tell from the real thing, however. The SMTP gateways keep track of the original IP address, so you can trace the message back, if not to a person at least to a machine, unless the originator was also using a spoofed IP address.



A Java remote program allows this kind of errant behavior to go one stage further. In fact, a remote program is typically allowed to connect to port 25 and appear to be a mail client. However, the only system it can connect to is the one that it was originally loaded from, because of the sandbox restrictions (see Figure 7.17 on page 241). Therefore, if an attacker is able to load a program on a remote machine, the program would be allowed to connect back to the server and send e-mail to the target of the attack. When the recipient checks the IP address, it belongs to a complete stranger, who has no idea that anything has happened.



7.4.5 SecurityManager Extensions



The default Java 2 SecurityManager is a fully functional class that acts as an interface between programs running on top of the JVM and the underlying Java 2 security access-control system. The structure is very flexible, and most applications will find that the default Java 2 SecurityManager will give them all the function they need. However, sometimes an application vendor, such as a J2EE product provider, will want to extend or limit the default SecurityManager's capabilities. Several examples follow.



  • You may want to prevent access to a system resource even if someone explicitly grants that Permission in the system policy database. This means that the SecurityManager overrides the security policy at runtime.

  • You may want to log all the requests for access to certain resources.

  • You may want to prompt users with a special password before a particular system resource can be accessed.



Because a SecurityManager is responsible for enforcing access-control restrictions, a SecurityManager extension should not be subjected to any security restrictions. In fact, a custom SecurityManager should be granted AllPermission.



7.4.5.1 Ignoring Policy


Sometimes, a JVM vendor or a J2SE/J2EE provider may wish to deny a certain Permission even when the Java system administrator has explicitly granted that Permission. The code fragment in Listing 7.7 shows how to override the checkPermission() method in the default SecurityManager to deny the Permission to print unconditionally.



Listing 7.7. Overriding SecurityManager.checkPermission()






public void checkPermission(Permission perm)

{

if (perm instanceof RuntimePermission &&

perm.getName() == "queuePrintJob")

{

System.out.println("Permission to print is denied");

throw new SecurityException();

}

super.checkPermission(perm);

}



The checkPermission() method shown in Listing 7.7 behaves exactly as its homonym in the SecurityManager superclass, except when it is invoked to check whether a RuntimePermission "queuePrintJob" has been granted to the running code. In this case, the custom SecurityManager will deny the Permission unconditionally by throwing a SecurityException.



7.4.5.2 Logging


In this section, we take an easy task and continue with our theme by implementing a simple audit log of Permission requests. Our example creates a log file during construction of the SecurityManager and overrides the checkPermission() method. Whenever a checkPermission() is received, this implementation of checkPermission() will log in file PermissionRequests.log that a check is being made to check whether a particular Permission is being checked by writing a String representation of the Permission to the file. Then, it will call the parent SecurityManager's checkPermission() method, which will enforce the access-control restrictions as imposed by the security policy currently in effect. A log function such as the one implemented by this SecurityManager extension could be useful in a J2EE environment for auditing purposes. The code of this SecurityManager extension is shown in Listing 7.8.



Listing 7.8. LogSecurityManager.java






import java.io.DataOutputStream;

import java.io.FileOutputStream;

import java.io.IOException;



import java.security.Permission;





/**

* LogSecurityManager extends the default SecurityManager in

* package java.lang by overriding the checkPermission()

* method. The implementation of checkPermission() offered

* by LogSecurityManager logs all the checkPermission()

* invocations by registering the name of the Permission

* being checked.

*/

public class LogSecurityManager extends SecurityManager

{

private DataOutputStream auditlog;



/**

* Public constructor. It calls the constructor in the

* SecurityManager superclass and then initializes the log

* function.

*/

public LogSecurityManager()

{

super(); // initilize using parent constructor



try

{

auditlog = new DataOutputStream(new

FileOutputStream("PermissionRequests.log"));

auditlog.writeBytes("Log Started:\n");

}

catch (IOException e)

{

System.err.println

("PermissionRequests.log file not opened " +

"properly\n" + e.toString());

}



System.out.println("LogSecurityManager constructed");

}



/**

* This method behaves exactly as checkPermission() in

* the SecurityManager superclass, the only difference

* being that all the Permission requests are being

* logged.

*

* @param perm a java.security.Permission object

* representing the access right being checked

* by this LogSecurityManager.

*/

public void checkPermission(Permission perm)

{

try

{

auditlog.writeBytes("Checking: " + perm.toString() +

"\n");

}

catch (IOException e)

{

System.err.println

("Could not write to log file\n" + e.toString());

}



// Invoke checkPermission() in the superclass.

super.checkPermission(perm);

}

}



7.4.5.3 Enforcing Password-Based Protection


This section shows how to program a SecurityManager implementing password-based authentication. This SecurityManager subclass asks the user for a password whenever a simple file read or write is attempted. This SecurityManager overrides the default implementation provided by the Java 2 API in package java.lang. We also show how to combine this password-based control with the policy-based access control of the default SecurityManager. Listing 7.9 gives the code.



Listing 7.9. RWSecurityManager.java






import java.io.BufferedReader;

import java.io.IOException;

import java.io.FileDescriptor;

import java.io.InputStreamReader;





/**

* This class implements a SecurityManager that prompts the

* user to authenticate with a password every time there is

* a file read and write attempt.

*/

public class RWSecurityManager extends SecurityManager

{

private String rpasswd; // private read password

private String wpasswd; // private write password



/**

* Public constructor, used to set the read and write

* passwords.

*

* @param rpwd a String representing the read password.

* @param wpwd a String representing the write password.

*/

public RWSecurityManager(String rpwd, String wpwd)

{

super();





// The class using this SecurityManager will set

// both the read and write passwords

this.rpasswd = rpwd;

this.wpasswd = wpwd;

}



/**

* This method overrides checkRead() in the superclass

* by asking the user for a password every time there is

* an attempt to perform a file read operation.

* Optionally, this method can call checkRead() in the

* superclass. In this case, entering the correct

* password will not be enough, and the code attempting

* to perform the file read operation will have to have

* been granted a java.io.FilePermission.

*

* @param fileName a String representing the name of

* the file from which the code is attempting to

* read.

*/

public void checkRead(String filename)

{

String pwdgiven;



// Ask if the user has the required password

System.out.println

("Enter the password for reading files.");



try

{

pwdgiven = new BufferedReader

(new InputStreamReader(System.in)).

readLine();



if (pwdgiven.equals(rpasswd))

System.out.println

("Permission to read files granted.");

else

throw new SecurityException

("Permission to read files denied");

}

catch (IOException e)

{

throw new SecurityException

("Permission to read files denied");

}



// Uncomment the line below if you want to call

// SecurityManager.checkRead() at this time



// super.checkRead(filename);

}



/**

* This method overrides checkWrite() in the superclass

* by asking the user for a password every time there is

* an attempt to perform a file write operation.

* Optionally, this method can call checkWrite() in the

* superclass. In this case, entering the correct

* password will not be enough, and the code attempting

* to perform the file write operation will have to have

* been granted a java.io.FilePermission.

*

* @param fileName a String representing the name of

* the file to which the code is attempting to

* write.

*/

public void checkWrite(String filename)

{

String pwdgiven;



// Ask if the user has the required password

System.out.println

("Enter the password for writing to files.");



try

{

pwdgiven = new BufferedReader(new

InputStreamReader(System.in)).readLine();

if (pwdgiven.equals(wpasswd))

System.out.println

("Permission to write files granted");

else

throw new SecurityException

("Permission to write files denied");

}

catch (IOException e)

{

throw new SecurityException

("Permission to write files denied");

}



// Uncomment the line below if you want to call

// SecurityManager.checkWrite() at this time



// super.checkWrite(filename);

}

}



If an instance of RWSecurityManager is set as the active SecurityManager of a Java system, code attempting to read and write files will cause a SecurityException to be thrown unless the user running the program enters the correct authenticating password. However, it is not necessary to grant the code the FilePermission to read and write files. The reason is that the methods checkRead() and checkWrite() of the superclass SecurityManager are completely overwritten. If invoked, those methods would call checkPermission() in AccessController. The RWSecurityManager class bases its policy decision on a password. If the application developer wants to keep the behavior of SecurityManager, which requires specific read and write FilePermissions enabled in the active policy, RWSecurityManager has to call super.checkRead() and super.checkWrite(). The code in Listing 7.9 shows the calls to these two methods commented out. Uncommenting those lines will enable the default SecurityManager functions. At that point, the Java system administrator will need to modify the active security policy of the Java system in order to have the application work correctly.













     < Day Day Up > 



    11.7 Request Processing

    Team-Fly
     

     

    TCP/IP Illustrated, Volume 2: The Implementation
    By
    Gary R. Wright, W. Richard Stevens
    Table of Contents
    Chapter 11. 
    ICMP: Internet Control Message Protocol


    11.7 Request Processing


    Net/3 responds to properly formatted ICMP request messages but passes invalid ICMP request messages to rip_input. We show in Chapter 32 how ICMP request messages may be generated by an application process.


    Most ICMP request messages received by Net/3 generate a reply message, except the router advertisement message. To avoid allocation of a new mbuf for the reply, icmp_input converts the mbuf containing the incoming request to the reply and returns it to the sender. We discuss each request separately.


    Echo Query: ICMP_ECHO and ICMP_ECHOREPLY


    For all its simplicity, an ICMP echo request and reply is arguably the single most powerful diagnostic tool available to a network administrator. Sending an ICMP echo request is called pinging a host, a reference to the ping program that most systems provide for manually sending ICMP echo requests. Chapter 7 of Volume 1 discusses ping in detail.



    The program ping is named after sonar pings used to locate objects by listening for the echo generated as the ping is reflected by the other objects. Volume 1 incorrectly described the name as standing for Packet InterNet Groper.



    Figure 11.20 shows the structure of an ICMP echo and reply message.



    Figure 11.20. ICMP echo request and reply.


    icmp_code is always 0. icmp_id and icmp_seq are set by the sender of the request and returned without modification in the reply. The source system can match requests and replies with these fields. Any data that arrives in icmp_data is also reflected. Figure 11.21 shows the ICMP echo processing and also the common code in icmp_input that implements the reflection of ICMP requests.



    Figure 11.21. icmp_input function: echo request and reply.


    235-237

    icmp_input converts an echo request into an echo reply by changing icmp_type to ICMP_ECHOREPLY and jumping to reflect to send the reply.


    277-282

    After constructing the reply for each ICMP request, icmp_input executes the code at reflect. The correct datagram length is restored, the number of requests and the type of ICMP messages are counted in icps_reflect and icps_outhist[], and icmp_reflect (Section 11.12) sends the reply back to the requestor.



    Timestamp Query: ICMP_TSTAMP and ICMP_TSTAMPREPLY


    The ICMP timestamp message is illustrated in Figure 11.22.



    Figure 11.22. ICMP timestamp request and reply.


    icmp_code is always 0. icmp_id and icmp_seq serve the same purpose as those in the ICMP echo messages. The sender of the request sets icmp_otime (the time the request originated); icmp_rtime (the time the request was received) and icmp_ttime (the time the reply was transmitted) are set by the sender of the reply. All times are in milliseconds since midnight UTC; the high-order bit is set if the time value is recorded in nonstandard units, as with the IP timestamp option.


    Figure 11.23 shows the code that implements the timestamp messages.



    Figure 11.23. icmp_input function: timestamp request and reply.


    238-246

    icmp_input responds to an ICMP timestamp request by changing icmp_type to ICMP_TSTAMPREPLY, recording the current time in icmp_rtime and icmp_ttime, and jumping to reflect to send the reply.


    It is difficult to set icmp_rtime and icmp_ttime accurately. When the system executes this code, the message may have already waited on the IP input queue to be processed and icmp_rtime is set too late. Likewise, the datagram still requires processing and may be delayed in the transmit queue of the network interface so icmp_ttime is set too early here. To set the timestamps closer to the true receive and transmit times would require modifying the interface drivers for every network to understand ICMP messages (Exercise 11.8).



    Address Mask Query: ICMP_MASKREQ and ICMP_MASKREPLY


    The ICMP address mask request and reply are illustrated in Figure 11.24.



    Figure 11.24. ICMP address mask request and reply.


    RFC 950 [Mogul and Postel 1985] added the address mask messages to the original ICMP specification. They enable a system to discover the subnet mask in use on a network.


    RFC 1122 forbids sending mask replies unless a system has been explicitly configured as an authoritative agent for address masks. This prevents a system from sharing an incorrect address mask with every system that sends a request. Without administrative authority to respond, a system should ignore address mask requests.


    If the global integer icmpmaskrepl is nonzero, Net/3 responds to address mask requests. The default value is 0 and can be changed by icmp_sysctl through the sysctl(8) program (Section 11.14).



    In Net/2 systems there was no mechanism to control the reply to address mask requests. As a result, it is very important to configure Net/2 interfaces with the correct address mask; the information is shared with any system on the network that sends an address mask request.



    The address mask message processing is shown in Figure 11.25.



    Figure 11.25. icmp_input function: address mask request and reply.


    247-256

    If the system is not configured to respond to mask requests, or if the request is too short, this code breaks out of the switch and passes the message to rip_input (Figure 11.15).



    Net/3 fails to increment icps_badlen here. It does increment icps_badlen for all other ICMP length errors.




    Select subnet mask


    257-267

    If the request was sent to 0.0.0.0 or 255.255.255.255, the source address is saved in icmpdst where it is used by ifaof_ifpforaddr to locate the in_ifaddr structure on the same network as the source address. If the source address is 0.0.0.0 or 255.255.255.255, ifaof_ifpforaddr returns a pointer to the first IP address associated with the receiving interface.


    The default case (for unicast or directed broadcasts) saves the destination address for ifaof_ifpforaddr.



    Convert to reply


    269-270

    The request is converted into a reply by changing icmp_type and by copying the selected subnet mask, ia_sockmask, into icmp_mask.



    Select destination address


    271-276

    If the source address of the request is all 0s ("this host on this net," which can be used only as a source address during bootstrap, RFC 1122), then the source does not know its own address and Net/3 must broadcast the reply so the source system can receive the message. In this case, the destination for the reply is ia_broadaddr or ia_dstaddr if the receiving interface is on a broadcast or point-to-point network, respectively. icmp_input puts the destination address for the reply in ip_src since the code at reflect (Figure 11.21) calls icmp_reflect, which reverses the source and destination addresses. The addresses of a unicast request remain unchanged.



    Information Query: ICMP_IREQ and ICMP_IREQREPLY


    The ICMP information messages are obsolete. They were intended to allow a host to discover the number of an attached IP network by broadcasting a request with 0s in the network portion of the source and destination address fields. A host responding to the request would return a message with the appropriate network numbers filled in. Some other method was required for a host to discover the host portion of the address.


    RFC 1122 recommends that a host not implement the ICMP information messages because RARP (RFC 903 [Finlayson et al. 1984]), and BOOTP (RFC 951 [Croft and Gilmore 1985]) are better suited for discovering addresses. A new protocol, the Dynamic Host Configuration Protocol (DHCP), described in RFC 1541 [Droms 1993], will probably replace and augment the capabilities of BOOTP. It is currently a proposed standard.



    Net/2 did respond to ICMP information request messages, but Net/3 passes them on to rip_input.




    Router Discovery: icmp_routeradvert and icmp_routersolicit


    RFC 1256 defines the ICMP router discovery messages. The Net/3 kernel does not process these messages directly but instead passes them, by rip_input, to a user-level daemon, which sends and responds to the messages.


    Section 9.6 of Volume 1 discusses the design and operation of these messages.





      Team-Fly
       

       
      Top
       


      Appendix E. Book Example Custom Actions and API Reference



      [ Team LiB ]







      Appendix E. Book Example Custom Actions and API Reference




      This appendix contains reference material for all custom actions, utility classes, and
      beans described in this book that can be used as is in other
      applications.



      Example code used in the book that isn't intended
      for reuse isn't included in this appendix. All
      source code for the book can, however, be downloaded either from the
      O'Reilly web site at http://www.oreilly.com/catalog/jserverpages3/
      or from the web site dedicated to this book at http://www.TheJSPBook.com/.



      The actions are described using the same conventions as for the JSP
      standard actions in Appendix A and the JSTL actions in Appendix B.
      Most of the custom actions accept request-time attribute values (EL
      or Java expressions), indicated by
      "Yes" in the
      "Dynamic value accepted" column in
      the Attribute tables.








        [ Team LiB ]



        Exercises








        Exercises


        Each of the fifteen core lessons in Agile Java has you build bits and pieces of a student information system for a university. I chose this single common theme to help demonstrate how you can incrementally build upon and extend existing code. Each lesson also finishes with a series of exercises. Instead of the student information system, the bulk of the exercises, provided by Jeff Bay, have you build bits and pieces of a chess application.


        Some of the exercises are involved and quite challenging. But I highly recommend that you do every one. The exercises are where the real learning startsyou're figuring out how to solve problems using Java, without my help. Doing all of the exercises will give you a second opportunity to let each lesson sink in.








          25.5 A memory-based device context



          [ Team LiB ]










          25.5 A memory-based device context


          In order to keep our rapidly animated Windows displays from flickering, we use the cMemoryDC
          objects as virtual windows, or memory bitmaps. It is not a standard MFC class like CPoint, nor is it a well-known kind of user-written class like cVector. cMemoryDC
          is a special memory device context class that the author implemented here in order to make Windows programming easier.


          The cMemoryDC
          class is a child of the standard CDC
          class. This means that we can write to a cMemoryDC
          with the same graphics methods that the CDC
          class uses to put graphics into an onscreen window or onto a printer page. And because cMemoryDC
          is a kind of CDC, we can use the powerful CDC::BitBlt
          method to do extremely fast copying from our cMemoryDC
          to a window-based CDC.


          What makes the cMemoryDC
          special is that instead of being based on some actual device, its writing area is a bitmap that lives in memory. Ten or fifteen years ago, the cMemoryDC
          approach was not practical because it requires a goodly amount of RAM for the memory bitmap � and 10 or 15 years ago, computers didn't have very much RAM. Although nowadays using a memory bitmap is becoming a fairly standard kind of trick for professional programmers, many books on Windows programming don't mention it. One exception is MFC Programming from the Ground Up, by Herbert Schildt (Osborne, 1996). Though Schildt does not encapsulate the memory-bitmap-plus-HDC technique and make a class of it as we do here, he does informally refer to the assemblage as a virtual window. Charles Petzold's classic book Programming Windows 95 (Microsoft Press, 1996) also has some discussion of the idea under the name of memory device context.


          Our Pop
          program uses a cMemoryDC
          in order to achieve smooth-looking graphics updates. The idea is to assemble the pieces of the new image in an offscreen memory bitmap. We paint the CPopView's
          _cMemDC with our background color, and then we use the cPolygon::draw
          method to paste images of the polygons on top of the background. Once all this is done, _cMemDC uses its copyTo
          method to put the fresh image onto the visible screen.


          Why not just do all this directly on the pDC that represents the onscreen window? Because it would be ugly and distracting to erase the critter bitmap images on the screen and then redraw them. You'd see drastic flicker. It's much nicer to use the _cMemDC as an offscreen drawing pad.


          Another reason to use a cMemoryDC
          is that drawing to a memory bitmap device like _cMemDC is often much faster than drawing to an onscreen bitmap like the one embodied in the actual window's CDC. The reason is that when you write to screen, you have to go through your computer's graphics card, which is usually the biggest speed bottleneck of any graphics program. When we write to a memory bitmap, we're just letting our screaming-fast CPU processor chip move bytes around in our RAM.


          So when you want to achieve a real time animation effect, the only way to go is to get your next frame ready in a cMemoryDC. The reasons are, again, that (a) it is prettier than having your users see the picture being assembled, and (b) it is faster.


          You should use a cMemoryDC
          not only in animation programs but in any program at all where you are writing a bunch of graphics to the screen in OnDraw. The reason is that if you are writing a lot of graphics it takes some time, and if the user sees this happening it looks bad. The right way to do graphics is always to keep a cMemoryDC
          for your view, and inside your OnDraw
          put all the graphics into the cMemoryDC
          and then use the cMemoryDC::copyTo
          method to blast them into the user window.


          Another use for cMemoryDC
          objects is to use one to hold an image of the background you want to use for your program. And, as we'll see below, there is a useful child class called cTransparentMemoryDC
          which is useful for holding small bitmaps to be used to represent movable objects such as game characters. A third trick is to use a large cTransparentMemoryDC
          as a foreground 'scrim' to put over your game pieces. But first we need to understand the basic use of a cMemoryDC.



          The cMemoryDC class definition


          Here's a partial, bare-bones listing of the cMemoryDC
          class definition. Because the author has worked with the cMemoryDC
          class for so long now, it's acquired a lot of bells and whistles, but we don't need to worry about those for now. See the memorydc.h code for a full listing.



          class cMemoryDC : public CDC
          {
          protected:
          CBitmap _cBitmap;
          COLORREF _blankcolor;
          int _cx, _cy;
          public:
          //Constructors and destructor
          cMemoryDC();
          cMemoryDC(int nSize, COLORREF blankcol = RGB(255, 255, 255));
          virtual ~cMemoryDC(); /* The destructor is declared to be virtual
          because cMemoryDC has a child class cTransparentMemoryDC,
          and the child's destructor is different. The methods that
          differ between parent and child are also declared virtual. */
          //Accessors
          int cx(){return _cx;}
          int cy(){return _cy;}
          //Mutators
          void clear();
          void setBlankColor(COLORREF blankcol);
          //Blt Methods
          virtual void copyTo(CDC *pDC, const CRect &rect);
          };


          The size of a class object


          How much size might a cMemoryDC
          object take up? One might think that maybe a class object had to carry around pointers to its methods, and maybe a class object's size is larger than the sum of its data field sizes. But this is wrong. The C++ compiler keeps the names of a class's methods straight in some global class-method pointer tables that it builds. So a class object isn't responsible for keeping track of its function pointers, and a class
          is no larger than a struct
          with the same data.


          So now our question is: how many bytes are used up by a cMemoryDC
          object's data fields?


          Well, as a child of the CDC
          class, a cMemoryDC
          inherits the CDC
          data fields which happen to be two 'HDC
          handles' called m_hDC
          and m_hAttribDC. (In all ordinary situations these two handles are the same, and we don't bother mentioning the second one.) Now a 'handle' is really just an int
          that the Windows operating system uses internally as a kind of half-baked pointer. So the size of the data inside a CDC
          is the same as the size of two integers. Since we're using 32-bit integers, this means four bytes per integer, so we have a total of eight bytes in a CDC.


          Now we add in the size of the cMemoryDC's
          own data fields. How big is the CBitmap
          member? Like a CDC, a CBitmap
          is a 'shallow wrapper' around a Windows handle, in this case an 'HBITMAP
          handle' that, once again, is really just an int. So we pick up four bytes here. The COLORREF
          is also an int
          -sized object, so here's another four bytes. And we get eight more bytes out of the two int
          _cx, _cy.


          Adding it up, we get 24 bytes in all.


          Is a cMemoryDC
          a lightweight object in terms of memory demands? Yes and no. Yes, there's a small amount of data in the fields of the cMemoryDC. But no, it isn't really lightweight because a cMemoryDC's
          CBitmap
          field is likely to hold the handle of an HBITMAP
          which represents a pixel for pixel copy of your whole screen. And as we'll see in Size of a Bitmap section below, this can run into several Meg of data.



          Declaration and construction of a cMemoryDC


          Usually we will want to have cMemoryDC
          for each of our views. Even if two views are showing the same data, we will normally want to size the data display to be an appropriate fit for the view's size. An exception to this would be a program in which we wish to work with a graphic image of some fixed size and simply let the views show different pieces of the image; in this case we'd put the cMemoryDC
          inside the document class. But for the rest of this preliminary discussion we'll assume we're putting it inside the view.


          You can have your cMemoryDC
          be a simple class member or you can have it be a pointer member. If it's a simple member you declare it with a line like cMemoryDC _cMemDC and you construct it with a line like _memDC(CMEMDC_FULLSCREEN, blankcolor);. The CMEMDC_FULLSCREEN parameter tells the constructor to make the cMemoryDC
          have as many pixels as a full-screen window. The blankcolor parameter tells the cMemoryDC
          to use a background color of blankcolor. If you just want a white background you can leave out the blankcolor argument.


          You can also give a CMEMDC_ONEPIXEL argument into the first slot of the constructor if you want a cMemoryDC
          that's only one pixel big. The default constructor with no arguments also makes a single-pixel cMemoryDC, by the way. The purpose of the single-pixel cMemoryDC
          s is that they are used for loading bitmaps to be used for background images or for character icons. When a cMemoryDC
          loads a bitmap it can dynamically resize itself to the size of the bitmap.


          It's worth looking at what happens inside a call to the constructor cMemoryDC(CMEMDC_FULLSCREEN, blankcol)
          so we get an idea of how the cMemoryDC
          works. Keep in mind that the members of a cMemoryDC
          which need initialization are the CBitmap _cBitmap, the COLORREF _blankcolor, and the two int _cx, _cy. As we already mentioned above, every CDC
          has an HDC m_hDC
          member, so as a child of the CDC
          class, a cMemoryDC
          has an HDC m_hDC
          which must be initialized as well. This initialization is accomplished implicitly by a call to CreateCompatibleDC.


          Here are the principal code blocks that are executed in the cMemoryDC(CMEMDC_FULLSCREEN, blankcol)
          call, with a short comment on each one.



          CDC cDC_display;
          cDC_display.CreateDC("DISPLAY", NULL, NULL, NULL);

          The purpose of the temporary CDC
          object cDC_display is to provide a role model object of what a CDC
          should be like in the runtime environment where this cMemoryDC
          is going to be used. The cDC_display gets destroyed when it goes out of scope at the end of the constructor's code.



          CreateCompatibleDC(&cDC_display);

          This is the line that initializes our cMemoryDC
          's 'shallowly wrapped' HDC m_hDC
          field. The call makes our cMemoryDC
          be a CDC
          compatible with the screen. Each CDC
          will have selected into it a CBitmap
          bitmap object of a certain area. The CreateCompatibleDC
          method only makes our cMemoryDC
          compatible with the screen, but does not give it a bitmap as large as the screen. It's worth noting that in Windows, a 'normal' device context such as one that you get from a window never has any interesting bitmap associated with it. These 'normal' CDC
          always have an empty bitmap, and they don't use it at all. But the CreateCompatibleDC
          call is designed for creating memory-based device contexts. Although the default CDC
          constructor gives a CDC
          an empty CBitmap
          tool, the CreateCompatibleDC
          call gives the CDC
          a CBitmap
          that is one pixel big. It's not much, but it's something. It's up to us to replace this bitmap with one the size of the screen. So now we figure out the size of the screen.



          _cx = GetSystemMetrics(SM_CXFULLSCREEN);
          _cy = GetSystemMetrics(SM_CYFULLSCREEN) � GetSystemMetrics(SM_CYMENU);

          Calling GetSystemMetrics
          with the SM_C?FULLSCREEN
          arguments gives the actual size of a full screen client window's screen measured in pixels. This assumes the window has a caption. We subtract off the region for the menu. Now we make the bitmap that we need.



          _cBitmap.CreateCompatibleBitmap(&cDC_display, _cx, _cy))

          It is important to use the screen-based cDC_display as the argument to the CreateCompatibleBitmap
          call. (If you try and use your memory device context *this as the argument instead, you'll get a monochrome bitmap!) Another thing to note here is that CreateCompatibleBitmap
          is a kind of a memory allocation call, in that it's going to look for enough memory to hold a bitmap the size of _cx * _cy. Conceivably it might fail, so in our constructor code we're careful to check if this call is successful. Assuming it is, we now select the newly created _cBitmap into our cMemoryDC.



          SelectObject(&_cBitmap);

          And now our cMemoryDC
          is a screen-compatible CDC
          with an effective area as big as a full screen window. From now on, anything that we write to the cMemoryDC
          goes into the bitmap, and anything that we put into the bitmap appears in the cMemoryDC. (Although we don't mention this in the code printed here, we also need to do a DeleteObject
          on the single-pixel CBitmap
          that gets 'unselected' by the SelectObject
          call.)



          Size of a bitmap


          How much RAM memory is a bitmap like _cBitmap going to use? Software engineers frequently have to talk about memory usage, and it's good to get fast at estimating it.


          If your computer is running graphics in the lowest resolution mode, it has 640 x 480 pixels which you can round off in your head to 600 * 500 pixels, which is 300,000 or 300 K. If you are using the common 256 color mode, then you're using one byte of data per pixel. So that means 300 K bytes for a low resolution 256 color bitmap. 300 K is only a third of a Meg, which is no sweat compared to the many Megs of RAM that you're likely to have.


          A lot of people use the 800 x 600 with 256 colors mode; here the bitmap is 480 K, or about half a Meg, still no big deal. If you go up to 24-bit color in a 'megapixel' mode of 1200 x 1000 you might end up needing 4 Meg per bitmap, which is still okay on most modern machines. But if you push your resolution high enough and use a lot of bitmaps, you may find a point where your RAM starts to suffer.


          When Windows can't find enough RAM for a bitmap it will usually store the bitmap on the hard drive rather than returning an error from the CreateCompatibleBitmap call. This is good in that it means your program doesn't crash, but it's bad in that your program's behavior turns ugly once it starts using disk-based bitmaps.


          The reason is that using a disk-based bitmap means lots of thrashing of your hard disk every time you uncover or resize a window onscreen. If your program switches to disk-based bitmaps you will notice a disturbing grinding sound from your hard drive every time you move your windows around.


          But even with a low amount of RAM, you can almost always afford two or three screen-sized bitmaps. And you can have lots of small, icon-sized bitmaps. You'll only tend to find yourself running out of RAM for the cMemoryDC
          if you open up, say 20 or 30 different documents and/or views at once.



          Writing to the cMemoryDC in OnDraw


          The general principle of using the cMemoryDC
          class is that whenever we want to write something to the screen, we instead write it to our cMemoryDC _cMemDC, and then use the cMemoryDC::copyTo
          method to send the image to the screen. The exception is when we're printing; in this case we don't worry about flicker and we write directly to the print CDC
          (which will either be the printer or an image inside the Print Preview window). As usual we can use the CDC::IsPrinting()
          method to distinguish between the two cases, and our CView::OnDraw(CDC *pDC)
          code could look something like this.



          if (!pDC->IsPrinting()) //The standard onscreen window case
          {
          //Put code here to draw your image into the _cMemDC... And then:
          _cMemDC.copyTo(pDC);
          }
          else //The Print or Print Preview case
          //Put code here to draw your image directly to the pDC...

          When we are not involved in printing, we write to the screen in a two-step process. The first step is to assemble our image in the cMemoryDC, and the second step is to copy the cMemoryDC
          to the onscreen CDC.


          The way we'll carry out our first step is that we'll write some kind of background image into our cMemoryDC, and then we'll write the images of our objects on top of it. In the case of the Pop
          program we use the simplest kind of background: we simply erase whatever was in the cMemoryDC
          and fill the image with the background color. This is encapsulated in our cMemoryDC::clear()
          method, which creates and selects a CBrush
          of color _blankcolor
          and then uses the PatBlt
          method to paint the whole cMemoryDC
          with the brush with the following line. (See the subsection below for information about PatBlt.)



          PatBlt(0, 0, _cx, _cy, PATCOPY);

          Once we've fixed our background, we put our graphics into the cMemoryDC.


          And then we're ready for the second step of the OnDraw
          process, of copying the cMemoryDC
          to the screen. We do this in the line _cMemDC.copyTo(pDC). This call to the cMemoryDC::copyTo
          function in turn calls the following line.



          pDC->BitBlt(0, 0, _cx, _cy, &_cMemDC, SRCCOPY);

          Note that in this BitBlt
          call, the 'target' pDC goes on the left and the 'source' &_cMemDC goes inside the BitBlt
          arguments. More about the BitBlt
          method is in the subsection below.



          The BitBlt function


          The CDC::BitBlt
          method is designed to move a rectangular block of pixel data from a source CDC
          to a target CDC. The CDC
          which calls the BitBlt
          method is the target; in effect, the caller CDC
          is saying 'copy a block of pixels to me.' You are allowed to specify the location and size of the target rectangle that you want to copy to, as well as the location of the source rectangle you're copying from. Since BitBlt
          does a one-pixel-to-one-pixel copy, the size of the source is the same as the size of the target. The prototype looks like this.



          BOOL CDC::BitBlt( int x, int y, int nWidth, int nHeight, CDC* pSrcDC, int xSrc,
          int ySrc, int dwRop ); 


          The arguments represent: the left upper corner and the horizontal and vertical extent of the target rectangle within the target HDC, the source CDC, the upper left corner of the source rectangle in the source CDC, and the write method. There are 14 write methods called ROP codes for 'Raster OPeration', where 'raster' is a word meaning an orderly grid, such as the one pixels are arranged in. The most natural ROP method is called SRCCOPY. The other ROP methods form various logical combinations of the source pixels, the target pixels, and the active brush-pattern pixels.


          In general the time it takes for a BitBlt
          to execute is directly proportional to the number of pixels moved, and this 'pixel area' is proportional to the product of the linear dimensions of the window. In other words, if you make your window twice as big, your BitBlt
          will run four times as slow. This is why one so often sees things like onscreen video being shown in very small windows.



          The PatBlt function


          PatBlt
          is a special kind of BitBlt
          function that doesn't use a source CDC. Its prototype is like this.



          BOOL PatBlt( int x, int y, int nWidth, int nHeight, int dwRop ); 


          PatBlt
          takes the CDC
          's currently selected CBrush
          and uses it to write over a specified target rectangle. Here the most commonly used ROP code is PATCOPY, which means to copy the brush pattern or color.


          Let's take another look at our call to _cMemDC.copyTo(pDC) which gets turned into pDC->BitBlt(0, 0, _cx, _cy, &_cMemDC, SRCCOPY). The _cx and _cy are the size of a full screen, so mightn't this code be inefficient if our window is smaller than the screen? Actually it doesn't matter because the pDC that's fed into the OnDraw
          function has a clipping region set to the size of the 'damaged' rectangle that it needs to repaint. The BitBlt
          will actually only try and do the pixel copying for the points that lie within the pDC clipping rectangle.


          In an animated program, at each step the entire window will need to be repainted, and that's how big the clipping rectangle will be. If all you've done is to uncover a small corner of the window in a non-animated program, then that corner will be the clipping rectangle.



          Calling the OnDraw


          There's one final thing to remember to do when you use a cMemoryDC
          inside your OnDraw
          function. You either need to use the Invalidate(FALSE) call instead of the Invalidate(), or, better, you need to override the OnEraseBkgnd
          to do nothing, like this.



          BOOL CPopView::OnEraseBkgnd(CDC* pDC)
          {
          /* We normally don't want to erase the background because our onDraw
          will cover it up with the _cMemDC copyTo. If we did erase the
          background, we'd get flicker. This is also true with OpenGL. */
          return TRUE;
          //Don't call baseclass method, CView::OnEraseBkgnd(pDC);
          }

          The argument to the CView::Invalidate(BOOL eraseflag)
          method specifies whether or not you should call OnEraseBkgnd, which normally will erase the screen with a hidden Windows background brush before writing to the screen in the OnDraw
          function. When we are refreshing the screen with a BitBlt
          of a cMemoryDC, we don't need to erase it. The reason for this is that the copyTo(pDC) call is going to 'erase' the screen anyway; that is, it's going to cover the screen over with a copy of whatever image you've drawn into the cMemoryDC.


          Now you might think it's harmless to go ahead and erase the screen anyway, but far from it. If you erase the screen, it's momentarily white, and your eye is going to pick up a flicker. And then all our work with the cMemoryDC
          is for nothing. But of course if we've overridden OnEraseBkgnd
          to do nothing, then calling it will do nothing. (If you didn't bother to override OnEraseBkgnd, you could partly avoid the flicker by feeding a FALSE argument into your Invalidate
          calls, but it turns out that resizing the screen would still call Invalidate(TRUE) and give you your flicker.)


          Let's sum up the use of the cMemoryDC.



          • Declare a cMemoryDC _cMemDC member of your CView
            class.


          • Initialize _cMemDC with a call to cMemDC(CMEMDC_FULLSCREEN) in the CView
            constructor.


          • In CView::OnDraw
            draw graphics into the _cMemDC, possibly using the _pMemDC>clear() to erase the old _cMemDC image.


          • In CView::OnDraw
            use _cMemDC.copyTo(pDC) to copy the image to the onscreen CDC.


          • Override CView::OnEraseBkgnd(CDC *pDC)
            to do nothing at all but return TRUE;. If you forget this, your view will flicker when you call Invalidate(), which by default calls Invalidate(TRUE). Also the view will flicker when you resize the window, as the resizing code automatically calls Invalidate(TRUE), which forces a call to OnEraseBkgnd.







            [ Team LiB ]