IT people's Paradise

  DonewsBlog  |  Donews首页  |  Donews社区  |  Donews邮箱  |  我的首页  |  联系作者  |  聚合   |  登录
  20篇文章 :: 0篇收藏:: 12篇评论:: 0个Trackbacks

公告

Site Counter

文章

收藏

相册

Blog Links

Site Links

存档


正在读取评论……


2008年05月02日

 

       I believe some who answered are not understanding the meaning of "Outsourcing". It is not a "shut down" and/or "relocation" but a unique way for businesses to operate a department or area using personnel from companies providing "experts" in specialized fields. In most cases all current employees remain. The company may do so if a department needs to expand, current department employees can best be utilized elsewhere, to increase production, better educated and knowledgeable personnel required, expected increase in production, etc. "Outsourcing" may also mean a current part or item in production can be made elsewhere to lower costs.

     In downtown Atlanta "outsourcing" is used in a "big" way. Coca-Cola USA in the 1990's saw the advantages and now the corporate accounting department is "outsourced", non-Coke employees hired through a "management oriented" firm. Coke pays a fee, workers are paid from the "management company", eliminating expenses for Coke, i.e., payroll, workers comp, benefits, etc. saving an abundance of "funds". The "management company" has their own "managers" and can release and replace employees, in a couple of hours. Coke has reported more production, timely assignments completed on-time, improved "audits", etc.

     Most large banks/businesses/corporations in Atlanta are doing same in areas, such as, shipping/receiving, mail rooms, etc. These people are the best, highly qualified, are "screened, drug tested, background and credit report checked, etc.

    The "positive", maintaining current location and employees improving operations with "skilled" employees not on payroll. Eliminates payroll, workers comp, benefits, etc., and allows owners/management to easily have replacements.

     The "negative", may eliminate position(s). This may effect current employees promotion, increase in income, eliminate over-time. As employees are terminated, retire, laid off some positions may be eliminated. It is a FACT, "outsourcing" works, more highly trained personnel, better quality in production if a product or part is "outsourced", saves company money, eliminates possible law suits from employees. It also allows local employment to increase.
   
    I have seen this with a lot of businesses. I understand it "works". So I trust in New York IT Outsourcing and recommend it to you.

 

  



2008年01月13日


    摘要:

$54.33 mesothelioma lawyers
$47.79 what is mesothelioma
$47.72 peritoneal mesothelioma
$47.25 consolidate loans
$47.16 refinancing mortgage
$45.55 tax attorney
$41.22 mesothelioma
$38.86 car accident lawyer
$38.68 ameriquest mortgage
$38.03 mortgage refinance
$37.55 refinancing
$35.99 auto accident attorney
$35.52 equity mortgage
$34.34 mesothelioma texas
$34.05 mortgages
$33.80 criminal defense    (全文共771字)——点击此处阅读全文




    摘要:

Introduction

      wfspy is a tool that helps you to view properties of any window form control in the system. I originally needed a small utility that will give me the managed control type name and the assembly name from a window handle, but gradually the tool became sophisticated enough to show all properties of a managed control (and optionally modify them). The only feature currently missing is the spying of events, which I plan to add later on.

    (全文共5703字)——点击此处阅读全文


2007年11月29日

 

Introduction

One of the revolutionary features of C++ over traditional languages is its support for exception handling. It provides a very good alternative to traditional techniques of error handling which are often inadequate and error-prone. The clear separation between the normal code and the error handling code makes programs very neat and maintainable. This article discusses what it takes to implement exception handling by the compiler. General understanding of the exception handling mechanism and its syntax is assumed. I implemented exception handling library for VC++ that is accompanied with this article. To replace the exception handler provided by VC++ with my handler, call the following function:

install_my_handler();

After this point, any exception that occurs with in the program - from throwing an exception to stack unwinding, calling the catch block and then resuming the execution - is processed by my exception handling library.

The C++ standard, like any other feature in C++, doesn't say anything about how exception handling should be implemented. This means that every vendor is free to use any implementation as he sees fit. I will describe how VC++ implements this feature, but it should be a good study material for those as well who use other compilers or Operating Systems. VC++ builds its exception handling support on top of structured exception handling (SEH) provided by Windows operating system.

Structure exception handling - Overview

For this discussion, I will consider exceptions to be those that are explicitly thrown or occur due to conditions like divide by zero or null pointer access. When exception occurs, interrupt is generated and control is transferred to the operating system. Operating System, in turn, calls the exception handler that inspects the function call sequence starting from the current function from where the exception originated, and performs its job of stack unwinding and control transfer. We can write our own exception handler and register it with the operating system that it would call in the event of an exception.

Windows defines a special structure for registration, called EXCEPTION_REGISTRATION:

struct EXCEPTION_REGISTRATION

{

   EXCEPTION_REGISTRATION *prev;

   DWORD handler;

};

To register your own exception handler, create this structure and store its address at offset zero of the segment pointed to by FS register, as the following pseudo assembly language instruction shows:

mov FS:[0], exc_regp

prev field signifies linked list of EXCEPTION_REGISTRATION structures. When we register EXCEPTION_REGISTRATION structure, we store the address of the previously registered structure in prev field.

So how does the exception call back function look like? Windows requires that the signature of exception handler, defined in EXCPT.h, be like:

EXCEPTION_DISPOSITION (*handler)(

    _EXCEPTION_RECORD *ExcRecord,

    void * EstablisherFrame,

    _CONTEXT *ContextRecord,

    void * DispatcherContext);

You can ignore all the parameters and return type for now. The following program registers exception handler with the Operating System and generates an exception by attempting to divide by zero. This exception is caught by the exception handler which does not do much. It just prints a message and exits.

Collapse

#include <iostream>

#include <windows.h>

 

using std::cout;

using std::endl;

 

 

struct EXCEPTION_REGISTRATION

{

   EXCEPTION_REGISTRATION *prev;

   DWORD handler;

};

 

 

EXCEPTION_DISPOSITION myHandler(

    _EXCEPTION_RECORD *ExcRecord,

    void * EstablisherFrame,

    _CONTEXT *ContextRecord,

    void * DispatcherContext)

{

        cout << "In the exception handler" << endl;

        cout << "Just a demo. exiting..." << endl;

        exit(0);

        return ExceptionContinueExecution; //will not reach here

}

 

int g_div = 0;

 

void bar()

{

        //initialize EXCEPTION_REGISTRATION structure

        EXCEPTION_REGISTRATION reg, *preg = &reg;

        reg.handler = (DWORD)myHandler;

       

        //get the current head of the exception handling chain      

        DWORD prev;

        _asm

        {

               mov EAX, FS:[0]

               mov prev, EAX

        }

        reg.prev = (EXCEPTION_REGISTRATION*) prev;

       

        //register it!

        _asm

        {

               mov EAX, preg

               mov FS:[0], EAX

        }

 

        //generate the exception

        int j = 10 / g_div;  //Exception. Divide by 0.

}

 

int main()

{

        bar();

        return 0;

}

 

/*-------output-------------------

In the exception handler

Just a demo. exiting...

---------------------------------*/

 

Please note that Windows strictly enforces one rule: EXCEPTION_REGISTRATION structure should be on the stack and it should be at a lower memory address than its previous node. Windows will terminate the process if it does not find such to be the case.

Functions and Stack

Stack is a contiguous area of memory which is used for storing local objects of a function. More specifically, each function has an associated stack frame that houses all the local objects of the function as well as any temporaries generated by the expressions with in the function. Please note that this is a typical picture. In reality, the compiler may store all or some of the objects in internal registers for faster access. Stack is a notion that is supported at the processor level. Processor provides internal registers and special instructions to manipulate it.

shows how a typical stack may look like when function foo calls function bar and bar calls function widget. Please note that in this case the stack grows downwards. This means that the next item to be pushed on the stack would be at lower memory address than the previous item.

The compiler uses EBP register to identify the current active stack frame. In current case, widget is being executed and as the figure shows, EBP register points at widget's stack frame. Function accesses its local objects relative to the frame pointer. The compiler resolves at compile time all the local object names to some fixed offset from frame pointer. For instance, widget would typically access its local variable as some fixed number of bytes below the frame pointer, say EBP-24.

The figure also shows the ESP register, stack pointer that points at the last item in the stack, or in current case, ESP points at the end of widget's frame. Next frame would be created after this location.

Processor supports two operations for the stack: push and pop. Consider:

pop EAX

means read 4 bytes from the location where ESP is pointing and increment (remember, stack grows downwards in our case) ESP by 4 (in 32 bit processors). Similarly,

push EBP

means decrement ESP by 4 and then write the contents of EBP register at the location where ESP is pointing.

When compiler compiles a function, it adds some code in the beginning of the function called prologue that creates and initializes the stack frame of the function. Similarly, it adds code at the end of the function called epilogue to pop the stack frame of the exiting function.

Compiler typically generates the following sequence for the prologue:

Push EBP      ; save current frame pointer on stack

Mov EBP, ESP  ; Activate the new frame

Sub ESP, 10   ; Subtract. Set ESP at the end of the frame

The first statement saves the current frame pointer EBP on the stack. The second statement activates the frame for the callee by setting EBP register at the location where it stored the EBP of the caller. And the third statement sets the ESP register at the end of the current frame by subtracting ESP's value with the total size of all the local objects and the temporaries that the function will create. Compiler knows at compile time the type and size of all the local objects of a function, so it effectively knows the frame size.

The epilogue does the reverse of the prologue, It has to remove the current frame from the stack:

Mov ESP, EBP   

Pop EBP         ; activate caller's frame

Ret             ; return to the caller

It sets ESP at the location where its caller's frame pointer is saved (which is at the location where callee's frame pointer points), pops it off in EBP thus activating its caller's stack frame and then executes return.

