Bangladesh is emerging as a strong ground in the outsourcing world. Competing directly with countries like India and China, Bangladesh is gradually getting more attention for high quality of work.
ReliSource Technologies Ltd. (www.relisource.com) is one of the best outsourcing companies of Bangladesh. ReliSource is experienced in providing high quality of work in different technology verticals including Embedded, Gaming Tools, E-Commerce, Gaming E-Commerce, Web 2.0, Enterprise Content Management & Recovery, Network Security, just to name a few. ReliSource, in fact, is the only company in Bangladesh which works with Embedded and Gaming.
ReliSource directly works with some of the fortune 500 companies, including some of the leaders in Embedded, Gaming, Enterprise CMS, etc. ReliSource possesses some of the best engineers of Bangladesh.
Contact information can be found here: http://www.relisource.com/contact-us.html
Friday, October 2, 2009
Monday, September 7, 2009
Maemo 5 - Linux at the core of next generation mobile OS
It's been long since I felt excited about any new mobile OS. Maemo 5 seems to be a very promising mobile OS for the next generation of mobile devices.
Multiple desktops, multitasking, Mozilla based browser, desktop widgets, cool UI, speed all seems to be best of what other OS has to offer. And guess what, it runs on Linux. The Symbian OS 9.4, Windows Mobile 6.5 or iPhone OS 3.. all seem to be moving ahead somewhat slowly. Comparing to them, Maemo 5 seems to be a leap ahead. It seems to be the next best thing after iPhone OS 1 came out thousands of years ago :).
Nokia will release N900 in a few months, the company's first phone running on Maemo 5. N900 itself has good spec with 600 MHz ARM processor, 256 MB RAM, 32 GB ROM, high-end cam, etc.
You can check more about Maemo at: http://maemo.nokia.com/ and more about N900 at http://maemo.nokia.com/n900/.
Multiple desktops, multitasking, Mozilla based browser, desktop widgets, cool UI, speed all seems to be best of what other OS has to offer. And guess what, it runs on Linux. The Symbian OS 9.4, Windows Mobile 6.5 or iPhone OS 3.. all seem to be moving ahead somewhat slowly. Comparing to them, Maemo 5 seems to be a leap ahead. It seems to be the next best thing after iPhone OS 1 came out thousands of years ago :).
Nokia will release N900 in a few months, the company's first phone running on Maemo 5. N900 itself has good spec with 600 MHz ARM processor, 256 MB RAM, 32 GB ROM, high-end cam, etc.
You can check more about Maemo at: http://maemo.nokia.com/ and more about N900 at http://maemo.nokia.com/n900/.
Wednesday, February 18, 2009
C++: Pointer to members
Pointer to member operators are not an everyday operators that we use but those can be useful if used in the correct way. Pointer to member operator ::* is used to declare a pointer that points to a member variable, pointer or function of a class. The operators .* and ->* are used to access it.
Following is an example of how we can use Pointer to member function (also called member function pointer).
Following is an example of how we can use Pointer to member function (also called member function pointer).
1 #include <iostream>Following is another (almost random) example of how we can write generic classes to wrap pointer to member function to use with multiple classes.
2 using namespace std;
3
4 class A{
5 public:
6 A(){
7 }
8 int Func(int a){
9 return a * 2;
10 }
11
12 };
13
14 int main(){
15 int (A::*fp)(int) = &A::Func;
16
17 A a;
18 cout << (a.*fp)(10) << endl;
19
20 return 0;
21 }
22
1 #include <iostream>
2 using namespace std;
3
4 class A{
5 public:
6 int Func1(int a){
7 return a * 2;
8 }
9
10 };
11
12 class B{
13 public:
14 int Func2(int a){
15 return a / 2;
16 }
17
18 };
19
20 template <class T>
21 class MemFnPtr
22 {
23 public:
24 MemFnPtr(int (T::*fp)(int), T &t) : m_fp(fp), m_t(t){
25 }
26 int Fn(int n)
27 {
28 return ((m_t).*(m_fp))(n);
29 }
30
31 //member vars
32 int (T::*m_fp)(int);
33 T &m_t;
34 };
35
36 int main(){
37 A a_obj;
38
39 MemFnPtr<A> fp_a(&A::Func1, a_obj);
40 cout << fp_a.Fn(10) << endl;
41
42 B b_obj;
43
44 MemFnPtr<B> fp_b(&B::Func2, b_obj);
45 cout << fp_b.Fn(10) << endl;
46
47 return 0;
48 }
49
C++ Problem: Define that macro
I'm planning to post some C++ programming problems that I come across and shall post their solutions too (I'll try to post multiple solutions). Here goes the first one:
Problem:
DO_REPORTING is MACRO which when called at the start of a function with appropriate parameter, prints the value of the parameter at the start of the function and also somehow prints the value before leaving the function. For the following code expected output would be:
Before:2
After:4
Before:4
After:2
How would you get this output without changing anything in class A and inside main function?
Solution 1:
Problem:
DO_REPORTING is MACRO which when called at the start of a function with appropriate parameter, prints the value of the parameter at the start of the function and also somehow prints the value before leaving the function. For the following code expected output would be:
Before:2
After:4
Before:4
After:2
How would you get this output without changing anything in class A and inside main function?
1 #include <iostream>
2 using namespace std;
3
4 #define DO_REPORTING(type,variable) ;
5
6 class A{
7 protected:
8 int val;
9 public:
10 A(int v){
11 val = v;
12 }
13 void FuncMult(){
14 DO_REPORTING(int, val);
15 val *= 2;
16 }
17 void FuncDiv(){
18 DO_REPORTING(int, val);
19 val /= 2;
20 }
21 int GetVal(){
22 return val;
23 }
24 };
25
26 int main(){
27 A a(2);
28 a.FuncMult();
29 a.FuncDiv();
30 return 0;
31 }
32
33
Solution 1:
1 #define DO_REPORTING(type,variable) Report<type> r(val);
2
3 template <class T>
4 class Report{
5 T &val;
6 public:
7 Report(T &v):val(v){
8 cout << "Before:" << val << endl;
9 }
10 ~Report(){
11 cout << "After:" << val << endl;
12 }
13 };
14
Back to the blog desk!
I apologize for not posting for quite some time (around 6 months! :P).. I was basically tied up with loads of project works and a bit with life too! Anyway, I'm now energized to spew out a whole lot of stuffs :P.
Stay tuned!
Stay tuned!
Monday, August 18, 2008
Don't click it!
It's been long since I got excited about a web site. I have to say, I'm very impressed with the idea, creativity, innovation and the intuitive interface of this web site/project. It's based on the idea that you don't need to click to navigate.. they strongly suggest that you don't click at all!
Check it out here: http://www.dontclick.it/. Check out the whole web site, all pages to get the real fun.
I feel like pulling out the buttons of my mouse :P.
Check it out here: http://www.dontclick.it/. Check out the whole web site, all pages to get the real fun.
I feel like pulling out the buttons of my mouse :P.
Sunday, July 6, 2008
C++: What's the difference between class and struct?
What's the difference between class and struct?
A beginner would say:
"There are a lot of differences, class is a C++ element and struct is a C one. We can have functions, virtual functions, inheritance, different access modifiers private, protected, public in a class but probably not in a struct. A struct generally holds member variables only."
(Just to clarify, the above statement is completely wrong.)
Intermediate and most advanced C++ programmers would say:
"class and struct do not have any difference, except the fact that class members are by default private and struct members are by default public. The keywords class and struct are interchangeable."
The above statement is correct to some extent and would satisfy most questioners, however, there are some special cases where class and struct are not interchangeable.
Consider the following code which compiles well in a C++ complier:
template <class T>
void fn()
{
}
Now replace the keyword 'class' with 'struct' and compile it..
template <struct T>
void fn()
{
}
It gives a compiler error! According to MSDN's definition of the template keyword:
"The template-parameter-list is a comma-separated list of template parameters, which may be types (in the form class identifier, typename identifier, or template < template-parameter-list > class identifier) or non-type parameters to be used in the template body."
So the template parameter list doesn't take the keyword 'struct'!
The template mechanism itself was introduced in later phase of the C++ evolution, some years later than the first version of C++. The 'struct' keyword was probably ignored when defining template which was considered to be an advance feature of C++.
"Another reasonable use of the C struct in C++, then, is when you want to pass all or part of a complex class object to a C function. This struct declaration serves to encapsulate that data and guarantees a compatible C storage layout. This guarantee, however, is maintained only under composition." said Stanley B. Lippman, author of several C++ books.
A beginner would say:
"There are a lot of differences, class is a C++ element and struct is a C one. We can have functions, virtual functions, inheritance, different access modifiers private, protected, public in a class but probably not in a struct. A struct generally holds member variables only."
(Just to clarify, the above statement is completely wrong.)
Intermediate and most advanced C++ programmers would say:
"class and struct do not have any difference, except the fact that class members are by default private and struct members are by default public. The keywords class and struct are interchangeable."
The above statement is correct to some extent and would satisfy most questioners, however, there are some special cases where class and struct are not interchangeable.
Consider the following code which compiles well in a C++ complier:
template <class T>
void fn()
{
}
Now replace the keyword 'class' with 'struct' and compile it..
template <struct T>
void fn()
{
}
It gives a compiler error! According to MSDN's definition of the template keyword:
"The template-parameter-list is a comma-separated list of template parameters, which may be types (in the form class identifier, typename identifier, or template < template-parameter-list > class identifier) or non-type parameters to be used in the template body."
So the template parameter list doesn't take the keyword 'struct'!
The template mechanism itself was introduced in later phase of the C++ evolution, some years later than the first version of C++. The 'struct' keyword was probably ignored when defining template which was considered to be an advance feature of C++.
"Another reasonable use of the C struct in C++, then, is when you want to pass all or part of a complex class object to a C function. This struct declaration serves to encapsulate that data and guarantees a compatible C storage layout. This guarantee, however, is maintained only under composition." said Stanley B. Lippman, author of several C++ books.
C++: Accessing the virtual table directly
This post is not intended for beginners. To understand the content of this topic, you need to have basic understanding of what virtual functions are.
We know that the run time binding or virtual function mechanism is implemented by a virtual table. If a class has at least one virtual function a virtual table will be created for that class. To be specific, 'only one' virtual table will be created for all of the instances/objects of that class. Each of the instances and objects will have a pointer to the virtual table.
The same thing is true for a class hierarchy. Meaning, if class Z derives class Y and class Y derives class X, only one virtual table will be created for all instances/objects of class X, Y and Z. Each of the instances and objects of X, Y and Z will have a pointer to the virtual table.
===============
Added on July 14, 2008:
The virtual tables for each of class X, Y and Z share common information but they are not necessarily the same table for each of these classes. The scenario is complex for multiple and virtual inheritance. I would like to discuss them in future posts.
===============
A pointer is 32 bit/4 bytes in a 32-bit architecture and 64-bit/8 bytes in a 64-bit architecture. So all instances/objects of a class or class hierarchy, where we have a virtual table, will have additional 4 bytes in them and 8 bytes in case of a 64-bit architecture.
This pointer is called virtual table pointer, sometimes 'vptr'. In VC++ compiler, the objects will have a pointer named '__vfptr' in them and in some other compiler it's '__vptr_X', where X is the class name.
Now __vfptr is not directly accessible from your code. For example, if you write the following code you'll get a compiler error as the __vfptr is not available for your use.
However, if you debug the code in VC++, you can see the 'a.__vfptr' in the variable watch windows. Interesting ha?
Okay, now we'd like to see how we can access the virtual table even if the compiler doesn't want us to. Let's have class X with a virtual function fn() which simply prints a member variable and we want to access the virtual table of class X to call the function fn() using it. The following code does that.
In line 26, we have:
The first entry of the virtual table is the function pointer of the virtual function 'fn'. We can access the first entry using vptr[0] (as this is just an array). So, in line 37, we just call the function using the function pointer. But wait, you might be asking why the following assembly line is there before that function call.
When we call the function fn() in this way: obj->fn(), the compiler does the job for us and sets 'obj' as 'this' before calling the function. But in line 37, we couldn't specify anything to the function fn() saying it is called for the object 'obj', so the function won't find out where to get the value of 'n' from. This is why we expicitly need to set the 'obj' as 'this' before we call the function fn() in line 37. We did that in line 33, in the assembly code. This line is again VC++ specific. In VC++, 'this' pointer is set in the register 'ECX'. Some other compiler may handle that differently.
If we had more virtual function, we could have access them using next indexes of vptr: vptr[1], vptr[2], etc.
We have learned some interesting facts about the virtual functions and the virtual table. We may not have any use of this kind of code where we need to directly access the virtual table in our general applications but this helps when you want to know more about C++ internals.
Enjoy!
July 12, 2008:
We assumed here that the vptr is placed in the beginning of the class object. here's a note on that:
Traditionally, the vptr has been placed after all the explicitly declared members of the class. More recently, it has been placed at the beginning of the class object. The C++ Standard allows the compiler the freedom to insert these internally generated members anywhere, even between those explicitly declared by the programmer.
We know that the run time binding or virtual function mechanism is implemented by a virtual table. If a class has at least one virtual function a virtual table will be created for that class. To be specific, 'only one' virtual table will be created for all of the instances/objects of that class. Each of the instances and objects will have a pointer to the virtual table.
The same thing is true for a class hierarchy. Meaning, if class Z derives class Y and class Y derives class X, only one virtual table will be created for all instances/objects of class X, Y and Z. Each of the instances and objects of X, Y and Z will have a pointer to the virtual table.
===============
Added on July 14, 2008:
The virtual tables for each of class X, Y and Z share common information but they are not necessarily the same table for each of these classes. The scenario is complex for multiple and virtual inheritance. I would like to discuss them in future posts.
===============
A pointer is 32 bit/4 bytes in a 32-bit architecture and 64-bit/8 bytes in a 64-bit architecture. So all instances/objects of a class or class hierarchy, where we have a virtual table, will have additional 4 bytes in them and 8 bytes in case of a 64-bit architecture.
This pointer is called virtual table pointer, sometimes 'vptr'. In VC++ compiler, the objects will have a pointer named '__vfptr' in them and in some other compiler it's '__vptr_X', where X is the class name.
Now __vfptr is not directly accessible from your code. For example, if you write the following code you'll get a compiler error as the __vfptr is not available for your use.
1 X a;
2 cout << a.__vfptr;
However, if you debug the code in VC++, you can see the 'a.__vfptr' in the variable watch windows. Interesting ha?
Okay, now we'd like to see how we can access the virtual table even if the compiler doesn't want us to. Let's have class X with a virtual function fn() which simply prints a member variable and we want to access the virtual table of class X to call the function fn() using it. The following code does that.
1 #include <iostream>Please note, this code is compiler dependent and may only work on VC++ compilers and it'll work correctly when you'll run it in 'Release' mode. Here goes some explanation of the code.
2
3 using namespace std;
4
5 //a simple class
6 class X
7 {
8 public:
9 //fn is a simple virtual function
10 virtual void fn()
11 {
12 cout << "n = " << n << endl;
13 }
14
15 //a member variable
16 int n;
17 };
18
19 int main()
20 {
21 //create an object (obj) of class X
22 X *obj = new X();
23 obj->n = 10;
24
25 //get the virtual table pointer of object obj
26 int* vptr = *(int**)obj;
27
28 // we shall call the function fn, but first the following assembly code
29 // is required to make obj as 'this' pointer as we shall call
30 // function fn() directly from the virtual table
31 __asm
32 {
33 mov ecx, obj
34 }
35
36 //function fn is the first entry of the virtual table, so it's vptr[0]
37 ( (void (*)()) vptr[0] )();
38
39 //the above is the same as the following
40 //obj->fn();
41
42 return 0;
43 }
44
In line 26, we have:
26 int* vptr = *(int**)obj;The virtual table pointer __vfptr is available in the first 4 bytes of the object. In this line, we get the value of the pointer __vfptr or the address of the virtual table as an integer pointer (say as a pointer to an integer array).
The first entry of the virtual table is the function pointer of the virtual function 'fn'. We can access the first entry using vptr[0] (as this is just an array). So, in line 37, we just call the function using the function pointer. But wait, you might be asking why the following assembly line is there before that function call.
33 mov ecx, objIf you take another look into the implementation of function fn(), you can see that it prints out the member variable 'n', which is only avaliable to object 'obj'. Inside the function fn(), 'obj' needs to be set as 'this' pointer, to give the function fn() access to all it's members.
When we call the function fn() in this way: obj->fn(), the compiler does the job for us and sets 'obj' as 'this' before calling the function. But in line 37, we couldn't specify anything to the function fn() saying it is called for the object 'obj', so the function won't find out where to get the value of 'n' from. This is why we expicitly need to set the 'obj' as 'this' before we call the function fn() in line 37. We did that in line 33, in the assembly code. This line is again VC++ specific. In VC++, 'this' pointer is set in the register 'ECX'. Some other compiler may handle that differently.
If we had more virtual function, we could have access them using next indexes of vptr: vptr[1], vptr[2], etc.
We have learned some interesting facts about the virtual functions and the virtual table. We may not have any use of this kind of code where we need to directly access the virtual table in our general applications but this helps when you want to know more about C++ internals.
Enjoy!
July 12, 2008:
We assumed here that the vptr is placed in the beginning of the class object. here's a note on that:
Traditionally, the vptr has been placed after all the explicitly declared members of the class. More recently, it has been placed at the beginning of the class object. The C++ Standard allows the compiler the freedom to insert these internally generated members anywhere, even between those explicitly declared by the programmer.
Saturday, June 28, 2008
C++: Avoid using assignments in constructors
In my post Coding better C++, I've suggested a list of techniques to improve C++ coding. I shall try to explain them one at a time in the future posts. In this post, I've talked about "4. Avoid using assignments in constructors, use initializer list to initialize members".
The initialize list is used to avoid double construction of a contained object. Take the following example:
As you can see the result of the step 1.a, string::string(), is discarded by step 3.a, string::operator=(const char *), the step 1.a is therefore redundant.
We can optimize this with the initializer list, in the following way:
Many optimizations require some kind of a trade-off. You often trade speed for clarity, simplicity, re-usability, or some other metric. But in this example, optimization requires no sacrifice at all. This constructor will generate the exact same Student object with the exception of improved performance.
The initialize list is used to avoid double construction of a contained object. Take the following example:
1We create an object of the Student, for example, in the following way.
2 Class Student
3 {
4 public:
5 Student ( const char *_name)
6 {
7 name = _name;
8 }
9 private:
10 string name;
11 };
Student s (“Abc”);The following executions take place:
1. string name is initialized
a. string::string() function is called
2. body of the Student::Student(const char *) constructor is called
3. the line name = _name is executed
a. string::operator=(const char *) function is called
As you can see the result of the step 1.a, string::string(), is discarded by step 3.a, string::operator=(const char *), the step 1.a is therefore redundant.
We can optimize this with the initializer list, in the following way:
1 Class StudentIn this way, the string 'name' is initialized only once and with the value of _name and it calls the string::string(const char *) function directly.
2 {
3 public:
4 Student ( const char *_name) : name (_name)
5 {
6 }
7 private:
8 string name;
9 };
10
Many optimizations require some kind of a trade-off. You often trade speed for clarity, simplicity, re-usability, or some other metric. But in this example, optimization requires no sacrifice at all. This constructor will generate the exact same Student object with the exception of improved performance.
Saturday, June 21, 2008
Project management thoughts - 3. Parallel development issues
This is a continuation of the "Project management thoughts" series. Please read the previous posts if you haven't already.
In this post, I shall talk about why the parallel development of prototype and real application is not a good idea in a case like ours.
Why prototype and real development should not be in parallel
The purpose of the prototype is to try out different brainstorming results of the client and solidifying requirements from it. Now, the scope of the new requirements and 'change requests' to already solidified requirements have no limits, officially.
Let's take a break and do some basic maths of project management. A project has three main key factors: Time (T), Material (M) and Resources (R). Time is proportional to material and inversely proportional to resources. This can be represented in the following way.
Meaning: 1. If your time is fixed and if you increase your material, you also need to increase your resources, 2. If your resources are fixed and if you increase your material, you also need to increase your time, 3. If you material is fixed and if you change your resources, your time will get changed, and so on.
I'm sure most of us know this in one form or another and I only described in details for the newbies.
Now back to the prototype, if the prototype continuously gives us new requirements through out the whole project time line, we need to have time open ended, according to the simple math that we done above, but that not an option in the first place. This is fixed time project.
Besides new requirements, the prototype will also yield a list of small and large changes (large, in terms of task volume). These changes need to get reflected in the already built features of the real application. The changes, of course, are materials (M) which requires more time (T).
The changes imposed from the prototype will virtually have no limits. If we have already implemented, for example, 20 features in the real application that were previously solidified, those may require complete or partial reworks in a very short time. Adjusting major changes in the requirements makes a solid codebase messy, making it more vulnerable to major bugs. Re-structuring, re-designing (the software design) and re-implementing are probably the last things a development team wants to do. If requirements get changed every now and then, which we cannot guarantee, may end up with unexpectedly weak software design and codebase.
There are also other factors of why it doesn't seem to be a good idea to run prototype and real application development in parallel. One of them is the time it takes to solidify requirements from the prototype. If we assume that we shall get requirements solidified from the prototype at a constant rate (or at a good rate) then it would be very wrong in real life. In practical scenarios we may get some or many of requirements solidified from the prototype and we may also need to wait a significant amount of time to get something solidified as it is very much dependent on client's decisions. 'Waiting' on a dependency for uncertain period of time for a 'fixed time and resources' project like ours (or in any project), is just one of the failure factors.
The better solution
Our proposed solution was to complete the prototype application first, with the effort of the full development team, solidifying requirements from it, and then working on the real application, again with the effort of the full development team.
This solves a lot of problems, the ones that I mentioned above. The only concern with this way is, can we really complete the prototype with all ideas of the client in time and then can we finalize requirements from the prototype in time? Well, it requires effort from both of the side, the team and the client. Both parties need to work aggressively towards one goal, which is to identify requirements within a given time frame.
To be continued.
Coming up soon: The team formation, the technologies, the tools used, the prototype phase, the software design phase..
[I shall continue to talk about my project management experiences and thoughts about this project in regular posts. I hope you shall find them interesting or useful].
In this post, I shall talk about why the parallel development of prototype and real application is not a good idea in a case like ours.
Why prototype and real development should not be in parallel
The purpose of the prototype is to try out different brainstorming results of the client and solidifying requirements from it. Now, the scope of the new requirements and 'change requests' to already solidified requirements have no limits, officially.
Let's take a break and do some basic maths of project management. A project has three main key factors: Time (T), Material (M) and Resources (R). Time is proportional to material and inversely proportional to resources. This can be represented in the following way.
T = M / R
or T x R = M
or T x R = M
Meaning: 1. If your time is fixed and if you increase your material, you also need to increase your resources, 2. If your resources are fixed and if you increase your material, you also need to increase your time, 3. If you material is fixed and if you change your resources, your time will get changed, and so on.
I'm sure most of us know this in one form or another and I only described in details for the newbies.
Now back to the prototype, if the prototype continuously gives us new requirements through out the whole project time line, we need to have time open ended, according to the simple math that we done above, but that not an option in the first place. This is fixed time project.
Besides new requirements, the prototype will also yield a list of small and large changes (large, in terms of task volume). These changes need to get reflected in the already built features of the real application. The changes, of course, are materials (M) which requires more time (T).
The changes imposed from the prototype will virtually have no limits. If we have already implemented, for example, 20 features in the real application that were previously solidified, those may require complete or partial reworks in a very short time. Adjusting major changes in the requirements makes a solid codebase messy, making it more vulnerable to major bugs. Re-structuring, re-designing (the software design) and re-implementing are probably the last things a development team wants to do. If requirements get changed every now and then, which we cannot guarantee, may end up with unexpectedly weak software design and codebase.
There are also other factors of why it doesn't seem to be a good idea to run prototype and real application development in parallel. One of them is the time it takes to solidify requirements from the prototype. If we assume that we shall get requirements solidified from the prototype at a constant rate (or at a good rate) then it would be very wrong in real life. In practical scenarios we may get some or many of requirements solidified from the prototype and we may also need to wait a significant amount of time to get something solidified as it is very much dependent on client's decisions. 'Waiting' on a dependency for uncertain period of time for a 'fixed time and resources' project like ours (or in any project), is just one of the failure factors.
The better solution
Our proposed solution was to complete the prototype application first, with the effort of the full development team, solidifying requirements from it, and then working on the real application, again with the effort of the full development team.
This solves a lot of problems, the ones that I mentioned above. The only concern with this way is, can we really complete the prototype with all ideas of the client in time and then can we finalize requirements from the prototype in time? Well, it requires effort from both of the side, the team and the client. Both parties need to work aggressively towards one goal, which is to identify requirements within a given time frame.
To be continued.
Coming up soon: The team formation, the technologies, the tools used, the prototype phase, the software design phase..
[I shall continue to talk about my project management experiences and thoughts about this project in regular posts. I hope you shall find them interesting or useful].
Subscribe to:
Posts (Atom)