⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 2control.html

📁 Visual C++ has been one of most effective tool for the large industrial applications. This book is t
💻 HTML
📖 第 1 页 / 共 2 页
字号:
    {}
    bool IsCtrl () const { return (_data & MK_CONTROL) != 0; }
    bool IsShift () const { return (_data & MK_SHIFT) != 0; }
    bool IsLButton () const { return (_data & MK_LBUTTON) != 0; }
    bool IsMButton () const { return (_data & MK_MBUTTON) != 0; }
    bool IsRButton () const { return (_data & MK_RBUTTON) != 0; }
private:
    WPARAM    _data;
};</pre>
</td></tr></table><!-- End Code -->

<p>The methods of <var>Win::KeyState</var> return the state of the control and shift keys and the state of the left, middle and right mouse buttons. For instance, if you move the mouse while you press the left button and the shift key, both <var>IsShift</var> and <var>IsLButton</var> will return <var>true</var>.

<p>In WinMain, where the window is created, we initialize our controller and pass it to <var>Win::Maker::Create</var> along with the window's title.

<!-- Code --><table width=100% cellspacing=10><tr>    <td class=codetable>
<pre>        TopController ctrl;
        win.Create (ctrl, "Simpleton");
</pre>
</td></tr></table><!-- End Code -->

<p>This is the modified <var>Create</var>. It passes the pointer to <var>Controller</var> as the user-defined part of window creation data--the last argument to <var>CreateWindowEx</var>.

<!-- Code --><table width=100% cellspacing=10><tr>    <td class=codetable>
<pre>HWND Maker::Create (Controller &amp; controller, char const * title)
{
    HWND hwnd = ::CreateWindowEx (
        _exStyle,
        _className,
        title,
        _style,
        _x,
        _y,
        _width,
        _height,
        _hWndParent,
        _hMenu,
        _hInst,
        &amp;<b>controller</b>);

    if (hwnd == 0)
        throw "Internal error: Window Creation Failed.";
    return hwnd;
}</pre>
</td></tr></table><!-- End Code -->

<p>To summarize, the controller is created by the client and passed to the <var>Create</var> method of <var>Win::Maker</var>. There, it is added to the creation data, and Windows passes it as a parameter to <var>WM_NCREATE</var> message. The window procedure unpacks it and stores it under <var>GWL_USERDATA</var> in the window's internal data structure. During the processing of each subsequent message, the window procedure retrieves the controller from this data structure and calls its appropriate method to handle the message. Finally, in response to <var>WM_DESTROY</var>, the window procedure calls the controller one last time and unplugs it from the window.

<p>Now that the mechanics of passing the controller around are figured out, let's talk about the implementation of <var>Controller</var>. Our goal is to concentrate the logic of a window in this one class. We want to have a generic window procedure that takes care of the ugly stuff--the big switch statement, the unpacking and re-packing of message parameters and the forwarding of the messages to the default window procedure. Once the message is routed through the switch statement, the appropriate <var>Controller</var> method is called with the correct (strongly-typed) arguments.

<p>For now, we'll just create a stub of a controller. Eventually we'll be adding a lot of methods to it--as many as there are different Windows messages. 
<p>The controller stores the handle to the window it services. This handle is initialized inside the window procedure during the processing of <var>WM_NCCREATE</var>. That's why we made <var>Win::Procedure</var> a friend of <var>Win::Controller</var>. The handle itself is protected, not private--derived classes will need access to it. There are only two message-handler methods at this point, <var>OnDestroy</var> and <var>OnMouseMove</var>. 

<!-- Code --><table width=100% cellspacing=10><tr>    <td class=codetable>
<pre>namespace Win
{
    class Controller
    {
        friend LRESULT CALLBACK Procedure (HWND hwnd, 
                        UINT message, WPARAM wParam, LPARAM lParam);

        void SetWindowHandle (HWND hwnd) { _h = hwnd; }
    public:
        virtual ~Controller () {}
        virtual bool OnDestroy () 
            { return false; }
        virtual bool OnMouseMove (int x, int y, KeyState kState) 
            { return false; }
    protected:
        HWND  _h;
    };
}</pre>
</td></tr></table><!-- End Code -->

