?? chapter6.html
字號:
<html><head><title>Writing Bug-Free C Code: Designing Modules</title></head><body><center><font size="+3">Chapter 6: Designing Modules</font><br><a href="index.html">Writing Bug-Free C Code</a><br></center><br><center><table><tr><td valign=top><small><a href="#smallvslarge">6.1 Small versus Large Projects</a><br><a href="#thekey">6.2 The Key</a><br><a href="#whatis">6.3 What Is a Module?</a><br><a href="#designing">6.4 Designing a Class Implementation Module</a><br><small></td><td width=30> </td><td valign=top><small><a href="#usemodel">6.5 The USE_* Include File Model</a><br><a href="#coding">6.6 Coding the Module</a><br><a href="#sample">6.7 A Sample Module</a><br><a href="#summary">6.8 Chapter Summary</a><br></small></td></tr></table></center><br><br>What is a module? Is it simply a source file that contains functions? In a high-level sense, yes, but it is also much more. Anyone can write some functions, place them in a file and call them a module. For me, a module is the result of applying a well-founded set of techniques to solving a problem.<br><a name="smallvslarge"><br></a><big><b>6.1 Small versus Large Projects</b></big> <br><br>How are small projects usually designed? It has been my experience that small projects are usually written by one person over the course of several hours to several days. In this environment, there is no real concern given to splitting the project into several well-defined source files. More than likely, the small project ends up simply being one source file. <br><br>For small projects, this one source file design works quite well, but what about large projects with hundreds of thousands of lines of code and hundreds of source files? <br><br>The challenge in working on any project is applying a well founded set of techniques from day one, because every project, no matter how big it is today, started out as a small project.<br><a name="thekey"><br></a><big><b>6.2 The Key</b></big> <br><br>The key to successfully coding a hierarchy of modules is that you must always code what to do, not how to do it. <br><br>In other words, implement the solution to a problem once, not multiple times. A simple, but effective example is copying a string from one location to another. Do you use while (*pDst++=*pSrc++); or do you use strcpy(pDst, pSrc);? This may seem like an absurd example, but study it carefully. The while loop is specifying how to copy a string from one location to another, while strcpy() is specifying what to do, leaving the how to strcpy(). <blockquote><table bgcolor="#E0E0E0" border=1 cellpadding=2 cellspacing=0><tr><td> The key is to always code <i>what</i> to do, not <i>how</i> to do it. </tr></td></table></blockquote>This point is so key that I will repeat it. The while loop is specifying how to copy a string from one location to another, while the strcpy() is specifying what to do, leaving the how to strcpy(). <br><br>I chose strcpy() on purpose because I do not know anyone who would argue for using the while loop instead of strcpy(). Why is the choice of which one to use so obvious? <br><br>More than likely, some of the code you have written contains code fragments that specify how to do something instead of specifying what to do. <br><br><b>6.2.1 An Example</b> <br><br>Let me give you a prime example that comes straight from programming in a GUI environment. In a dialog box, there exists an edit control that contains text that the user can edit, but suppose you want to limit the number of characters that the user can type. How do you do it? In Microsoft Windows, you send a message to the edit control, informing it of the text limit. This is done with the SendDlgItemMessage() function and the EM_LIMITTEXT message. <br><br><table bgcolor="#CCCCEE" cellpadding=0 cellspacing=0><tr><td><pre><b>Limiting text size in a Microsoft Windows edit control</b>SendDlgItemMessage( hDlg, nID, EM_LIMITTEXT, wSize, 0L );</pre></tr></td></table> <br>So, whenever you want to limit the size of an edit control, you call SendDlgItemMessage(), right? I do not think so! <br><br>The problem is that by calling SendDlgItemMessage() directly, you are specifying how to limit the text size. <br><br>The solution is to code a new function, let's call it EmLimitText(), that calls SendDlgItemMessage(). Whenever you want to limit the text size of an edit control, you call EmLimitText() instead of SendDlgItemMessage(). <br><br><table bgcolor="#CCCCEE" cellpadding=0 cellspacing=0><tr><td><pre><b>EmLimitText function</b>void APIENTRY EmLimitText( HWND hDlg, int nID, WORD wSize ){ SendDlgItemMessage( hDlg, nID, EM_LIMITTEXT, wSize, 0L );} /* EmLimitText */</pre></tr></td></table> <br>By doing this, you now have the basis for an entire module. For all messages that can be sent to edit controls, provide code wrappers that specify what to do and let the functions call SendDlgItemMessage(), which specify how to do it. <br><br><b>6.2.2 Another Example</b> <br><br>The class methodology is a prime example of specifying what to do and not how to do it. Consider what coding is like without the class methodology. It is up to each and every module to directly access a data object to perform whatever action is necessary. By directly accessing the object, the code is specifying how to perform the action. <br><br>The class methodology forces modules to use method functions to perform an action upon the object. The code is specifying what do to, leaving the how up to the method function. <br><br><b>6.2.3 The Advantages</b> <br><br>By far the single biggest advantage of using the technique of specifying what to do instead of how to do it is that it allows you to radically change the how (the implementation), without having to change the what (the function calls). <br><br>Another advantage of this technique is that it allows you to make changes to the implementation and recompile only one source file. Since the function interface remains the same, no other source files have to be recompiled. <br><br>The turnaround time in making a change and testing it are also dramatically reduced because only one source file needs to be recompiled instead of tens or hundreds. <br><br>Another benefit of this technique is that code size is reduced. Instead of repeatedly spelling out in code how to do something, you are now making a function call, which in most cases reduces the code size. <br><br><b>6.2.4 The Goal</b> <br><br>The goal in using this technique in your programs is to reach the point at which implementing new functionality is simply a matter of making a few function calls. This is obviously ideal and does not happen all the time, but the more this technique is used, the more frequently it starts to happen. <br><br><b>6.2.5 Conclusion</b> <br><br>The key to this technique is to always code what to do, not how to do it. In other words, you never want to reinvent the wheel. Instead, implement it once and be done with it! Be careful, however, that you design a solid interface that stands up to the test of time. You do not want to change the interface later, since this requires all modules that use the interface to change as well. <br><br>This technique is easy to use when the implementation of something is hundreds of lines long and the obvious choice is to make a function call. However, the real power of this technique comes when it is used extensively in a project, even for implementations that are several lines to one line long. It takes a lot of discipline to use the technique in these cases, but the payoff comes the next time when all you need is a function call.<br><a name="whatis"><br></a><big><b>6.3 What Is a Module?</b></big> <br><br>A module is a source file with well-defined entry points, or method functions (methods), that can be called by other modules. It is the result of applying a well-founded set of techniques to solving a problem. There are primarily two types of modules: code wrapper modules and class implementation modules. Some code wrapper modules also implement a class and hence are considered class implementation modules as well. <br><br>Code wrapper modules provide functions that are primarily code wrappers. Most code wrapper functions are independent and stand on their own. Because of this, they can be moved to another module without any compilation problems. Functions are grouped in a module because they provide similar functionality. <br><br>An example is the heap manager module. It sits on top of C's memory allocation routines to provide a cleaner, more robust interface into the system. <br><br>Another example is a module that provides code wrappers around all messages that can be sent to edit controls in a GUI environment. An example is using EmLimitText() instead of calling SendDlgItemMessage() directly.<br><a name="designing"><br></a><big><b>6.4 Designing a Class Implementation Module</b></big> <br><br>Class implementation modules provide functions that implement a class. The functions must be present in the module for a successful compilation. Functions are grouped in a module because the functions, as a whole, implement the class. <br><br>An example of a class implementation module is the random number generator discussed in <a href="chapter4.html#randomexample">§4.7</a>. <br><br><b>6.4.1 The Interface or API</b> <br><br>By far the toughest and most important part of designing a module is designing the API (Application Programmer's Interface) to the new module. <br><br>Once an API is chosen and implemented, it is not likely to change much over time. This is because as a module gets used more and more by other modules, a change in the API is expensive in terms of the time and effort required to implement and debug the change. <blockquote><table bgcolor="#E0E0E0" border=1 cellpadding=2 cellspacing=0><tr><td> An API must be designed right the first time because changes to an API are costly. </tr></td></table></blockquote>The first step in creating a new class implementation module API is deciding upon a handle name that is used to refer to objects created by the API. The handle name must begin with an H and be in uppercase. A name is chosen so that it is obvious, unique, relatively short and has a good mixed case name, usually spelled the same as the handle. You also need a module name so that functions of the module follow the module/verb/noun naming convention. <br><br>In the random number generator, HRAND was chosen as the handle name. It is pretty obvious, unique from all other handle names and relatively short. The variable name used to refer to objects of the class is hRand. Rand is the module name used to prefix method functions, as in RandCreate() and RandDestroy(). <br><br>Suppose we are creating a class module that provides a code wrapper around opening, reading, writing and closing operating system files. In this case, the code wrapper module is a class implementation module. I would choose a handle name of HDOSFH (Handle to the DOS File Handle), a variable name of hDosFh, a module name of Dos and method function names of DosOpenFile(), DosRead(), DosWrite() and DosCloseFile(). <br><br>All modules that implement a class have at least two method functions: one method function that creates the object (for example, RandCreate() and DosOpenFile()) and another method function that destroys the created object (for example, RandDestroy() and DosCloseFile()). <br><br>An important concept of class modules is that the method functions act upon dynamically allocated objects, objects that are created and destroyed using method functions. The method functions do not act upon static objects. <br><br><b>6.4.2 An Interface Specification Template</b> <br><br>The interface specification for new class modules follows a general template. First of all there is a NEWHANDLE(HOBJ) declaration that is at the top of a global include file. This adds a new type checkable data type, named HOBJ, into the system. It is declared near the top of the include file, next to all other NEWHANDLE() declarations, so that, if needed, HOBJ may be used in the prototypes of other USE_ sections. <br><br>The NEWHANDLE(HOBJ) declaration is not included in the USE_HOBJ section because to use only the HOBJ data type in other USE_ prototype sections would require the entire USE_HOBJ section to be included first. Why include the entire section when all that is needed is access to NEWHANDLE(HOBJ)? <br><br><table bgcolor="#CCCCEE" cellpadding=0 cellspacing=0><tr><td><pre><b>Template interface for a new module</b>NEWHANDLE(HOBJ);...#ifdef USE_HOBJ/*-------------------------------------------------------------- * * Short one line description of module * *-------------------------------------------------------------*/EXTERNC HOBJ APIENTRY ObjCreate ( arguments );EXTERNC type APIENTRY ObjMethodName1 ( HOBJ, arguments );EXTERNC type APIENTRY ObjMethodName2 ( HOBJ, arguments );...EXTERNC type APIENTRY ObjMethodNameN ( HOBJ, arguments );EXTERNC HOBJ APIENTRY ObjDestroy ( HOBJ );#endif</pre></tr></td></table> <br><br>The function prototypes for the method functions of the class appear later in the include file. A create method that returns a handle to the created object is present along with all other method functions for the class (which expect the object handle as the first argument). By convention, NULL is always returned by the ObjDestroy() method. The arguments and return type of all other method functions depend upon the class implementation. Usage of APIENTRY is discussed later in this chapter. <br><br>Notice that the module prototypes are enclosed within an #ifdef / #endif section. This implements the USE_* include file model, which is discussed next.<br><a name="usemodel"><br></a><big><b>6.5 The USE_* Include File Model</b></big> <br><br>When I first started coding in C, I followed the traditional technique of placing data declarations and function prototypes in a separate include file and explicitly including it in whatever source files needed access to the information.
?? 快捷鍵說明
復制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -