Userland API draft (asking for opinions)

Programming, for all ages and all languages.
Post Reply
User avatar
BigBuda
Member
Member
Posts: 104
Joined: Fri Sep 03, 2021 5:20 pm

Userland API draft (asking for opinions)

Post by BigBuda »

So, my desire to build my own OS started over 25 years ago. It suffered many periods of stagnation due to lack of time, but I'm picking it up again. I was there together with a lot of other pioneers of the hobby OS scene that are today part of this community. Used to run all the usual sources at the time (HelpPC, Chris Lattner's sabre/nondot/os, qbfiles... still have an extensive library of tutorials, datasheets and example source from the 90s and boy did I drool over the then .txt format of the i386 Intel specification, the appearance of NASM and all that how I miss NASMIDE... at the time the closest we had to this forum were the Newsgroups)

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();
  }
Second example: threads

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");
  }

}
Third example: trivial one-client webserver

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;
Fourth example: file access

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();
The API isn't closed yet, the next part of it to receive an overhaul is the Networking section. I've been using it for other projects as well, outside of my OS, and have found it feels quite intuitive to me (though I'm clearly biased) but I'd really appreciate any suggestions you might have that could help make it even more intuitive.

"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...
Last edited by BigBuda on Sat Sep 18, 2021 2:35 pm, edited 3 times in total.
Writing a bootloader in under 15 minutes: https://www.youtube.com/watch?v=0E0FKjvTA0M
dseller
Member
Member
Posts: 84
Joined: Thu Jul 03, 2014 5:18 am
Location: The Netherlands
Contact:

Re: Userland API draft (asking for opinions)

Post by dseller »

I like it. Very clean API! I’ve enjoyed your contributions so far :)
User avatar
BigBuda
Member
Member
Posts: 104
Joined: Fri Sep 03, 2021 5:20 pm

Re: Userland API draft (asking for opinions)

Post by BigBuda »

dseller wrote:I like it. Very clean API! I’ve enjoyed your contributions so far :)
Well, thank you! And here I was preparing for a boatload of criticism!
Writing a bootloader in under 15 minutes: https://www.youtube.com/watch?v=0E0FKjvTA0M
dseller
Member
Member
Posts: 84
Joined: Thu Jul 03, 2014 5:18 am
Location: The Netherlands
Contact:

Re: Userland API draft (asking for opinions)

Post by dseller »

BigBuda wrote:
dseller wrote:I like it. Very clean API! I’ve enjoyed your contributions so far :)
Well, thank you! And here I was preparing for a boatload of criticism!
Heh, well, I do think your naming convention is a bit strange, but nothing regarding the API itself!
User avatar
BigBuda
Member
Member
Posts: 104
Joined: Fri Sep 03, 2021 5:20 pm

Re: Userland API draft (asking for opinions)

Post by BigBuda »

dseller wrote:Heh, well, I do think your naming convention is a bit strange, but nothing regarding the API itself!
If you're referring to the prefixes, those have a very good reason to be. I've been using that naming convention for over 20 years and passing it along to others, and it has worked way better than I initially anticipated when I first began using it. That naming convention helps prevent a lot, but I mean A LOT of mistakes and bugs because it reduces name clashes and makes it very clear where that variable came from. If it begins with a lowercase L, then you immediately know it's a local variable and your lookup zone is restricted to the point you're at in the code and the beginning of that method function. If it's a lowercase P, then you immediately know it's a parameter of that method or function. If it's a lowercase M, then you know it's an attribute (member) of that class or structure. I find that immediately knowing the origin of a variable way more important and useful than knowing the type, like it's done the hungarian notation that Microsoft used so much. The way I format code is even more useful for people with dyslexia, and that has been shown with actual field testing.
I perfectly understand that it can seem strange at first, but I'd definitely recommend that you try it for yourself for a while. The extra two characters per variable that you type will more than make up for the benefits, it's my really honest opinion. It helps a lot in helping maintain the code self-documented and easy to navigate.
Writing a bootloader in under 15 minutes: https://www.youtube.com/watch?v=0E0FKjvTA0M
Post Reply