<p>You should keep in mind that <var>Win::Controller</var> will be a part of the library to be used as a base class for all user-defined controllers. That's why all message handlers are declared virtual and, by default, they return <var>false</var>. The meaning of this Boolean is, "I handled the message, so there is no need to call <var>DefWindowProc</var>." Since our default implementation doesn't handle any messages, it always returns <var>false</var>.

<p>The user is supposed to define his or her own controller that inherits from <var>Win::Controller</var> and overrides some of the message handlers. In this case, the only message handler that has to be overridden is <var>OnDestroy</var>--it must close the application by sending the "quit" message. It returns <var>true</var>, so that the default window procedure is not called afterwards.

<!-- Code --><table width=100% cellspacing=10><tr>    <td class=codetable>
<pre>class TopController: public Win::Controller
{
public:
    bool OnDestroy ()
    {
        ::PostQuitMessage (0);
        return true;
    }
};</pre>
</td></tr></table><!-- End Code -->

<p>To summarize, our library is designed in such a way that its client has to do minimal work and is protected from making trivial mistakes. For each class of windows, the client has to create a customized controller class that inherits from our library class, <var>Win::Controller</var>. He implements (overrides) only those methods that require non-default implementation. Since he has the prototypes of all these methods, there is no danger of misinterpreting message parameters. This part--the interpretation and unpacking--is done in our Win::Procedure. It is written once and for all, and is thoroughly tested.

<p>This is the part of the program that is written by the client of our library. In fact, we will simplify it even more later.

<p><i>Is it explained that the result of assignment can be used in an expression?</i>

<!-- Code --><table width=100% cellspacing=10><tr>    <td class=codetable>
<pre>#include "Class.h"
#include "Maker.h"
#include "Procedure.h"
#include "Controller.h"

class TopController: public Win::Controller
{
public:
    bool OnDestroy ()
    {
        ::PostQuitMessage (0);
        return true;
    }
};

int WINAPI WinMain
   (HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR cmdParam, int cmdShow)
{
    char className [] = "Simpleton";
    Win::ClassMaker winClass (className, hInst);
    winClass.Register ();
    Win::Maker maker (className, hInst);
    TopController ctrl;
    Win::Dow win = maker.Create (ctrl, "Simpleton");
    win.Display (cmdShow);

    MSG  msg;
    int status;
    while ((status = ::GetMessage (&amp; msg, 0, 0, 0)) != 0)
    {
        if (status == -1)
            return -1;
        ::DispatchMessage (&amp; msg);
    }
    return msg.wParam;
}</pre>
</td></tr></table><!-- End Code -->

<p>Notice that we no longer have to pass window procedure to class maker. Class maker can use our generic <var>Win::Procedure</var> implemented in terms of the interface provided by our generic <var>Win::Controller</var>. What will really distinguish the behavior of one window from that of another is the implementation of a controller passed to <var>Win::Maker::Create</var>.

<p>The cost of this simplicity is mostly in code size and in some minimal speed deterioration. 
<p>Let's start with speed. Each message now has to go through parameter unpacking and a virtual method call--even if it's not processed by the application. Is this a big deal? I don't think so. An average window doesn't get many messages per second. In fact, some messages are queued in such a way that if the window doesn't process them, they are overwritten by new messages. This is for instance the case with mouse-move messages. No matter how fast you move the mouse over the window, your window procedure will not choke on these messages. And if a few of them are dropped, it shouldn't matter, as long as the last one ends up in the queue. Anyway, the frequency with which a mouse sends messages when it slides across the pad is quite arbitrary. With the current processor speeds, the processing of window messages takes a marginally small amount of time.
<p>Program size could be a consideration, except that modern computers have so much memory that a megabyte here and there doesn't really matter. A full blown <var>Win::Controller</var> will have as many virtual methods as there are window messages. How many is it? About 200. The full vtable will be 800 bytes. That's less than a kilobyte! For comparison, a single icon is 2kB. You can have a dozen of controllers in your program and the total size of their vtables won't even reach 10kB. 
<p>There is also the code for the default implementation of each method of <var>Win::Controller</var>. Its size depends on how aggressively your compiler optimizes it, but it adds up to at most a few kB.
<p>Now, the worst case, a program with a dozen types of windows, is usually already pretty complex--read, large!--plus it will probably include many icons and bitmaps. Seen from this perspective, the price we have to pay for simplicity and convenience is minimal. 