When processor encounters return instruction, it does the following: it pops off the return address from the stack and transfer's control at that address. The return address was put on the stack when its caller executed call instruction to call it. Call instruction first pushes the address of the next instruction where control should be returned and then jumps to the beginning of the callee. Figure 3 shows a more detailed view of the stack at runtime. As the figure shows, function parameters are also part of the stack frame of the function. The caller pushes callee's arguments on the stack. When the function returns, the caller removes the callee's arguments from the stack by adding the size of the arguments to the ESP which is known at compile time:

Add ESP, args_size

Alternatively, callee can also remove the parameters by specifying the total parameter size in return instruction which again is known at compile time. The instruction below removes 24 bytes from the stack before returning to the caller, assuming total parameter size is 24:

Ret 24

Only one of these schemes is used at a time depending upon callee's calling convention. Please note that every thread in a process has its own associated stack.

C++ and Exceptions

Recall that I had talked about EXCEPTION_REGISTRATION structure in the first section. It is used to register the exception callback with the operating system that it calls when exception occurs.

VC++ extends the semantics of this function by adding two more fields at the end:

struct EXCEPTION_REGISTRATION

{

   EXCEPTION_REGISTRATION *prev;

   DWORD handler;

   int   id;

   DWORD ebp;

};

VC++, with few exceptions, creates EXCEPTION_REGISTRATION structure for every function as its local variable. The last field of the structure overlaps the location where frame pointer EBP points. Function's prologue creates this structure on its stack frame and registers it with the operating system. The epilogue restores the EXCEPTION_REGISTRATION of the caller. I will discuss the significance of the id field in the next sections.

When VC++ compiles a function, it generates two set of data for the function:

a) Exception callback function.
b) A data structure that contains important information about the function like the catch blocks, their addresses, the type of exception they are interested in catching etc. I will refer to this structure as funcinfo and talk more about it in the next section.

Figure 4 shows a broader picture of how things look like at runtime when considering exception handling. Widget's exception callback is at the head of the exception chain pointed to by FS:[0] (which was set by widget's prologue). Exception handler passes widget's funcinfo structure's address to __CxxFrameHandler function, which inspects this data structure to see if there is any catch block in the function interested in catching the current exception. If it does not find any, it returns ExceptionContinueSearch value back to the operating system. Operating system gets the next node off the exception handling list and calls its exception handler (which is the handler for the caller of the current function).

This continues until the exception handler finds the catch block interested in catching the exception in which case it does not return back to the operating system. But before it calls the catch block (it knows the address of the catch block from funcinfo structure, see figure 4), it must perform stack unwinding: cleaning up the stack frames of the functions below this function's frame. Cleaning of the stack frame involves nice little intricacy: The exception handler must find all the local objects of the function alive on the frame at the time of the exception and call their destructors. I will discuss more about it in a later section.

The exception handler delegates the task of cleaning the frame to the exception handler associated with that frame. It starts from the beginning of the exception handling list pointed to by FS:[0] and calls the exception handler at each node, telling it that the stack is being unwound. In response, the handler calls destructor for all the local objects on the frame and returns. This continues until it arrives at the node that corresponds to itself.

Since catch block is part of a function, it uses the stack frame of the function to which it belongs. Hence the exception handler needs to activate its stack frame before calling the catch block. Secondly, every catch block accepts exactly one parameter, its type being the type of exception it is willing to catch. Exception handler should copy the exception object or its reference to catch block's frame. It knows where to copy the exception from funcinfo structure. The compiler is generous enough to generate this information for it.

After copying the exception and activating the frame, exception handler calls the catch block. Catch block returns it the address where control should be transferred in the function after try-catch block. Please note that at this moment, even though the stack unwinding has occurred and frames have been cleaned up, they have not been removed and they still physically occupy the space on the stack. This is because the exception handler is still executing and like any other normal function, it also uses the stack for its local objects, its frame present below the last function's frame from where the exception originated. When catch block returns it needs to destroy the exception. It is after this point that the exception handler removes all the frames including its own by setting the ESP at the end of the function's frame (to which it has to transfer the control) and transfers control at the end of try-catch block. How does it know where the end of function's frame is? It has no way of knowing. That's why the compiler saves it (through function's prologue) on function's stack frame for the exception handler to find it. See figure 4. It is sixteen bytes below the stack frame pointer EBP.

The catch block may itself throw a new exception or rethrow the same exception. Exception handler has to watch for this situation and take appropriate action. If catch block throws a new exception, the exception handler has to destroy the old exception. If catch block specifies a rethrow, then the exception handler has to propagate the old exception.

There is one important point to note here: Since every thread has its own stack, this means that every thread has its own list of EXCEPTION_REGISTRATION structures pointed to by FS:[0].

C++ and Exceptions - 2

figure 4 shows the layout of funcinfo structure. Please note that the names may vary from the actual names used by VC++ compiler and I have only shown the relevant fields. Structure of unwind table is discussed in the next section.

When exception handler has to search for a catch block in a function, the first thing it has to determine is whether the point from where the exception originated from within a function has an enclosing try block or not. If it does not find any try block, then it returns back. Otherwise, it searches the list of catch blocks associated with the enclosing try block.

First, let's see how it goes about finding the try block. At compile time, the compiler assigns each try block a start id and end id. These id's are also accessible to exception handler through funcinfo structure. See >figure 5. The compiler generates tryblock data structure for each try block with in the function.

In the previous section, I had talked about VC++ extending the EXCEPTION_REGISTRATION structure to include id field. Recall, this structure is present on function's stack frame. See figure 5. At the time of exception, the exception handler reads this id from the frame and checks the tryblock structure to see if the id is equal to or falls in between its start id and end id. If it does, then the exception originated from with in this try block. Otherwise, it looks at the next tryblock structure in tryblocktable.

Who writes the id value on the stack and what should be written there? The compiler adds statements in the function at various points that update id value to reflect the current runtime state. For instance, the compiler will add a statement in the function at the point where try block is entered that will write the start id of the try block on the stack frame.

Once exception handler finds the try block, it can traverse the catchblock table associated with the try block to see if any catch block is interested in catching the exception. Please note that in case of nested try blocks, the exception that originated from inner try block also originated from outer try block. The exception handler should first look for the catch blocks for the inner try block. If none is found, then it looks for the catch blocks of the outer try block. While laying the structures in tryblock table, VC++ puts inner try block structure before the outer try block.

How is the exception handler going to determine (from catchblock structure) if a catch block is interested in catching the current exception? It does so by comparing the type of the exception with the type of the catch block's parameter. Consider:

void foo()

{

   try {

      throw E();

   }

   catch(H) {

      //.

   }

}

The catch block catches the exception if H and E are the exact same type. The exception handler has to compare the types at runtime. Normally, languages like C provide no facility to determine object's type at runtime. C++ provides run time type identification mechanism (RTTI) and has a standard way of comparing types at runtime. It defines a class type_info, defined in standard header <typeinfo> that represents a type at runtime. The second field of the catchblock structure (see figure 5) is a pointer to the type_info structure that represents the type of the catch block's parameter at runtime. type_info has operator == that tells whether the two types are of exact same class or not. So, all the exception handler has to do is to compare (call operator ==) the type_info of the catch block's parameter (available through catchblock structure) with the type_info of the exception to determine if the catch block is interested in catching the current exception.

The exception handler knows about the type of the catch block's parameter from funcinfo structure, but how does it know about the type_info of the exception? When compiler encounters a statement like

throw E();

It generates excpt_info strcuture for the exception thrown. See figure 6. Please note that the names may vary from the actual names used by VC++ compiler and I have only shown the relevant fields. As shown in the figure, exception's type_info is available through excpt_info structure. At some point of time, exception handler needs to destroy the exception (after the catch block is invoked). It may also need to copy the exception (before invoking the catch block). To help exception handler do these tasks, compiler makes available exception's destructor, copy constructor and size to the exception handler through excpt_info structure.

If the catch block's parameter type is a base class and the exception is its derived class, the exception handler should still invoke that catch block. However, comparing the typeinfo's of the two in this case would yield false as they are not the same type. Neither does class type_info provide any member function or operator that tells if one class is base class of the other. And yet, the exception handler has to invoke this catch block. To help it do so, the compiler has generated more information for the handler. If the exception is a derived class, then etypeinfo_table (available through excpt_info structure) contains etype_info (extended type_info, my name) pointer for all the classes in the hierarchy. So the exception handler compares the type_info of the catch block's parameter with all the type_info's available through the excpt_info structure. If any match is found then the catch block will be invoked.

Before I wrap up this section, one last point: How does the exception handler become aware of the exception and the excpt_info structure? I will attempt to answer this question in the following discussion.

VC++ translates throw statement into something like:

//throw E(); //compiler generates excpt_info structure for E.

E e = E();  //create exception on the stack

_CxxThrowException(&e, E_EXCPT_INFO_ADDR);

_CxxThrowException passes control to the operating system (through software interrupt, see function RaiseException) passing it both of its parameters. The operating system packages these two parameters in _EXCEPTION_RECORD structure in its preparation to call the exception callback. It starts from the head of the EXCEPTION_REGISTRATION list pointed to by FS:[0] and calls the exception handler at that node. The pointer to this EXCEPTION_REGISTRATION is also the second parameter of the exception handler. Recall that in VC++, every function creates its own EXCEPTION_REGISTRATION on its stack frame and registers it. Passing the second parameter to the exception handler makes important information available to it like EXCEPTION_REGISTRATION's id field (important for finding the catch block). It also makes the exception handler aware of the function's stack frame (useful for cleaning the stack frame) and the position of the EXCEPTION_REGISTRATION node on the exception list (useful for stack unwinding). The first parameter is the pointer to the _EXCEPTION_RECORD structure through which the exception pointer and its excpt_info structure is available. The signature of the exception handler defined in EXCPT.H is:

EXCEPTION_DISPOSITION (*handler)(

    _EXCEPTION_RECORD *ExcRecord,

    void * EstablisherFrame,

    _CONTEXT *ContextRecord,

    void * DispatcherContext);

You can ignore the last two parameters. The return type is an enumeration (see EXCPT.H). As I discussed before, if the exception handler cannot find catch block, it returns ExceptionContinueSearch value back to the system. For this discussion, other values are not important. _EXCEPTION_RECORD structure is defined in WINNT.H as:

struct _EXCEPTION_RECORD

{

    DWORD ExceptionCode;

    DWORD ExceptionFlags;

    _EXCEPTION_RECORD *ExcRecord;

    PVOID   ExceptionAddress;

    DWORD NumberParameters;

    DWORD ExceptionInformation[15];

} EXCEPTION_RECORD;

The number and kind of entries in the ExceptionInformation array depends upon the ExceptionCode field. If the ExceptionCode represents C++ (Exception code is 0xe06d7363) exception (which will be the case if the exception occurs due to throw), then ExceptionInformation array contains pointer to the exception and the excpt_info structure. For other kind of exceptions, it almost never has any entry. Other kind of exceptions can be divide by zero, access violation etc and their values can be found in WINNT.H.

Exception handler looks at the ExceptionFlags field of the _EXCEPTION_RECORD structure to determine what action to take. If the value is EH_UNWINDING (defined in Except.inc), that is an indication to the exception handler that the stack is being unwound and it should clean its stack frame and return. Cleaning up involves finding all the local objects alive on the frame at the time of the exception and calling their destructors. The next section discusses this. Otherwise, exception handler has to search for the catch block in the function and invoke it if it is found.

Stack Frame Cleanup

C++ standard says that when the stack is being unwound, destructor for all the local objects alive at the time of exception should be called. Consider:

int g_i = 0;

void foo()

{

   T o1, o2;

   {

       T o3;

   }

   10/g_i; //exception occurs here

   T o4;

   //...

}

When exception occurs, local objects o1 and o2 exists on foo's frame while o3 has completed its lifetime. O4 was never created. Exception handler should be aware of this fact and should call destructor for o1 and o2.

As I wrote before, the compiler adds code to the function at various special points that register the current runtime state of the function as execution proceeds. It assigns id's to these special regions in the function. For instance, try block entry point is a special region. As discussed before, the compiler will add statement in the function at the point when try block is entered that will write the start id of the try block on function's frame.

The other special region in the function is where the local object is created or destroyed. In other words, the compiler assigns each local object a unique id. When compiler encounters object definition like:

void foo()

{

   T t1;

   //.

}

It adds statement after the definition (after the point when object will be created) to write its id value on the frame:

void foo()

{

   T t1;

   _id = t1_id; //statement added by the compiler

   //.

}

The compiler creates a hidden local variable (designated in the above code as _id) that overlaps with the id field of the EXCEPTION_REGISTRATION structure. Similarly, it adds statement before calling the destructor for the object to write the id of the previous region.

When exception handler has to clean up the frame, it reads the id value from the frame (id field of the EXCEPTION_REGISTRATION structure or 4 bytes below the frame pointer, EBP). This id is an indication that the code in the function up to the point to which current id corresponds executed without any exception. All the objects above this point were created. Destructors for all or some of the objects above this point need to be called. Please note that some of these objects may have been destroyed if they are part of a sub block. Destructor for these should not be called.

Compiler generates yet another data structure for the function, unwindtable(my name), which is an array of unwind structures. This table is available through funcinfo structure. See figure 5. For every special region in the function, there is one unwind structure. The structure entries appear in the unwindtable in the same order as their corresponding regions appear in the function. unwind structure corresponding to objects is of interest (remember, each object definition denotes special region and has id associated with it). It contains information to destroy the object. When compiler encounters object definition, it generates a short routine that knows about the object's address on the frame (or its offset from the frame pointer) and destroys this object. One of the fields of unwind structure contains the address of this routine:

typedef  void (*CLEANUP_FUNC)();

struct unwind

{

    int prev;

    CLEANUP_FUNC  cf;

};

unwind structure for try block has a value of zero for the second field. prev field signifies that the unwintable is also a linked list of unwind structures. When exception handler has to cleanup the frame, it reads the current id from the frame and uses it as an index into the unwind table. It reads the unwind structure at that index and calls the clean up function as specified by the second field of the structure. This destroys the object corressponding to this id. The handler then reads the previous unwind structure from unwind table at an index as specified by prev field. This continues until end of list is reached (prev is -1). Figure 7 shows how a unwind table may look like for the function in the figure.

Consider case of the new operator:

T* p = new T();

The system first allocates memory for T and then calls the constructor. If constructor throws an exception, then the system must free up the memory allocated for this object. To achieve this, VC++ also assigns id to each new operator for a type that has a non-trivial constructor. There is corresponding entry in the unwind table, the cleanup routine frees the space allocated. Before calling the constructor, it stores id for the allocation in EXCEPTION_REGISTRATION structure. After constructor returns successfully, it restores the id of the previous special region.

Furthermore, the object may have been partially constructed when the constructor throws exception. If it has member sub objects or base class sub objects and some of them had been constructed at the time of exception, destructor for those objects must be called. Compiler generates same set of data for a constructor as for any normal function to perform these tasks.

Please note that the exception handler calls the user-defined destructors while unwinding the stack. It is possible for the destructor to throw an exception. The C++ standard says that while the stack is unwinding, the destructor may not throw an exception. If it does, the system calls std::terminate.

Implementation

This section talks about three topics that have not been discussed above:

a) Installing the exception handler.
b) Catch block rethrowing the exception or throwing a new exception.
c) Per thread exception handling support.

