One thing that I kept developing all this time was the userland API, which was designed in such a way that it can be made available on multiple platforms. My thoughts behind this were that if there was such an API that could be used on multiple platforms, it could make it more interesting to third parties to eventually port software to my OS (yeah, I was planning that much ahead over twenty years ago when I was in my teens - I can't be contempt with just plan A and B and pretty much obsess with having backup plans with all the letters, numbers, letter combinations, foreign characters, etc. for every single situation). Most of the testing is actually done on Linux and up to a few years ago, a Windows port was also fully functional (now it's just a bit forgotten and hasn't been kept up to date).
I'd like to show some examples of parts of the API that are currently working and ask for everyone's opinions on whether you like it, or if you have any improvement suggestions.
The code samples are real working examples with the C++ version of the API. Without further delay, here they are:
First example: database access
Code: Select all
Net::Web::URI l_uri("firebird://user:password@server/database");
try
{
Database::Database l_database;
l_database.Connect(l_uri);
Database::Statement l_statement(l_database);
l_statement.Prepare("SELECT * FROM object WHERE object_id = ?");
Database::Transaction l_transaction = l_database;
l_transaction.Begin();
Database::Result l_result = (l_transaction, l_statement).Query(1);
Database::Row l_row = l_result;
while(++l_row)
{
// EN: instead of using casts, the programmer can also use the .to*() methods (toString, toCString, toInt, toUnsignedLong, toChar...)
JL_TRACE(0, "Object_id = %llu, name = %s\n", (unsigned huge) l_row["object_id"], (char *) l_row["name"]);
}
Database::Statement l_insert(l_database);
l_insert.Prepare("INSERT INTO test VALUES(?, ?)");
auto l_lines = (l_transaction, l_statement).Execute(1, 2);
l_transaction.Commit();
JL_TRACE(0, "Inserted %llu lines\n", l_lines);
}
catch(Exception & l_e)
{
JL_TRACE(0, "Exception caught\n");
l_e.printStackTrace();
}
Code: Select all
void * thread_func(void * p_param)
{
auto l_time = (unsigned native int) p_param;
JL_TRACE(0, "Starting thread %d\n", l_time);
while(true)
{
Threads::Thread::Sleep(l_time);
JL_TRACE(0, "Cycle completed %d\n", l_time);
}
}
int main(int p_argc, const char ** p_argv)
{
Threads::Thread l_thread1;
Threads::Thread l_thread2;
l_thread1 = Threads::ThreadTarget(thread_func);
l_thread2 = Threads::ThreadTarget(thread_func);
l_thread1.Run((void *) 5);
l_thread2.Run((void *) 7);
while(true)
{
Threads::Thread::Sleep(10);
JL_TRACE(0, "Main thread\n");
}
}
Code: Select all
String l_address = "127.0.0.1";
String l_port = "8080";
// EN: this is essentially a Containers::Vector<TAddress *>
Net::TAddressSet l_candidates;
Net::aIPAddress::resolveAddress(l_candidates, l_address, l_port);
// EN: this static method creates a stream socket appropriate for HTTP listening
auto l_socket = Net::Web::Server::makeSocket((*l_addresses)[0]);
Net::Web::Server l_server(l_socket);
// EN: this is the reception of a new connection, followed by the processing of the request
auto l_channel = dynamic_cast<Net::Web::Channel *>(l_server.Wait());
auto l_handshake = l_channel->ExpectoHandshakum();
Net::Web::Response l_response(l_handshake);
// EN: GenericResponse exists so that the same code supporting the webserver can be used by the programmer for other web-based protocols
Net::Web::GenericResponse l_responseLine("HTTP/1.1", (unsigned int_16) Net::Web::E_StatusCode::S200_OK);
l_response.set_preamble(l_responseLine, true);
Net::Web::ContentLengthHeader l_contentLength;
// EN: just sending an empty response
l_contentLength.set(0);
l_response.addHeaderPersistent(l_contentLength);
l_response.End();
delete l_channel;
delete l_socket;
delete l_handshake;
Code: Select all
Filesystem::File l_file;
l_file.open("File.txt", Streams::E_Flags::ReadOnly);
if(l_file.isReadable()) JL_TRACE(0, "File is readable\n");
if(l_file.isWritable()) JL_TRACE(0, "File is writable\n");
if(l_file.isSeekable()) JL_TRACE(0, "File is seekable\n");
Buffer l_buffer(1000);
// EN: .Read() with no size parameter will result in atempting to read up to the size of the allocated buffer automatically
unsigned int l_len = l_file.Read(l_buffer);
l_buffer[l_len] = 0;
JL_TRACE(0, "Read %d bytes of %llu:\n%s\n", l_len, l_file.get_size(), l_buffer);
l_file.Close();
"native" and "byte" are defined in the API. The first always translates to the native integer size of the machine where it's being compiled, and byte is just a typedef for unsigned byte.
The variable prefixes are part of my code formatting methods. Lowercase L identifies local variables, lowercase P identifies parameters. I find it that it makes the code way easier to navigate to know the origin of a variable right way just looking at it. It's probably an acquired taste.
Well... fire away...