<h3>Exception Specification</h3>

<p>What would happen if a Controller method threw an exception? It would pass right through our <var>Win::Procedure</var>, then through several layers of Windows code to finally emerge through the message loop. We could, in principle catch it in <var>WinMain</var>. At that point, however, the best we could do is to display a polite error message and quit. Not only that, it's not entirely clear how Windows would react to an exception rushing through its code. It might, for instance, fail to deallocate some resources or even get into some unstable state. The bottom line is that Windows doesn't expect an exception to be thrown from a window procedure.
<p>We have two choices, either we put a <var>try</var>/<var>catch</var> block around the switch statement in <var>Win::Procedure</var> or we promise not to throw any exceptions from Controller's methods. A <var>try</var>/<var>catch</var> block would add time to the processing of every single message, whether it's overridden by the client or not. Besides, we would again face the problem, what to do with such an exception. Terminate the program? That seems pretty harsh! On the other hand, the contract not to throw exceptions is impossible to enforce. Or is it?!

<p>Enter exception specifications. It is possible to declare what kind of exceptions can be thrown by a function or method. In particular, we can specify that no exceptions can be thrown by a certain method. The declaration:
<!-- Code --><table width=100% cellspacing=10><tr>	<td class=codetable>
<pre>virtual bool OnDestroy () throw ();</pre>
</td></tr></table><!-- End Code -->
<p class="continue">promises that <var>OnDestroy</var> (and all its overrides in derived classes) will not throw any exceptions. The general syntax is to list the types of exceptions that can be thrown by a procedure, like this:
<!-- Code --><table width=100% cellspacing=10><tr>	<td class=codetable>
<pre>void Foo () throw (bad_alloc, char *);</pre>
</td></tr></table><!-- End Code -->

<p>How strong is this contract? Unfortunately, the standard doesn't promise much. The compiler is only obliged to detect exception specification mismatches between base class methods and derived class overrides. In particular, the specification can be only made stronger (fewer exceptions allowed). There is no stipulation that the compiler should detect even the most blatant violations of this promise, for instance an explicit <var>throw</var> inside a method defined as <var>throw()</var> (throw nothing). The hope, however, is that compiler writers will give in to the demands of programmers and at least make the compiler issue a warning when an exception specification is violated. Just as it is possible for the compiler to report violations of <var>const</var>-ness, so it should be possible to track down violations of exception specifications.

<p>For the time being, all that an exception specification accomplishes in a standard-compliant compiler is to guarantee that all unspecified exceptions will get converted to a call to the library function <var>unexpected ()</var>, which by default terminates the program. That's good enough, for now. Declaring all methods of <var>Win::Controller</var> as "throw nothing" will at least force the client who overrides them to think twice before allowing any exception to be thrown. 

<h3>Cleanup</h3>

<p>It's time to separate library files from application files. For the time being, we'll create a subdirectory "lib" and copy all the library files into it. However, when the compiler compiles files in the main directory, it doesn't know where to find library includes, unless we tell it. All compilers accept additional include paths. We'll just have to add "lib" to the list of additional include paths. As part of the cleanup, we'll also move the definition of <var>TopController</var> to a separate file, <i>control.h</i>.

</table>
</html>

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -