<html>
<head>
<title>Introduction to ACF</title>
<style> <!-- h1 { font-size: 18pt; font-family:Verdana }
h2 { font-size: 14pt; font-family:Verdana }
body { font-family: Verdana; font-size: 10pt }
p { font-family: Verdana; font-size: 10pt }
pre { padding:8px; font-family: Lucida Console; font-size: 10pt; background-color:#DDDDDD }
.inlinecode { font-family: Lucida Console; font-size: 10pt; color:#800000 }
--></style>
</head>
<body>
<h1>Introduction to ACF (Another C++ Framework)</h1>
<p>The latest version can be found
<a href="http://sourceforage.net/projects/acfproj/">here</a>.</p>
<p>If you'd like to contribute, please e-mail the project admin.</p>
<h2>Abstract</h2>
<p>This document provides an introduction to ACF, a C++ framework designed to implement
part of the .NET framework in standard C++. Knowledge of C++ and the .NET Framework
is assumed.</p>
<h2>Contents</h2>
<ul>
<li>What is ACF</li>
<li>Why ACF</li>
<li>Project vision and plan</li>
<li>Getting started</li>
<li>Basic type system</li>
<li>Exceptions</li>
<li>Arrays and collections</li>
<li>Delegates and events</li>
<li>Strings and text</li>
<li>I/O</li>
<li>Threading</li>
<li>History</li>
<li>Contact</li>
</ul>
<h2>What is ACF</h2>
<p>ACF (Another C++ Framework), is a C++ framework designed to bring the power and
RAD of the .NET framework to standard C++. ACF does this by implementing the features
and APIs of the .NET framework in standard C++, and at the same time take advantage
of C++ unique features such as templates.</p>
<h2>Why ACF</h2>
<p>The .NET framework is definitely the future development platform of Microsoft,
and C# is the best programming language for that platform. However, today there
are thousands of existing C++ projects and developers. These projects are not likely
to be totally rewritten for .NET, and the developers are facing the headache choice
between the RAD and benefits of C# and the .NET framework and the performance and
control of C++. Apparently, the C++ community need a bridge now before moving to
the .NET in the future.</p>
<p>The solution from Microsoft is C++ Managed Extension. C++ ME tries to bring C++
to .NET, the result is that C++ code can be compiled to IL, and managed code can
easily interop with native code. However, C++ ME may not be a good choice for many
projects and developers. First, C++ is almost the symbol of performance and control,
but JIT and GC is very slow, and the runtime is also too huge for many applications.
Second, C++ is already very complex and hard to learn and use, C++ ME adds to it.</p>
<p>ACF tries to solve this problem from another side - bring .NET to C++. By implementing
the .NET framework in standard C++, ACF helps existing C++ projects and developers:
1) ACF can be used in existing or new C++ projects (ACF works seamlessly with MFC/ATL/STL,
etc), and everything compiles to machine code. 2) Developers can use existing C++
skills and at the same time use concepts and APIs that are very close to the .NET
framework. Their new skills are reusable across C#, C++ and C++ ME (for example,
they always use <span class="inlinecode">String::Format</span> to build formatted
strings). 3) ACF also helps when porting code between C++ and C#.</p>
<h2>Project vision and plan</h2>
<p>As stated, the vision of ACF project is to deliver a modern C++ framework that
implements part of the .NET framework. Currently we'll focus on the portable runtime
libraries (Corlib, System, XML) and windows client technologies (Drawing, Windows
Forms, Data). For server-side programming you should choose Java or C#.</p>
<p>The development plan is as follows:</p>
<table border="1" width="100%" id="table1" style="border-collapse: collapse" bordercolorlight="#000000" bordercolordark="#000000">
<tr>
<td align="center" bgcolor="#000000" width="12%"><font color="#FFFFFF">Version</font></td>
<td align="center" bgcolor="#000000" width="88%"><font color="#FFFFFF">
Vision</font></td>
</tr>
<tr>
<td valign="top" align="center" width="12%">1.0</td>
<td valign="top" width="88%">
<ul>
<li>Implement the portable subset of the .NET framework (i.e. ECMA CLI)</li>
<li>Major components:<ul>
<li>Corlib (.NET mscorlib.dll)</li>
<li>System (.NET System.dll)</li>
<li>Xml (.NET System.Xml.dll)</li>
</ul>
</li>
</ul>
</td>
</tr>
<tr>
<td valign="top" align="center" width="12%">2.0</td>
<td valign="top" width="88%">
<ul>
<li>Implement windows client development technologies</li>
<li>Major components<ul>
<li>Drawing (.NET System.Drawing.dll)</li>
<li>Winforms (.NET System.Windows.Forms.dll)</li>
<li>Data (.NET System.Data.dll)</li>
</ul>
</li>
</ul>
</td>
</tr>
</table>
<p>The latest released version is 0.2. Version 1.0 is expected to release in 2004.</p>
<h2>Getting started</h2>
<p>Currently ACF is developed under Visual C++ 2003, you'd better get it before
we move on (if you are still using Visual C++ 6.0, throw it away since it is not
very standards conformant and its template support is very limited). The first step
will be to build ACF if not done before. For example, to build Corlib,
open Corlib.sln (under "{...}\Acf\src\Corlib", where {...} is the directory which
contains ACF) with Visual Studio and build it. Like using other C++ libraries, you
need to setup the include and library directories in your development environment
before using ACF in your applications. The include directory for Corlib is "{...}\Acf\src\Corlib",
library directory is "{...}\Acf\lib".</p>
<p>The "hello world" program can be written as follows:</p>
<pre><font color="#0000FF">#include</font> <font color="#FF0000"><AcfCorlib.h></font>
<font color="#0000FF">int</font> main() {
Acf::Console::WriteLine(<font color="#FF0000">L"hello, world"</font>);
}
</pre>
<p>To compile this program, you need to turn on "treat wchar_t as built-in type"
and "enable runtime type info" compiler options, and link to the multithreaded CRT.
This is required for all ACF projects.</p>
<p>ACF is organized into a number of namespaces, following the design of the .NET
framework. The <span class="inlinecode">Acf</span> namespace corresponds to the
<span class="inlinecode">System</span> namespace in the .NET framework.
<span class="inlinecode">AcfCorlib.h</span> is the header file for the Corlib. Before
we can write more useful applications, let's look into the type system of ACF.</p>
<h2>Basic type system</h2>
<p>There are some significant differences between the C++ type system and the C#/CLI
type system, this affects how ACF type system is implemented.</p>
<p>C++ does not have a unified type system. Primitive types are "magic"; types are
not required to inherit from a common root; and MI is allowed. C++ also has a very
flexible memory model, the developers control how and when to allocate/free memory.</p>
<p>C#/CLI has an unified type system. All types directly or indirectly inherit from
<span class="inlinecode">System.Object</span>; MI is not allowed, but a type can
implement multiple interfaces. Types can be value types or reference types. Instances
of value types directly contain data; instances of reference types are allocated
on the GC heap, and are automatically reclaimed. The translation between value types
and reference types is called boxing/unboxing, which is handled by the compiler
and the runtime.</p>
<p>ACF tries to emulate the C#/CLI type system on C++ and at the same time retain
performance. The result is a hybrid type system. In ACF, types include reference
types and other types. Reference types are allocated on the heap, and managed using
reference count (like COM). Reference types are required to inherit from a single
root - <span class="inlinecode">Acf::Object</span>. MI is only used to implement
interfaces, and all reference interfaces are required to inherit from
<span class="inlinecode">Acf::InterfaceBase</span>. Other types include the primitive
types (e.g. int, float), enum types and other C++ structures and classes. The following
diagram shows the basic object model (green classes are value types, blue classes
are reference types):</p>
<p><img src="objmod.gif"></p>
<p>In ACF, there is no automatically boxing/unboxing since it requires compiler
and runtime support. Developers have to define the value class and reference class
separately and handle the boxing/unboxing requests (in C#/CLI there is only one
type, and boxing/unboxing are automatically handled). For example,
<span class="inlinecode">Int32</span> is a value type and does not have a base.
It has an instance int field and provides methods for parsing and formatting. The
<span class="inlinecode">Int32Object</span> reference type has an instance
<span class="inlinecode">Int32</span> field and inherits from
<span class="inlinecode">Acf::Object</span> and implements interfaces such as
<span class="inlinecode">IFormattable</span>. (ACF uses X and XObject naming conversion
to distinguish value types and their corresponding reference types, another example
is <span class="inlinecode">Acf::Guid</span> and <span class="inlinecode">Acf::GuidObject</span>.)</p>
<p>The example:</p>
<pre><font color="#0000FF">int</font> main() {
<font color="#0000FF">int</font> i = <font color="#800000">123</font>;
ObjectPtr o = <font color="#0000FF">box</font>(i); <font color="#008000">// boxing</font>
<font color="#0000FF">int</font> j = <font color="#0000FF">unbox</font><<font color="#0000FF">int</font>>(o); <font color="#008000">// unboxing</font>
}
</pre>
<p>shows how boxing and unboxing are used. The <span class="inlinecode">box</span>
and <span class="inlinecode">unbox<T></span> functions are template functions under
<span class="inlinecode">Acf</span> namespace. The default implementation of
<span class="inlinecode">box</span> returns an instance of
<span class="inlinecode">Acf::Boxed<T></span> which inherits from
<span class="inlinecode">Acf::Object</span> and acts as an object box. The
<span class="inlinecode">box</span> and <span class="inlinecode">unbox<T></span>
functions should be overloaded for user-defined types that have separate value class
and reference class definitions (for example, <span class="inlinecode">Int32</span>
and <span class="inlinecode">Int32Object</span>).</p>
<p>In ACF, all reference types are allocated on the heap via operator
<span class="inlinecode">new</span>. The <span class="inlinecode">new</span> operator
is overloaded so that it throws <span class="inlinecode">Acf::OutOfMemoryException</span>
when memory allocation fails. On successful allocation, the returned memory is cleared
so there are no random bits. As stated, ACF uses reference count to manage object
lifetime (all interfaces share the same reference count with their owner objects).
When holding an object or an interface, you should call <span class="inlinecode">
AddRef</span>; when you are done you should call <span class="inlinecode">Release</span>.
An object has an initial reference count 0 and is freed when its reference count
goes back to 0. A smart pointer class, <span class="inlinecode">RefPtr<T></span>,
is designed to do the <span class="inlinecode">AddRef/Release</span> jobs automatically.
As a common design philosophy, <span class="inlinecode">RefPtr<T></span> is treated
as strong reference, and raw C++ pointer is treated as weak reference. We need to
distinguish strong reference and weak reference because the reference count approach
has problem dealing with cyclic reference. For example, if object A has a strong
reference to object B, and object B has a strong reference back to object A, then
these two objects form a cyclic reference and both will never be freed. This problem
can be solved by letting B hold a weak reference to A (or reverse, depending on
the scenario).</p>
<p>The following example shows how to define and use reference type:</p>
<pre><font color="#0000FF">#include </font><font color="#FF0000"><AcfCorlib.h></font>
<font color="#0000FF">using namespace </font>Acf;
<font color="#0000FF">class</font> Person : <font color="#0000FF">public</font> <font color="#0000FF">Object</font> {
<font color="#0000FF">public</font>:
StringPtr Name;
<font color="#0000FF">int</font> Age;
Person() {
}
<font color="#0000FF">protected</font>:
<font color="#0000FF">virtual</font> ~Person() {
}
};
<font color="#0000FF">int</font> main() {
StringPtr name = <font color="#0000FF">new</font> String(<font color="#FF0000">L"Foo"</font>);
RefPtr<Person> person = <font color="#0000FF">new</font> Person();
person->Name = name;
person->Age = <font color="#800000">25</font>;
Console::WriteLine(<font color="#FF0000">L"Name: {0}, Age: {1}"</font>, person->Name, <font color="#0000FF">str</font>(person->Age));
}</pre>
<p>The class Person is a reference type and inherits from
<span class="inlinecode">Acf::Object</span>. It has two instance fields, Name (string)
and Age (int). <span class="inlinecode">StringPtr</span> is a typedef of
<span class="inlinecode">RefPtr<String></span>. The destructor is protected so this
type cannot be allocated on stack (reference types shall always be allocated on
the heap).</p>
<p>In the main function, the first statement defines and allocates a new string
instance. Next a new person instance is allocated and its fields are changed. Then
<span class="inlinecode">Console::WriteLine</span> is used to format and print the
person's name and age (the format string is defined by the .NET framework).</p>
<h2>Exceptions</h2>
<p>ACF uses exceptional handling as its basic error raising and handling mechanism.
In the .NET framework, exceptions are also reference types and collected by GC.
In ACF, exceptions are value types. This is because in C++, the preferred way to
throw and catch exceptions is <span class="inlinecode">throw MyException()</span>
and <span class="inlinecode">catch (const MyException& e)</span>.
<span class="inlinecode">Acf::Exception</span> is the base class for all exceptions.
Common exception classes include <span class="inlinecode">ArgumentNullException</span>,
<span class="inlinecode">IOException</span>, <span class="inlinecode">FormatException</span>,
etc.</p>
<h2>Arrays and collections</h2>
<p>Arrays and collections in ACF are different from the .NET framework ones, because
C++ provides good template and template specialization support (especially partial
specialization).</p>
<p><span class="inlinecode">Array<T></span> is the array class in ACF (currently
multidimensional arrays are not supported). The actual class declaration is
<span class="inlinecode">Array<T, Tr></span>, where <span class="inlinecode">Tr</span>
is the collection traits class (there are default implementations for most classes).
The collection traits control how to pass parameters, how to clear elements, how
to compare two elements and how to generate hash code. This technique is used by
all ACF collection classes and interfaces.</p>
<p>Here are two array examples:</p>
<pre>RefPtr<Array<<font color="#0000FF">int</font>> > array1 = Array<<font color="#0000FF">int</font>>::Build(<font color="#800000">3</font>, <font color="#800000">1</font>, <font color="#800000">2</font>, <font color="#800000">5</font>, <font color="#800000">4</font>);
Array<<font color="#0000FF">int</font>>::Sort(array1);
RefPtr<Array<StringPtr> > array2 = <font color="#0000FF">new</font> Array<StringPtr>(<font color="#800000">2</font>);
array2->Item[<font color="#800000">0</font>] = <font color="#0000FF">str</font>(<font color="#FF0000">L"hello"</font>);
array2->Item[<font color="#800000">1</font>] = <font color="#0000FF">str</font>(<font color="#FF0000">L"world"</font>);
</pre>
<p>The first example builds an int array with 5 elements and sorts it. The second
example creates a string array with two elements and sets the elements. the
<span class="inlinecode">Item[0]</span> syntax is a Visual C++ language extension
which represents an indexed property. It is translated into
<span class="inlinecode">array2->set_Item(0, str(L"hello"))</span> at compile time.</p>
<p>The other collection classes and interfaces in ACF (under namespace
<span class="inlinecode">Acf::Collections</span>) is just like the .NET framework
ones, except they are template based. Here are some examples:</p>
<pre>RefPtr<List<<font color="#0000FF">int</font>> > list = <font color="#0000FF">new</font> List<<font color="#0000FF">int</font>>();
list->Add(<font color="#800000">10</font>);
list->Add(<font color="#800000">20</font>);
RefPtr<Dictionary<StringPtr, <font color="#0000FF">int</font>> > map = <font color="#0000FF">new</font> Dictionary<StringPtr, <font color="#0000FF">int</font>>();
map->Item[<font color="#0000FF">str</font>(<font color="#FF0000">L"s1"</font>)] = 1;
map->Item[<font color="#0000FF">str</font>(<font color="#FF0000">L"s2"</font>)] = 10;
</pre>
<p>C# has a <span class="inlinecode">foreach</span> statement to enumerate over
arrays and collections and many developers like it very much. ACF defines a
<span class="inlinecode">FOREACH</span> macro to emulate this feature. For example,
given an array which stores int, we can enumerate the array as follows:</p>
<pre>FOREACH (<font color="#0000FF">int</font>, n, array) {
Console::WriteLine(n);
}
</pre>
<h2>Delegates and events</h2>
<p>Delegates and events are so attractive features of C# and the .NET framework
that ACF definitely cannot miss them.</p>
<p>The delegate definition in ACF is quite different from C#/CLI. For example, the
<span class="inlinecode">EventHandler</span> delegate definition in C# is as follows:</p>
<pre><font color="#0000FF">delegate void </font>EventHandler(<font color="#0000FF">object</font> sender, EventArgs e);</pre>
<p>In ACF it is like:</p>
<pre><font color="#0000FF">typedef</font> Delegate2<<font color="#0000FF">void</font>, <font color="#0000FF">Object</font>*, EventArgs*> EventHandler;</pre>
<p>Here <span class="inlinecode">DelegateN<R, P1, P2, ..., Pn></span> is the delegate
class in ACF, where <span class="inlinecode">N</span> is the number of the function
parameters and <span class="inlinecode">R</span> is the function return type. To
invoke a delegate, call the instance method <span class="inlinecode">Invoke</span>
with the function parameters.</p>
<p>In C# it is very easy to define and consume events, but in ACF it's a little
complex since there is no compiler support. To define an event, use the
<span class="inlinecode">ACF_DECLARE_EVENT</span> macro.</p>
<p>Here is an example on how to use delegates and events in ACF:</p>
<pre><font color="#0000FF">typedef</font> Delegate2<<font color="#0000FF">void</font>, <font color="#0000FF">Object</font>*, EventArgs*> EventHandler;
<font color="#0000FF">typedef</font> RefPtr<EventHandler> EventHandlerPtr;
<font color="#0000FF">class</font> Button : <font color="#0000FF">public</font> <font color="#0000FF">Object</font> {
<font color="#0000FF">public</font>:
ACF_DECLARE_EVENT(EventHandler, Click)
<font color="#0000FF">protected</font>:
<font color="#0000FF">void</font> OnClick(EventArgs* e) {
<font color="#0000FF">if</font> (<font color="#0000FF">this</font>->Click != <font color="#0000FF">null</font>)
<font color="#0000FF">this</font>->Click->Invoke(<font color="#0000FF">this</font>, e);
}
};
<font color="#0000FF">class</font> Form1 : <font color="#0000FF">public</font> Object {
<font color="#0000FF">private</font>:
RefPtr<Button> _button;
<font color="#0000FF">public</font>:
Form1() {
<font color="#0000FF">this</font>->_button = <font color="#0000FF">new</font> Button();
EventHandlerPtr h1 = <font color="#0000FF">new</font> EventHandler(<font color="#0000FF">this</font>, Form1::ButtonClickHandler);
<font color="#0000FF">this</font>->_button->add_Click(h1);
EventHandlerPtr h2 = <font color="#0000FF">new</font> EventHandler(Form1::StaticButtonClickHandler);
<font color="#0000FF">this</font>->_button->add_Click(h2);
}
<font color="#0000FF">private</font>:
<font color="#0000FF">void</font> ButtonClickHandler(<font color="#0000FF">Object</font>* sender, EventArgs* e) {
std::cout << <font color="#FF0000">"Button clicked!"</font> << std::endl;
}
<font color="#0000FF">static void </font>StaticButtonClickHandler(<font color="#0000FF">Object</font>* sender, EventArgs* e) {
std::cout << <font color="#FF0000">"Static: Button clicked!"</font> << std::endl;
}
};
</pre>
<p>The <span class="inlinecode">ACF_DECLARE_EVENT</span> macro actually generates
the following code:</p>
<pre><font color="#0000FF">class</font> Button : <font color="#0000FF">public</font> <font color="#0000FF">Object</font> {
<font color="#0000FF">private</font>:
RefPtr<EventHandler> Click;
<font color="#0000FF">public</font>:
<font color="#0000FF">void</font> add_Click(EventHandler* h) {
LOCK (<font color="#0000FF">this</font>)
<font color="#0000FF">this</font>->Click = EventHandler::Combine(<font color="#0000FF">this</font>->Click, h);
}
<font color="#0000FF">void</font> remove_Click(EventHandler* h) {
LOCK (<font color="#0000FF">this</font>)
<font color="#0000FF">this</font>->Click = EventHandler::Remove(<font color="#0000FF">this</font>->Click, h);
}
};</pre>
<p>In <span class="inlinecode">Form1</span>'s constructor, two delegates are created,
one attaches to a static method and another attaches to an instance method. For
the instance method, the delegate also need a reference to the object (here it is
<span class="inlinecode">Form1</span>). Since <span class="inlinecode">Form1</span>
has a strong reference to <span class="inlinecode">Button</span>, and
<span class="inlinecode">Button</span> has a strong reference to
<span class="inlinecode">Click</span>, so, if <span class="inlinecode">Click</span>
has a strong reference to <span class="inlinecode">Form1</span>, then a cyclic reference
is formed. However, in other cases the delegates may really need strong references
to the objects. So, the constructors of the delegate classes are overloaded for
both <span class="inlinecode">T*</span> and <span class="inlinecode">RefPtr<T></span>.
If you pass a <span class="inlinecode">T*</span> when constructing a delegate (typically
using the <span class="inlinecode">this</span> pointer), the delegate will holds
a weak reference to the object. Otherwise it'll hold a strong reference.</p>
<h2>Strings and text</h2>
<p>The <span class="inlinecode">String</span> and <span class="inlinecode">StringBuilder</span>
classes provide basic string manipulations. The <span class="inlinecode">Acf::Text
</span>namespace also provides classes for encoding and decoding text, as the .NET
framework. Here is a simple example:</p>
<pre>StringPtr s = String::Format(<font color="#FF0000">L"Text: {0} {1}"</font>, s1, obj);
EncodingPtr enc = Encoding::get_Default();
RefPtr<Array<<font color="#0000FF">byte</font>> > bytes = enc->GetBytes(s);
</pre>
<p>The <span class="inlinecode">str</span> function converts numerical values and
C-style strings to <span class="inlinecode">String</span>.</p>
<pre><font color="#0000FF">int</font> a = <font color="#800000">10</font>;
<font color="#0000FF">float</font> b = <font color="#800000">15.2</font>;
<font color="#0000FF">const char</font>* c = <font color="#FF0000">"hello"</font>;
StringPtr s = String::Format(<font color="#FF0000">L"{0}{1}{2}"</font>, <font color="#0000FF">str</font>(a), <font color="#0000FF">str</font>(b), <font color="#0000FF">str</font>(c));</pre>
<h2>I/O</h2>
<p>The <span class="inlinecode">Acf::IO</span> namespace contains classes for reading
and writing streams and files (currently ACF does not support asynchronous file
I/O).</p>
<p>For example, to read text from a file, you can write code as follows:</p>
<pre>StringPtr path = <font color="#0000FF">new</font> String(<font color="#FF0000">L"C:\in.txt"</font>);
StreamReaderPtr reader = <font color="#0000FF">new</font> StreamReader(path);
StringPtr text = reader->ReadToEnd();
</pre>
<p>ACF supports basic streams including <span class="inlinecode">FileStream</span>
and <span class="inlinecode">MemoryStream</span>, and readers/writers including
<span class="inlinecode">BinaryReader/BinaryWriter</span>,
<span class="inlinecode">TextReader/TextWriter</span>, <span class="inlinecode">
StreamReader/StreamWriter</span> and <span class="inlinecode">StringReader/StringWriter</span>.</p>
<p>ACF also supports simple file system management via <span class="inlinecode">
Path</span>, <span class="inlinecode">File</span> and <span class="inlinecode">Directory</span>
classes.</p>
<h2>Threading</h2>
<p>ACF provides basic support for writing multithreaded applications. The following
code shows how to create and start worker threads:</p>
<pre><font color="#0000FF">static void </font>WorkerThreadProc() {
...
}
<font color="#0000FF">class</font> MyObject : <font color="#0000FF">public</font> Object {
<font color="#0000FF">public</font>:
<font color="#0000FF">void</font> WorkerThreadProc() {
...
}
};
<font color="#0000FF">int</font> main() {
ThreadPtr thread1 = <font color="#0000FF">new</font> Thread(WorkerThreadProc);
thread1->Start();
RefPtr<MyObject> obj = <font color="#0000FF">new</font> MyObject();
ThreadStartPtr start = <font color="#0000FF">new</font> ThreadStart(obj, MyObject::WorkerThreadProc);
ThreadPtr thread2 = <font color="#0000FF">new</font> Thread(start);
thread2->Start();
...
}
</pre>
<p>To synchronize data access, use the <span class="inlinecode">LOCK</span> macro
or the <span class="inlinecode">Interlocked</span>, <span class="inlinecode">Monitor</span>,
<span class="inlinecode">AutoResetEvent</span>, <span class="inlinecode">ManualResetEvent</span>
or <span class="inlinecode">Mutex</span> classes.</p>
<h2>History</h2>
<ul>
<li>5/25/2004, version 0.2.</li>
<li>4/25/2004, version 0.1.</li>
</ul>
<h2>Contact</h2>
<p>Your feedback and suggestions are key factors to make this framework better, please
feel free to send them to Yingle Jia (<a href="mailto:yljia@msn.com">yljia@msn.com</a>).
You can also visit his weblog at
<a href="http://blogs.wwwcoder.com/yljia/">
http://blogs.wwwcoder.com/yljia/</a>.</p>
</body>
</html>