Please look at the Readme.txt file in the source distribution for build instructions 1. It also includes a demo project.

The first task is to install the exception handling library, or in other words, replace the library provided by VC++. From the above discussion, it is clear that VC++ provides __CxxFrameHandler function that is the entry point for the all the exceptions. For each function, the compiler generates exception handling routine that is called if the exception occurs with in this function. This routine passes funcinfo pointer to the __CxxFrameHandler function.

install_my_handler() function inserts code at the beginning of __CxxFrameHandler that jumps to my_exc_handler(). But __CxxFrameHandler resides in readonly code page. Any attempt to write to it would cause access violation. So the first step is to change the access of the page to read-write using VirtualProtectEx function provided by Windows API. After writing to the memory, we restore the old protection of the page. The function writes the contents of jmp_instr structure at the beginning of __CxxFrameHandler.

Collapse

//install_my_handler.cpp

 

#include <windows.h>

#include "install_my_handler.h"

 

//C++'s default exception handler

extern "C"

EXCEPTION_DISPOSITION  __CxxFrameHandler(

     struct _EXCEPTION_RECORD *ExceptionRecord,

     void * EstablisherFrame,

     struct _CONTEXT *ContextRecord,

     void * DispatcherContext

     );

 

namespace

{

    char cpp_handler_instructions[5];

    bool saved_handler_instructions = false;

}

 

namespace my_handler

{

    //Exception handler that replaces C++'s default handler.

    EXCEPTION_DISPOSITION my_exc_handler(

         struct _EXCEPTION_RECORD *ExceptionRecord,

         void * EstablisherFrame,

         struct _CONTEXT *ContextRecord,

         void * DispatcherContext

         ) throw();

 

#pragma pack(1)

    struct jmp_instr

    {

        unsigned char jmp;

        DWORD         offset;

    };

#pragma pack()

   

    bool WriteMemory(void * loc, void * buffer, int size)

    {

        HANDLE hProcess = GetCurrentProcess();

       

        //change the protection of pages containing range of memory

        //[loc, loc+size] to READ WRITE

        DWORD old_protection;

       

        BOOL ret;

        ret = VirtualProtectEx(hProcess, loc, size,

                         PAGE_READWRITE, &old_protection);

        if(ret == FALSE)

            return false;

 

        ret = WriteProcessMemory(hProcess, loc, buffer, size, NULL);

      

        //restore old protection

        DWORD o2;

        VirtualProtectEx(hProcess, loc, size, old_protection, &o2);

 

               return (ret == TRUE);

    }

 

    bool ReadMemory(void *loc, void *buffer, DWORD size)

    {

        HANDLE hProcess = GetCurrentProcess();

        DWORD bytes_read = 0;

        BOOL ret;

        ret = ReadProcessMemory(hProcess, loc, buffer, size, &bytes_read);

        return (ret == TRUE && bytes_read == size);

    }

 

    bool install_my_handler()

    {

        void * my_hdlr = my_exc_handler;

        void * cpp_hdlr = __CxxFrameHandler;

 

        jmp_instr jmp_my_hdlr;

        jmp_my_hdlr.jmp = 0xE9;

        //We actually calculate the offset from __CxxFrameHandler+5

        //as the jmp instruction is 5 byte length.

        jmp_my_hdlr.offset = reinterpret_cast<char*>(my_hdlr) -

                    (reinterpret_cast<char*>(cpp_hdlr) + 5);

        

        if(!saved_handler_instructions)

        {

            if(!ReadMemory(cpp_hdlr, cpp_handler_instructions,

                        sizeof(cpp_handler_instructions)))

                return false;

            saved_handler_instructions = true;

        }

 

        return WriteMemory(cpp_hdlr, &jmp_my_hdlr, sizeof(jmp_my_hdlr));

    }

 

    bool restore_cpp_handler()

    {

        if(!saved_handler_instructions)

            return false;

        else

        {

            void *loc = __CxxFrameHandler;

            return WriteMemory(loc, cpp_handler_instructions,

                           sizeof(cpp_handler_instructions));

        }

    }

}

The #pragma pack(1) directive at the definition of jmp_instr structure tells the compiler to layout the members of the structure without any padding between them. Without this directive, size of this structure is eight bytes. It is five bytes when we define this directive.

Going back to exception handling, when the exception handler calls the catch block, the catch block may rethrow the exception or throw a completely new exception. If catch block throws a new exception, then the exception handler has to destroy the previous exception before moving ahead. If the catch block decides to rethrow, the exception handler has to propagate the current exception. At this moment, the exception handler has to tackle with two questions: how does the exception handler know that the exception originated from with in a catch block and how does it keep track of the old exception? The way I solved this problem is that before the handler calls the catch block, it stores the current exception in exception_storage object and registers a special purpose exception handler, catch_block_protector. The exception_storage object is available by calling get_exception_storage() function:

exception_storage* p = get_exception_storage();

p->set(pexc, pexc_info);

register catch_block_protector

call catch block

//....

If exception is (re)thrown from within a catch block, the control goes to catch_block_protector. Now this function can extract the previous exception from exception_storage object and destroy it if catch block threw new exception. If the catch block did a rethrow (which it can find out by inspecting first two entries of ExceptionInformation array, both are zero. See code below), then the handler needs to propagate the current exception by copying it in ExceptionInformation array. The following snippet shows catch_block_protector() function.

Collapse

 

 

//-------------------------------------------------------------------

// If this handler is calles, exception was (re)thrown from catch

// block. The  exception  handler  (my_handler)  registers this

// handler before calling the catch block. Its job is to determine

// if the  catch block  threw  new  exception or did a rethrow. If

// catch block threw a  new  exception, then it should destroy the

// previous exception object that was passed to the catch block. If

// the catch block did a rethrow, then this handler should retrieve

// the original exception and save in ExceptionRecord for the

// exception handlers to use it.

//-------------------------------------------------------------------

EXCEPTION_DISPOSITION  catch_block_protector(

         _EXCEPTION_RECORD *ExceptionRecord,

         void * EstablisherFrame,

         struct _CONTEXT *ContextRecord,

         void * DispatcherContext

         ) throw()

{

    EXCEPTION_REGISTRATION *pFrame;

    pFrame = reinterpret_cast<EXCEPTION_REGISTRATION*>

   

    (EstablisherFrame);if(!(ExceptionRecord->ExceptionFlags & ( 

          _EXCEPTION_UNWINDING | _EXCEPTION_EXIT_UNWIND)))

    {

        void *pcur_exc = 0, *pprev_exc = 0;

        const excpt_info *pexc_info = 0, *pprev_excinfo = 0;

        exception_storage *p =

        get_exception_storage();  pprev_exc=

        p->get_exception();  pprev_excinfo=

        p->get_exception_info();p->set(0, 0);

        bool cpp_exc = ExceptionRecord->ExceptionCode == MS_CPP_EXC;

        get_exception(ExceptionRecord, &pcur_exc);

        get_excpt_info(ExceptionRecord, &pexc_info);

        if(cpp_exc && 0 == pcur_exc && 0 ==   pexc_info)

        //rethrow

            {ExceptionRecord->ExceptionInformation[1] =

                reinterpret_cast<DWORD>

            (pprev_exc);ExceptionRecord->ExceptionInformation[2] =

                reinterpret_cast<DWORD>(pprev_excinfo);

        }

        else

        {

            exception_helper::destroy(pprev_exc, pprev_excinfo);

        }

    }

    return ExceptionContinueSearch;

}

Consider one possible implementation of get_exception_storage() function:

exception_storage* get_exception_storage()

{

    static exception_storage es;

    return &es;

}

This would be a perfect implementation except in a multithreaded world. Consider more than one thread getting hold of this object and trying to store exception object in it. This will be disasterous. Every thread has its own stack and own exception handling chain. What we need is a thread specific exception_storage object. Every thread has its own object which is created when the thread begins its life and destroyed when the threads ends. Windows provides thread local storage for this purpose. Thread local storage enables each object to have its own private copy of an object accesible through a global key. It provides TLSGetValue() and TLSSetValue() functions for this purpose.

Excptstorage.cpp file defines get_exception_storage() function. This file is built as a DLL. This is due to the fact that it enables us to know whenever a thread is created or destroyed. Every time a thread is created or destroyed, Windows calls every DLL's (that is loaded in this process's address space) DllMain() function. This function is called in the thread that is created. This gives us a chance to initialize thread specific data, exception_storage object in our case.

Collapse

//excptstorage.cpp

 

#include "excptstorage.h"

#include <windows.h>

 

namespace

{

    DWORD dwstorage;

}

 

namespace my_handler

{

    __declspec(dllexport) exception_storage* get_exception_storage() throw()

    {

        void *p = TlsGetValue(dwstorage);

        return reinterpret_cast<exception_storage*>(p);

    }

}

 

 

BOOL APIENTRY DllMain( HANDLE hModule,

                       DWORD  ul_reason_for_call,

                       LPVOID lpReserved

                                       )

{

    using my_handler::exception_storage;

    exception_storage *p;

    switch(ul_reason_for_call)

    {

    case DLL_PROCESS_ATTACH:

        //For the first main thread, this case also contains implicit

        //DLL_THREAD_ATTACH, hence there is no DLL_THREAD_ATTACH for

        //the first main thread.

        dwstorage = TlsAlloc();

        if(-1 == dwstorage)

            return FALSE;

        p = new exception_storage();

        TlsSetValue(dwstorage, p);

        break;

    case DLL_THREAD_ATTACH:

        p = new exception_storage();

        TlsSetValue(dwstorage, p);

        break;

    case DLL_THREAD_DETACH:

        p = my_handler::get_exception_storage();

        delete p;

        break;

    case DLL_PROCESS_DETACH:

        p = my_handler::get_exception_storage();

        delete p;

        break;

    }

    return TRUE;

}

Conclusion

As discussed above, the C++ compiler and the runtime exception library, with support from the Operating System, cooperate to perform exception handling.

 

 

 

 

 

 





    摘要:

Introduction

    This article explains the technique of finding permutations, and provides source code for the recursive implementation. I will also explain how to use the STL template function next_permutation().

The formula for permutations is n!.

Explanation

    Actually, finding permutations of a small group of numbers by yourself is not difficult, even without the help of computers. Let    (全文共7507字)——点击此处阅读全文



2007年11月14日


    摘要:

Introduction

      Combination is the way of picking a different unique smaller set from a bigger set, without regard to the ordering (positions) of the elements (in the smaller set). This article teaches you how to find combinations. First, I show you the technique to find combinations. Next, I will go on to explain how to use my source code. The source includes a recursive template version and a non-recursive template version. At th    (全文共14342字)——点击此处阅读全文



2007年10月22日


    摘要:

Introduction

     This is a short and simple demonstration of .NET framework's capability of creating custom controls.

Here I'm going to make a custom control and then, test my control in a Windows application. I have implemented some custom properties for my control, so you can learn how it is done in C#.

Building the Control

  1. Open the Visual Studio and start a new project. Yo    (全文共7551字)——点击此处阅读全文



2007年10月10日


    摘要:

Introduction

     In 1998  Microsoft introduced in its Visual J++ language the delegate as a flexible alternative to Sun's Java subscriber pattern for handling events generated by visual elements like buttons and text boxes. In fact the delegate has been a matter of controversy between Microsoft and Sun as the latter never accepted it to be part of the Java Standard. For a verbose yet informative description of delegates and Java subs    (全文共9375字)——点击此处阅读全文



2007年10月01日

Introduction

    The examples used in this article uses the Pubs database that comes as a sample database when you install SQL Server. If you need to rebuild the Pubs database, follow the steps to install a fresh copy :

  1. Run the osql command prompt utility and detach the Pubs database from SQL Server by using the sp_detach_db system stored procedure.
    osql -U sa -P "" -Q "exec sp_detach_db 'Pubs'" 
  2. Delete the database files for pubs database (pubs.mdf, pubs_log.ldf). These files are located in the \Data directory.

  3. Re-creating the Pubs database requires the Instpubs.sql script to be executed. Run the script from the command line (if the .sql files are in a different directory, adjust the path accordingly). You can also run this script file from the Query Analyzer.
     osql -U sa -P "" -i 
    "C:\Program Files\Microsoft SQL Server\MSSQL\Install\InstPubs.sql"

    (The osql utility uses case-sensitive options. If neither the -U or -P options are used, SQL Server 2000 attempts to connect using Windows Authentication Mode. More information about the osql Utility can be found in the Sql Server Books Online)

Transactions

    Transactions group a set of tasks into a single execution unit. Each transaction begins with a specific task and ends when all the tasks in the group successfully complete. If any of the tasks fails, the transaction fails. Therefore, a transaction has only two results: success or failure. Incomplete steps result in the failure of the transaction.

    Users can group two or more Transact-SQL statements into a single transaction using the following statements:

  • Begin Transaction
  • Rollback Transaction
  • Commit Transaction

    If anything goes wrong with any of the grouped statements, all changes need to be aborted. The process of reversing changes is called rollback in SQL Server terminology. If everything is in order with all statements within a single transaction, all changes are recorded together in the database. In SQL Server terminology, we say that these changes are committed to the database.

Here is an example of a transaction :

USE pubs

DECLARE @intErrorCode INT

BEGIN TRAN
UPDATE Authors
SET Phone = '415 354-9866'
WHERE au_id = '724-80-9391'

SELECT @intErrorCode = @@ERROR
IF (@intErrorCode <> 0) GOTO PROBLEM

UPDATE Publishers
SET city = 'Calcutta', country = 'India'
WHERE pub_id = '9999'

SELECT @intErrorCode = @@ERROR
IF (@intErrorCode <> 0) GOTO PROBLEM
COMMIT TRAN

PROBLEM:
IF (@intErrorCode <> 0) BEGIN
PRINT 'Unexpected error occurred!'
ROLLBACK TRAN
END

    Before the real processing starts, the BEGIN TRAN statement notifies SQL Server to treat all of the following actions as a single transaction. It is followed by two UPDATE statements. If no errors occur during the updates, all changes are committed to the database when SQL Server processes the COMMIT TRAN statement, and finally the stored procedure finishes. If an error occurs during the updates, it is detected by if statements and execution is continued from the PROBLEM label. After displaying a message to the user, SQL Server rolls back any changes that occurred during processing. Note: Be sure to match BEGIN TRAN with either COMMIT or ROLLBACK.

Nested Transactions

    SQL Server allows you to nest transactions. Basically, this feature means that a new transaction can start even though the previous one is not complete. Transact-SQL allows you to nest transaction operations by issuing nested BEGIN TRAN commands. The @@TRANCOUNT automatic variable can be queried to determine the level of nesting - 0 indicates no nesting , 1 indicates nesting one level deep, and so fourth.

    A COMMIT issued against any transaction except the outermost one doesn't commit any changes to disk - it merely decrements the@@TRANCOUNT automatic variable. A ROLLBACK, on the other hand, works regardless of the level at which it is issued, but rolls back all transactions, regardless of the nesting level. Though this is counterintuitive, there's a very good reason for it. If a nested COMMIT actually wrote changes permanently to disk, an outer ROLLBACK wouldn't be able to reverse those changes since they would already be recorded permanently.

  
Here is an example of a nested transaction :

USE pubs
SELECT 'Before BEGIN TRAN', @@TRANCOUNT -- The value of @@TRANCOUNT is 0
BEGIN TRAN
SELECT 'After BEGIN TRAN', @@TRANCOUNT -- The value of @@TRANCOUNT is 1
DELETE sales
BEGIN TRAN nested
SELECT 'After BEGIN TRAN nested', @@TRANCOUNT
-- The value of @@TRANCOUNT is 2
DELETE titleauthor
COMMIT TRAN nested
-- Does nothing except decrement the value of @@TRANCOUNT

SELECT 'After COMMIT TRAN nested', @@TRANCOUNT
-- The value of @@TRANCOUNT is 1
ROLLBACK TRAN

SELECT 'After ROLLBACK TRAN', @@TRANCOUNT -- The value of @@TRANCOUNT is 0
-- because ROLLBACK TRAN always rolls back all transactions and sets
-- @@TRANCOUNT to 0.

SELECT TOP 5 au_id FROM titleauthor

    In this example we see that despite the nested COMMIT TRAN, the outer ROLLBACK still reverses the effects of the DELETE titleauthor command.

Here is another similar example of nested transaction :

USE pubs
SELECT 'Before BEGIN TRAN', @@TRANCOUNT -- The value of @@TRANCOUNT is 0
BEGIN TRAN
SELECT 'After BEGIN TRAN', @@TRANCOUNT -- The value of @@TRANCOUNT is 1
DELETE sales
BEGIN TRAN nested
SELECT 'After BEGIN TRAN nested', @@TRANCOUNT
-- The value of @@TRANCOUNT is 2
DELETE titleauthor
ROLLBACK TRAN

SELECT 'After COMMIT TRAN nested', @@TRANCOUNT
-- The value of @@TRANCOUNT is 0 because
-- ROLLBACK TRAN always rolls back all transactions and sets @@TRANCOUNT
-- to 0.

IF (@@TRANCOUNT > 0) BEGIN
COMMIT TRAN -- Never makes it here cause of the ROLLBACK
SELECT 'After COMMIT TRAN', @@TRANCOUNT
END

SELECT TOP 5 au_id FROM titleauthor

    In this example, execution never reaches the out COMMIT TRAN because the ROLLBACK TRAN reverses all transactions currently in progress and sets @@TRANCOUNT to 0. Unless ROLLBACK TRAN is called with a save point, ROLLBACK TRAN always rolls back all transactions and sets @@TRANCOUNT to 0, regardless of the context in which it's called.

SAVE TRAN and Save Points

    Savepoints offer a mechanism to roll back portions of transactions. A user can set a savepoint, or marker, within a transaction. The savepoint defines a location to which a transaction can return if part of the transaction is conditionally canceled. SQL Server allows you to use savepoints via the SAVE TRAN statement, which doesn't affect the @@TRANCOUNT value. A rollback to a savepoint (not a transaction) doesn't affect the value returned by @@TRANCOUNT, either. However, the rollback must explicitly name the savepoint: using ROLLBACK TRAN without a specific name will always roll back the entire transaction.

    The following script demonstrates how savepoints can be used :

Collapse
USE pubs
SELECT 'Before BEGIN TRAN main', @@TRANCOUNT
-- The value of @@TRANCOUNT is 0

BEGIN TRAN main
SELECT 'After BEGIN TRAN main', @@TRANCOUNT
-- The value of @@TRANCOUNT is 1
DELETE sales
SAVE TRAN sales -- Mark a save point
SELECT 'After SAVE TRAN sales', @@TRANCOUNT
-- The value of @@TRANCOUNT is still 1

BEGIN TRAN nested
SELECT 'After BEGIN TRAN nested', @@TRANCOUNT
-- The value of @@TRANCOUNT is 2
DELETE titleauthor
SAVE TRAN titleauthor -- Mark a save point
SELECT 'After SAVE TRAN titleauthor', @@TRANCOUNT
-- The value of @@TRANCOUNT is still 2
ROLLBACK TRAN sales

SELECT 'After ROLLBACK TRAN sales', @@TRANCOUNT
-- The value of @@TRANCOUNT is still 2

SELECT TOP 5 au_id FROM titleauthor

IF (@@TRANCOUNT > 0) BEGIN
ROLLBACK TRAN
SELECT 'AFTER ROLLBACK TRAN', @@TRANCOUNT
-- The value of @@TRANCOUNT is 0 because
-- ROLLBACK TRAN always rolls back all transactions and sets @@TRANCOUNT
-- to 0.
END

SELECT TOP 5 au_id FROM titleauthor

Error Handling

    The examples presented here are specific to stored procedures as they are the desired method of interacting with a database. When an error is encountered within a stored procedure, the best you can do is halt the sequential processing of the code and either branch to another code segment in the procedure or return processing to the calling application. The @@ERROR automatic variable is used to implement error handling code. It contains the error ID produced by the last SQL statement executed during a client’s connection. When a statement executes successfully, @@ERROR contains 0. To determine if a statement executes successfully, an IF statement is used to check the value of @@ERROR immediately after the target statement executes. It is imperative that @@ERROR be checked immediately after the target statement, because its value is reset to 0 when the next statement executes successfully. If a trappable error occurs, @@ERROR will have a value greater than 0. SQL Server resets the @@ERROR value after every successful command, so you must immediately capture the @@ERROR value. Most of the time, you'll want to test for changes in @@ERROR right after any INSERT, UPDATE, or DELETE statement.

CREATE PROCEDURE addTitle(@title_id VARCHAR(6), @au_id VARCHAR(11), 
@title VARCHAR(20), @title_type CHAR(12))
AS

BEGIN TRAN
INSERT titles(title_id, title, type)
VALUES (@title_id, @title, @title_type)

IF (@@ERROR <> 0) BEGIN
PRINT 'Unexpected error occurred!'
ROLLBACK TRAN
RETURN 1
END

INSERT titleauthor(au_id, title_id)
VALUES (@au_id, @title_id)

IF (@@ERROR <> 0) BEGIN
PRINT 'Unexpected error occurred!'
ROLLBACK TRAN
RETURN 1
END

COMMIT TRAN

RETURN 0

    This kind of solution contains substantial repetition especially if your business logic requires more than two Transact-SQL statements to be implemented. A more elegant solution is to group codes into a generic error handling procedure:

CREATE PROCEDURE addTitle(@title_id VARCHAR(6), @au_id VARCHAR(11),
@title VARCHAR(20), @title_type CHAR(12))
AS

BEGIN TRAN
INSERT titles(title_id, title, type)
VALUES (@title_id, @title, @title_type)

IF (@@ERROR <> 0) GOTO ERR_HANDLER

INSERT titleauthor(au_id, title_id)
VALUES (@au_id, @title_id)

IF (@@ERROR <> 0) GOTO ERR_HANDLER

COMMIT TRAN

RETURN 0

ERR_HANDLER:
PRINT 'Unexpected error occurred!'
ROLLBACK TRAN
RETURN 1



Introduction

    First of all 2 things: I'm Spanish and parts of the code is in Spanish but only a little, and sorry for my English.

    This is a Visual C++ class that uses the Oracle OCI library to connect to an Oracle database, keeping a cursor and a connection until the class object is destroyed.

    It uses the OCI library, and you will need these files:

#include <oratypes.h>
#include <ocidfn.h>
#include <ociapr.h>
#include <ocidem.h>

    Also you need the respective .dlls installed in your system.

    When you start a new project to include this class, you have to open the Project Settings and add oci.lib in the Object/library modules (in the Link tab). You need oci.lib and other .lib from the OCI libs in the default lib directory.

Note: The OCI lib is included with any Oracle Client or is available from their web site.

    The code is simple and is based on Oracle samples with oci lib.

    The defines section defines the language options of SQL. Also the max number of fields to return in the SQL (MAX_SELECT_LIST_SIZE), and the max length of the data returned (MAX_ITEM_BUFFER_SIZE).

    The class members are Lda and Cda structures to manage the data and connect with Oracle. It could be converted to private and only keep public the most useful ones:

  • ncolumnas - stores the number of columns of the SQL (SELECT )
  • nfilas - stores the number of rows returned by the SELECT
  • resultado - an array that stores the returned data of the SELECT
  • resultadostr - an array that stores the returned data in a string format
  • txterror - string that stores the error if there is one.

    The description of the member functions is the next:

CursorOrc() - is the constructor and initializes a few member variables

  • Conecta() - makes a connection with Oracle using a user, password and a database name (defined in TNSNAMES.ORA)
  • Abre() - Opens a cursor, member of the class (each instance of the class will have its own cursor)
  • parsea() - checks the SQL sentence looking for errors
  • reserva() & describe() - these call describes define function to reserve memory and make data definitions.
  • ejecuta() - executes the SQL to return the data. Also takes the number of rows returned.

    The parameter it takes must be true if is a SELECT sentence, or false if is a UPDATE, INSERT, ... or if it is a SELECT only to know the number of rows returned (not the data, i.e. a SELECT COUNT )

We don't need to call all these functions, just need two:

  • InicializaSql() - connects to Oracle, parses the SQL, initialize vars and executes the SQL. Must be used first time of connection or if we need to change the user.
  • LanzaSql() - if we have call InicializaSql previously, the rest of SQL sentences will be executed calling this function. The boolean parameter is passed to ejecuta() function.

    If we have executed a SELECT, to access the data, we can use siguiente() to go to the next data row returned (used also to access the first time). After calling it, we have the data in resultadostr matrix of the row. The order is the one in the SELECT sentence.

    Not yet developed the prev() or first() functions typical in cursors in VB objects.

A sample code using this class could be something like this:

Collapse
...
CursorOrc conn;
...
const text *sql = (text *) "SELECT FIELD1, ...,
TO_CHAR(FIELDDATE,'DD/MM/YYYY'), .. FROM
TABLE WHERE FIELD1 NOT IN (SELECT FIELD3 FROM TABLE2)"
;

//cheks the connection
if (conn.InicializaSql((text *) usuario, (text *) pwd,
(text *) bd, (text *)"COMMIT",1))
{
error();
return 1;
}

//launchs the sql
if (conn.LanzaSql(sql,1))
{
error();
return 1;
}

//if some result
if (conn.nfilas != 0)
{
for (int i=0;i<conn.nfilas;i++)
{
conn.siguiente();
....
//outs the first field of each row
outs(conn.resultadostr[0]);
}
}

   If anyone needs help or any question about this class, it will be welcome, as any suggestion or modification.





    摘要:

Introduction

With this C++ wrapper, you can easily do the following:

  • Connect/disconnect to an Oracle server;
  • Execute SQL select/insert/update/delete commands;
  • Store t    (全文共2668字)——点击此处阅读全文


2007年09月29日

Introduction

    Security in software applications is an ever more important topic. In this article, I discuss various aspects of SQL Injection attacks, what to look for in your code, and how to secure it against SQL Injection attacks. Although the technologies used here are SQL Server 2000 and the .NET Framework, the general ideas presented apply to any modern data driven application framework, which makes attacks potentially possible on any type of application that depends on that framework.

What is a SQL Injection Attack?

    A SQL Injection attack is a form of attack that comes from user input that has not been checked to see that it is valid. The objective is to fool the database system into running malicious code that will reveal sensitive information or otherwise compromise the server.

    There are two main types of attacks. First-order attacks are when the attacker receives the desired result immediately, either by direct response from the application they are interacting with or some other response mechanism, such as E-mail.  Second-order attacks are when the attacker injects some data that will reside in the database, but the payload will not be immediately activated. I will discuss each in more detail later in this article.

An example of what an attacker might do

    In the following example, assume that a web site is being used to mount an attack on the database. If you think about a typical SQL statement, you might think of something like:

SELECT ProductName, QuantityPerUnit, UnitPrice 
FROM Products
WHERE ProductName LIKE 'G%'

    The objective of the attacker is to inject their own SQL into the statement that the application will use to query the database. If, for instance, the above query was generated from a search feature on a web site, then they user may have inserted the "G" as their query. If the server side code then inserts the user input directly into the SQL statement, it might look like this:

string sql = "SELECT ProductName, QuantityPerUnit, UnitPrice "+
"FROM Products " +
"WHERE ProductName LIKE '"+this.search.Text+"%';
SqlDataAdapter da = new SqlDataAdapter(sql, DbCommand);
da.Fill(productDataSet);

    This is all fine if the data is valid, but what if the user types something unexpected? What happens if the user types:

' UNION SELECT name, type, id FROM sysobjects;--

    Note the initial apostrophe; it closes the opening quote in the original SQL statement. Also, note the two dashes at the end; that starts a comment, which means that anything left in the original SQL statement is ignored.

    Now, when the attacker views the page that was meant to list the products the user has searched for, they get a list of all the names of all the objects in the database and the type of object that they are. From this list, the attacker can see that there is a table called Users. If they take note of the id for the Users table, they could then inject the following:

' UNION SELECT name, '', length FROM syscolumns 
WHERE id = 1845581613;--

    This would give them a list of the column names in the Users table. Now they have enough information to get access to a list of users, passwords, and if they have admin privileges on the web site.

' UNION SELECT UserName, Password, IsAdmin FROM Users;--

    Assume that there is a table called Users which has columns called UserName and Password, it is possible to union that with the original query and the results will be interpreted as if the UserName was the name of the product and the Password was the quantity per unit. Finally, because the attacker discovered that there is a IsAdmin column, they are likely to retrieve the information in that too.

Locking down

    Security is something that needs to be tackled on many levels because a chain is only as strong as its weakest link. When a user interacts with a piece of software, there are many links in the chain; if the user is malicious, he could attempt to attack these links to find the weak point and attempt to break the system at that point. With this in mind, it is important that the developer does not become complacent about the security of the system because one security measure is put in place, or a set of security measures are in place on only one part of the system.

    An intranet website that uses Windows authentication (it takes the user's existing credentials based on who they are logged in as) and is sitting inside the corporate network and unavailable to Internet users may give the impression that only authorised users can access the intranet web application. However, it is possible for an authenticated user to gain unauthorised access if the security is not taken much beyond that level. Some statistics support the suggestion that most security breaches are insider jobs rather than people attacking the system from outside.

    With this in mind, it is important that even if the application permits only valid data through that has been carefully verified and cleaned up, other security measures are put in place. This is especially important between application layers where there may be an increased opportunity for spoofing of requests or results.

    For example, if a web application were to request that the user choose a date, then it would be normal that the values for the date are checked in some JavaScript function on the web page before any data was posted back to the server. This improves the user experience by reducing the wait between lots of server requests. However, the value needs to be validated again on the server as it is possible to spoof the request with a deliberately crafted invalid date.

Encrypting data

    Starting from the proposition that somehow an attacker has managed to break through all other defenses, what information is so sensitive that it needs to remain a secret? Candidates for encryption include user log in details or financial information such as credit card details.

    For items such as passwords, the user's password can be stored as a "salted hash". What happens is that when a user creates a password, a randomly generated "salt" value is created by the application and appended to the password, and the password-and-salt are then passed through a one way encryption routine, such as found in the .NET Framework's helper class FormsAuthentication (HashPasswordForStoringInConfigFile method). The result is a salted hash which is stored in the database along with the clear text salt string.

    The value of a salted hash is such that a dictionary attack is not going to work as each dictionary would have to be rebuilt appending the various salt values and recomputing the hash values for each item. While it is still possible to determine the password by brute force, the use of the salt (even though it is known) greatly slows down the process. The second advantage of the salt is that it masks any situations where two independent users happen to use the same password, as the salted hash value for each user would be different if given different salt values.

Least Privilege - Database account

    Running an application that connects to the database using the database's administrator account has the potential for an attacker to perform almost limitless commands with the database. Anything an administrator can do, so can an attacker.

     Using the example application above, an attacker could inject the following to discover the contents of the hard disk(s) on the server.

    The first command is used to create a temporary store on the database and fill it with some data. The following injected code will create a table with the same structure as the result set of the extended stored procedure that will be called. It then populates the table with the results of the extended stored procedure.

'; CREATE TABLE haxor(name varchar(255), mb_free int); 
INSERT INTO haxor EXEC master..xp_fixeddrives;--

A second injection attack has to take place in order to get the data out again.

' UNION SELECT name, cast((mb_free) as varchar(10)), 1.0 FROM haxor;--

This returns the name of the disks with the available capacity in megabytes. Now that the drive letters of the disks are known, a new injection attack can take place in order to find out what is on those disks.

'; DROP TABLE haxor;CREATE TABLE haxor(line varchar(255) null); 
INSERT INTO haxor EXEC master..xp_cmdshell 'dir /s c:\';--

And again, a second injection attack is used to get the data out again.

' UNION SELECT line, '', 1.0 FROM haxor;-- 

xp_cmdshell, by default, is only executable by a user with the sysadmin privilege, such as sa, and CREATE TABLE is only available to sysadmin, db_dbowner or db_dlladmin users. It is therefore important to run the application with the least privileges that are necessary in order to perform the necessary functions of the application.

Least Privilege - Process account

    When an instance of SQL Server is installed on a computer, it creates a service that runs in the background and processes the commands from applications that are connected to it. By default, this service is installed to use the Local System account. This is the most powerful account on a Windows machine, it is even more powerful than the Administrator account.

    If an attacker has an opportunity to break out of the confines of SQL Server itself, such as through the extended procedure xp_cmdshell, then they could gain unrestricted access to the machine that the SQL Server is on.

    Microsoft recommends that during the installation of SQL Server, the service is given a domain account which has the permissions set to only the necessary resources. That way, an attacker is confined by the permission set needed to run SQL Server.

Cleaning and Validating input

    In many applications, the developer has side-stepped the potential use of the apostrophe as a way to get access to the system by performing a string replace on the input given by the user. This is useful for valid reasons, such as being able to enter surnames such as "O'Brian" or "D'Arcy", and so the developer may not even realise that they have partly defeated a SQL injection attack. For example:

string surname = this.surnameTb.Text.Replace("'", "''");
string sql = "Update Users SET Surname='"+surname+"' "+
"WHERE id="+userID;

All of the previous injection attack examples would cease to work given a scenario like this.

    However, many applications need the user to enter numbers and these don't need to have the apostrophes escaped like a text string. If an application allows the user to review their orders by year, the application may execute some SQL like this:

SELECT * FROM Orders WHERE DATEPART(YEAR, OrderDate) = 1996

And in order for the application to execute it, the C# code to build the SQL command might look like this:

string sql = "SELECT * FROM Orders WHERE DATEPART(YEAR, OrderDate) = "+
this.orderYearTb.Text);

    It becomes easy to inject code into the database again. All the attackers need to do in this instance is start their attack with a number, then they inject the code they want to run. Like this:

0; DELETE FROM Orders WHERE ID = 'competitor';--

    It is therefore imperative that the input from the user is checked to determine that it really is a number, and in the valid range. For instance:

string stringValue = orderYearTb.Text;
Regex re = new Regex(@"\D");
Match m = re.Match(someTextBox.Text);
if (m.Success)
{
// This is NOT a number, do error processing.
}
else
{
int intValue = int.Parse(stringValue);
if ((intValue < 1990) || (intValue > DateTime.Now.Year))
{
// This is out of range, do error processing.
}
}

Second-Order Attacks

    A second-order attack is one where the data lies dormant in the database until some future event occurs. It often happens because once data is in the database, it is often thought of as being clean and is not checked again. However, the data is frequently used in queries where it can still cause harm.

    Consider an application that permits the users to set up some favourite search criteria. When the user defines the search parameters, the application escapes out all the apostrophes so that a first-order attack cannot occur when the data for the favourite is inserted into the database. However, when the user comes to perform the search, the data is taken from the database and used to form a second query which then performs the actual search. It is this second query which is the victim of the attack.

For example. If the user types the following as the search criteria:

'; DELETE Orders;--

    The application takes this input and escapes out apostrophe so that the final SQL statement might look like this:

INSERT Favourites (UserID, FriendlyName, Criteria)
VALUES(123, 'My Attack', ''';DELETE Orders;--')

    which is entered into the database without problems. However, when the user selects their favourite search, the data is retrieved to the application, which forms a new SQL command and executes that. For example, the C# code might look like:

// Get the valid user name and friendly name of the favourite
int uid = this.GetUserID();
string friendlyName = this.GetFriendlyName();

// Create the SQL statement to retrieve the search criteria
string sql = string.Format("SELECT Criteria FROM Favourites "+
"WHERE UserID={0} AND FriendlyName='{1}'",
uid, friendlyName);
SqlCommand cmd = new SqlCommand(sql, this.Connection);
string criteria = cmd.ExecuteScalar();

// Do the search
sql = string.Format("SELECT * FROM Products WHERE ProductName = '{0}'",
criteria);
SqlDataAdapter da = new SqlDataAdapter(sql, this.Connection);
da.Fill(this.productDataSet);

The second query to the database, when fully expanded, now looks like this:

SELECT * FROM Products WHERE ProductName = ''; DELETE Orders;--

It will return no results for the expected query, but the company has just lost all of their orders.

Parameterised Queries

    SQL Server, like many database systems, supports a concept called parameterised queries. This is where the SQL Command uses a parameter instead of injecting the values directly into the command. The particular second-order attack above would not have been possible if parameterised queries had been used.

Where the application developer would have constructed a SqlCommand object like this:

string cmdText=string.Format("SELECT * FROM Customers "+
"WHERE Country='{0}'", countryName);
SqlCommand cmd = new SqlCommand(cmdText, conn);

A parameterised query would look like this:

string commandText = "SELECT * FROM Customers "+
"WHERE Country=@CountryName";
SqlCommand cmd = new SqlCommand(commandText, conn);
cmd.Parameters.Add("@CountryName",countryName);

    The value is replaced by a placeholder, the parameter, and then the parameter's value is added to the Parameters collection on the command.

    While many second-order attacks can be prevented by using parameters, they can only be used in places were a parameter is permitted in the SQL statement. The application may return a variable sized result set based on user preference. The SQL statement would include the TOP keyword in order to limit the result set, however, in SQL Server 2000, TOP can only accept literal values so the application would have to inject that value into the SQL command to obtain that functionality. For example:

string sql = string.Format("SELECT TOP {0} * FROM Products", numResults);

Using Stored Procedures

    Stored Procedures add an extra layer of abstraction in to the design of a software system. This means that, so long as the interface on the stored procedure stays the same, the underlying table structure can change with no noticeable consequence to the application that is using the database. This layer of abstraction also helps put up an extra barrier to potential attackers. If access to the data in SQL Server is only ever permitted via stored procedures, then permission does not need to be explicitly set on any of the tables. Therefore, none of the tables should ever need to be exposed directly to outside applications. For an outside application to read or modify the database, it must go through stored procedures. Even though some stored procedures, if used incorrectly, could potentially damage the database, anything that can reduce the attack surface is beneficial.

    Stored procedures can be written to validate any input that is sent to them to ensure the integrity of the data beyond the simple constraints otherwise available on the tables. Parameters can be checked for valid ranges. Information can be cross checked with data in other tables.

    For example, consider a database that has the user details for a website, this includes the user name and password. It is important that an attacker is unable to get a list of passwords or even one password. The stored procedures are designed so that a password can be passed in, but it will never put a password in any result set. The stored procedures for registering and authenticating a user for the website might be:

  • RegisterUser
  • VerifyCredentials
  • ChangePassword

    RegisterUser takes the user name and password as parameters (possibly along with other information that is necessary for registering on the website) and returns the UserID.

   VerifyCredentials would be used for logging into the site by accepting the user name and the password. If there is a match the UserID is returned, if not then a NULL value.

   ChangePassword would take the UserID, the old password and the new password. If the UserID and the password match, the password can be changed. A value that indicates success or failure is returned.

The above example shows that the password is always contained in the database and is never exposed.

Stored Procedure Caveat

    While stored procedures seem to be a wonderful panacea against injection attacks, this is not necessarily the case. As mentioned above, it is important to validate data to check that it is correct and it is a definite benefit of stored procedures that they can do this; however, it is doubly important to validate data if the stored procedure is going to use EXEC(some_string) where some_string is built up from data and string literals to form a new command.

    For instance, if the stored procedure is to modify the data model of the database, such as creating a table, the code may be written as follows:

CREATE PROCEDURE dbo.CreateUserTable
@userName sysname
AS
EXEC('CREATE TABLE '+@userName+
' (column1 varchar(100), column2 varchar(100))');
GO

It is obvious that whatever @userName contains will be appended to the CREATE statement. An attacker could inject into the application some code that sets the user name to be:

a(c1 int); SHUTDOWN WITH NOWAIT;--

which will immediately stop the SQL Server without waiting for other requests to complete.

    It is important to validate the input to ensure that no illegal characters are present. The application could be set to ensure that spaces are not permitted as part of the user name and this could be rejected before it ever got as far as constructing the CREATE statement.

    If the stored procedure is going to construct a SQL command based on an existing object, such as a table or view, then it should check that such an object exists. For instance:

CREATE PROCEDURE dbo.AlterUserTable
@userName sysname
AS
IF EXISTS(SELECT * FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_SCHEMA = 'dbo'
AND TABLE_TYPE = 'BASE TABLE'
AND TABLE_NAME = @userName)
BEGIN
// The table is known to exist
// construct the appropriate command here
END
GO

Error Messages

    Error messages are useful to an attacker because they give additional information about the database that might not otherwise be available. It is often thought of as being helpful for the application to return an error message to the user if something goes wrong so that if the problem persists they have some useful information to tell the technical support team. Applications will often have some code that looks like this:

try
{
// Attempt some database operation
}
catch(Exception e)
{
errorLabel.Text = string.Concat("Sorry, your request failed. ",
"If the problem persists please report the following message ",
"to technical support", Environment.Newline, e.Message);
}

    A better solution that does not compromise security would be to display a generic error message that simply states an error has occurred with a unique ID. The unique ID means nothing to the user, but it will be logged along with the actual error diagnostics on the server which the technical support team has access to. The code above would change to something like this instead:

try
{
// Attempt some database operation
}
catch(Exception e)
{
int id = ErrorLogger.LogException(e);
errorLabel.Text = string.Format("Sorry, your request Failed. "+
"If the problem persists please report error code {0} "
"to the technical support team.", id);
}

Summary

  • Encrypt sensitive data.
  • Access the database using an account with the least privileges necessary.
  • Install the database using an account with the least privileges necessary.
  • Ensure that data is valid.
  • Do a code review to check for the possibility of second-order attacks.
  • Use parameterised queries.
  • Use stored procedures.
  • Re-validate data in stored procedures.
  • Ensure that error messages give nothing away about the internal architecture of the application or the database.



 

Introduction

  Sooner or later in your database project you will need to store hierarchical information. For instance enterprise structure, categories of merchandise, product catalogs, or folders with documents all are good nominees for such structures. It is easy of course to implement that kind of store with self-related table. Problems arrive once you need to answer hierarchical questions. For example what if we have an enterprise structure and need to know how many employees reporting to manager. There have been already some solutions of the problem.

  The above techniques are good when you only reading the data, but when you need to modify the tree you will have considerable performance deprivation.

  I am going to introduce method that has similar performance for reading but better performance for modification of the tree.

The Idea

Suppose we have the following table:

 

  Where NodeId is the primary key, ParentId is the foreign key, and NodeName is some extra field. In order to provide simple examples let’s assume that NodeName has unique constraint on it.

For all examples below I will use the following tree:

 

Let’s create another table having two relationships with the Node table:

 

Where both NodeId and ParentId are foreign keys referencing Node table.

  Each record in the Tree table means that the Node pointed by Tree.NodeId has the ancestor pointed by Tree.ParentId. By ancestor I understand any node lying on the path from the node to the root of the tree, i.e. direct parent or grand parent or grand-grand parent and so on.

  Now let’s ensure one simple invariant: all ancestors of each node from Node table are enumerated in Tree table.

For example for node 4 they are going to be 2 and 1.

  The immediate result of this invariant is: this is very easy to get full path from the node to the root of the tree - just select all records from the Tree table related to the seeking node. Let’s write down this select. Suppose we need to find path from node 7 to the root.

Query 1:

select p.*

from Node n, Tree t, Node p

where n.NodeName=7

    and n.NodeId = t.NodeId

    and t.ParentId = p.NodeId

It is a pretty strait forward select, isn’t it?

  The second result of the above invariant is: we can select all descendants of the node. This is because we are enlisting all ancestors for every node from Node table in the Tree table including of course all ancestors of the seeking node. In order to select entire sub tree of the node we just query for all nodes that have the seeking node as their ancestor.

Query 2:

select c.*

from Node n, Tree t, Node c

where n.NodeName=2

    and n.NodeId = t.ParentId

    and t.NodeId = c.NodeId

This is as simple as that. Note that the two queries are opposite in the direction passing through Tree table.

Implementation

Before going further let me make two notes.

# 1: from my experience it is very convenient to have one more field in the Tree table representing the distance on the tree between nodes pointed by NodeId and ParentId, in another words - level of ancestor. So the actual structure includes Level field:

 

# 2: in most probable cases we need sub tree of the node or path from the node to the root including the node itself. So it would be useful having in the Tree table the node itself as its own ancestor of the level 0.

It is possible to implement the above invariant either as stored procedures or as triggers. I personally prefer trigger one.

Let’s start with insert trigger for Node table. I will be using MS SQL server.

First of all the trigger must create a record to satisfy # 2. This can be done by the following statement:

    insert into Tree(NodeId, ParentId, Level)

    select NodeId, NodeId, 0

    from inserted

Now we are ready to add the list of all ancestors of the inserted nodes. Take in consideration that the parent of each inserting node already has enumerated all his ancestors, so we can use them as a foundation. But we need to replace parent’s NodeId with the id of the new node and increment the level.

    insert into Tree(NodeId, ParentId, Level)

    select n.NodeId, t.ParentId, t.Level + 1

    from inserted n, Tree t

    where n.ParentId = t.NodeId

So the overall trigger will be:

create trigger NodeInsert on Node for insert as

begin

    set nocount on

 

    insert into Tree(NodeId, ParentId, Level)

    select NodeId, NodeId, 0

    from inserted

 

    insert into Tree(NodeId, ParentId, Level)

    select n.NodeId, t.ParentId, t.Level + 1

    from inserted n, Tree t

    where n.ParentId = t.NodeId

end

go

  Now it is time to define update trigger which takes care of changing node’s parent. The goal of update trigger is to delete all obsolete records from Tree table and create new ones. In order to minimize changes been made by this trigger let’s reuse the information already stored in the Tree table in a manner we did in insert trigger. What is not going to be affected by changing node’s parent?

  Apparently for updated node itself it is persistent only one record that satisfies #2. And for descendants they are going to be permanent all records about ancestors below updated node including this node. While every ancestor above the updated node will obsolete.

  For example if we are changing parent for node 4 then for nodes 5, 6, and 7 will be changed only ancestors above node 4 i.e. 1 and 2.

  On the first step we back up all descendants of updated nodes in the following table:

    declare @child table(NodeId int, Level int)

  We insert all descendants’ primary keys and the level of each of them relatively to updated nodes. Condition t.Level > 0 allows to exclude updated node from been inserted to the @child table.

    insert into @child(NodeId, Level)

    select t.NodeId, t.Level

    from inserted n, Tree t

    where n.NodeId = t.ParentId and t.Level > 0

The second step deletes all obsolete rows for all descendants:

    delete Tree

    where

        Tree.NodeId in(select NodeId from @child)

        and Tree.ParentId in(

            select t.ParentId

            from inserted n, Tree t

            where n.NodeId = t.NodeId and t.Level > 0

        )

Condition t.Level > 0 excludes updated nodes themselves from the deletion.

The third step removes obsolete records for updated nodes:

    delete Tree

    where Tree.NodeId in(select NodeId from inserted)

        and Tree.Level > 0

On the next two steps we will populate new information in the Tree table.

So the forth step is going to be:

    insert into Tree(NodeId, ParentId, Level)

    select n.NodeId, t.ParentId, t.Level + 1

    from inserted n, Tree t

    where n.ParentId = t.NodeId

This is exactly how we did it in the insert trigger.

And finally the fifth step:

    insert into Tree(NodeId, ParentId, Level)

    select c.NodeId, t.ParentId, t.Level + c.Level

    from inserted n, Tree t, @child c

    where n.NodeId = t.NodeId and t.Level > 0

  It might look strange that there is no any joins with @child table in this statement. But this is what we really need here because we are going to repeat ancestor’s information for each child. Also note that we are adding child’s level stored in the @child table rather that just 1.

The entire trigger looks like:

Collapse

create trigger NodeUpdate on Node for update as

if update(ParentId)

begin

    set nocount on

 

    declare @child table(NodeId int, Level int)

 

    insert into @child(NodeId, Level)

    select t.NodeId, t.Level

    from inserted n, Tree t

    where n.NodeId = t.ParentId and t.Level > 0

 

    delete Tree

    where

        Tree.NodeId in(select NodeId from @child)

        and Tree.ParentId in(

            select t.ParentId

            from inserted n, Tree t

            where n.NodeId = t.NodeId and t.Level > 0

        )

 

    delete Tree

    where Tree.NodeId in(select NodeId from inserted) and Tree.Level > 0

 

    insert into Tree(NodeId, ParentId, Level)

    select n.NodeId, t.ParentId, t.Level + 1

    from inserted n, Tree t

    where n.ParentId = t.NodeId

 

    insert into Tree(NodeId, ParentId, Level)

    select c.NodeId, t.ParentId, t.Level + c.Level

    from inserted n, Tree t, @child c

    where n.NodeId = t.NodeId and t.Level > 0

end

go

  As you can see it will be executed only if ParentId was updated and all other updates of Node table will be filtered out by the very first if statement.

Restrictions of the implementation

  If you are going to insert or update more than one Node at a time please make sure you are not doing this for more then one level of the tree. From the practical point of view this is not a strong restriction at all.

Remaining steps

   In order to make the functionality completed we need the ability to clean the entire Tree table and populate it from the scratch. It is probably a good idea to create a stored procedure for this purpose, and I am not going to take away from you the pleasure to write down such procedure in you own.

 

 




Introduction

     This article follows the great JavaScript for Beginners article and hopes to bring its readers to the cruel reality of life by discussing cookies .

    The problem I wanted to solve was "automated" access to http://www.thehungersite.com. This page has the ability to limit you to one visit per day only (don't forget to click on the link above!). Until now, each time I first started my browser I would manually choose a bookmark to load the page.

Why not create simple script for it?

Because I wanted to have it work under Netscape as well as IE I started to study JavaScript.

The Solution

    Main idea is simple: create a page what will determine whether it has already been loaded today, and if not make it switch to http://www.thehungersite.com, and set this page as browser's home page.

Getting the actual page and redirecting is easy. The problem was remembering.

Because JavaScript has no file access functions (and looking into examples like http://www.javascript.sk/skript.php3?150, no vector graphics either) we must use so called cookies. (also see MSDN - Platform SDK/Tools and Languages/Scripting/JScript/FileSystemObject User's Guide/Working with Files. There are examples for file access from JScript using ActiveX objects, but of course this is only good for Microsoft browsers).

Cookies are size-limited variables associated with a server's domain. By default cookie expire when the browser closes (not when you leave the page!) but a script programmer can change this. Persistent cookies are stored at the users end, separately for every browser user and separately for every browser (Netscape uses for cookies in file, Internet Explorer stores every cookie in a separate one). This difference in browsers brings surprises at some servers where you must log in again and again Be aware that a user can disable cookies in his or her browser settings.

For a detailed cookie description you can see http://www.netscape.com/newsref/std/cookie_spec.html. Believing this all is true I found and used examples for cookies using at http://www.javascript.sk.

All was fine and nice until time I not tried it with Internet Explorer. Then after I called some example Javascript:

   cookieExpires = "01-APR-" + nLyear + " GMT";
document.cookie = cookieName + "=" + cookieValue + "; expires=" + cookieExpires;

and then called

   document.write(document.cookie);

document.cookie was empty.

After experiments and searches in examples I found that

  • you cannot read/display the cookie expriry part. If you want to know it you must assign the same to another simple string variable:
       document.cookie = cookieName + "=" + cookieValue + "; expires=" + cookieExpires;
    myvar = cookieName + "=" + cookieValue + "; expires=" + cookieExpires;
    document.write(myvar);
  • browsers use different date format:
    • Netscape ends with "GMT", IExplorer "UTC", because of this it is good to use constructions like
         var expdate = new Date()
      cookieExpires.setTime (expdate.getTime() + 1 * (24 * 60 * 60 * 1000)) //+1 day
      cookieExpires.toGMTString()
    • When you display Date parts
        document.write(expdate.getYear() + "<br>" + expdate.getMonth() + "<br>" + expdate.getDate());
      for 15. November 2000 you will see 2000/10/15 under Internet Explorer, and 100/10/15 under Netscape
      (Editors Note: Be aware of the Y2K problem with some Netscape browsers)
      .
    • In examples I saw parts like
         if (platform == "Mac") {
      lastVisit = lastVisit - (24 * 60 * 60 * 1000)
      }
      but I have no possibility to check it.


2007年09月28日


    摘要:

Introduction

    Matlab(c) is a well-known scientific software covering a wide range of engineering and mathematicals fields. It contains also a set of complete and powerfull visualization tools.

    Matlab(c) contains it's own language and you can easily develop applications on it. However, sometimes you will want to run it from a C/C++ application and that's where everything get's tricky. In fact, alth    (全文共6869字)——点击此处阅读全文