Search icon CANCEL
Subscription
0
Cart icon
Your Cart (0 item)
Close icon
You have no products in your basket yet
Save more on your purchases now! discount-offer-chevron-icon
Savings automatically calculated. No voucher code required.
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Conferences
Free Learning
Arrow right icon
Systems Programming with C# and .NET
Systems Programming with C# and .NET

Systems Programming with C# and .NET: Building robust system solutions with C# 12 and .NET 8

Arrow left icon
Profile Icon Vroegop
Arrow right icon
$26.99 $38.99
eBook Jul 2024 474 pages 1st Edition
eBook
$26.99 $38.99
Paperback
$32.99 $47.99
Subscription
Free Trial
Renews at $19.99p/m
Arrow left icon
Profile Icon Vroegop
Arrow right icon
$26.99 $38.99
eBook Jul 2024 474 pages 1st Edition
eBook
$26.99 $38.99
Paperback
$32.99 $47.99
Subscription
Free Trial
Renews at $19.99p/m
eBook
$26.99 $38.99
Paperback
$32.99 $47.99
Subscription
Free Trial
Renews at $19.99p/m

What do you get with eBook?

Product feature icon Instant access to your Digital eBook purchase
Product feature icon Download this book in EPUB and PDF formats
Product feature icon Access this title in our online reader with advanced features
Product feature icon DRM FREE - Read whenever, wherever and however you want
Product feature icon AI Assistant (beta) to help accelerate your learning
Table of content icon View table of contents Preview book icon Preview Book

Systems Programming with C# and .NET

Overview of Systems Programming

So, you want to learn about systems programming in .NET, using C#. At least, I assume you want to learn that; you probably read the title of this book and decided that this was a good match. Maybe you have dived into systems programming a bit and want to get better at it. Or, perhaps you haven’t touched that subject and want to start. Or, maybe you picked the wrong book. If the latter is the case, I hope you still have your receipt so you can return this book and get something else. For all others: welcome!

Let’s define systems programming

Before we go into the nitty gritty details of systems programming, we need to set the stage. We need to have a common understanding of a couple of things. For instance, what does the term “systems programming” even mean? What is it for? Who is it for?

Let me get started with a definition.

Systems programming is the programming of systems. That might technically be correct, but I don’t think it helps us move forward.

Let us break it down: what is a system?

That one is easy. We have been building systems for ages, so we understand what we mean by a system.

Let me show you one definition:

A system is a set or arrangement of things that are related or connected so as to form a unity or organic whole. It is a collection of components or parts that interact with each other to function. This term is used in various fields such as physics, biology, computer science, and business management, each with slightly different connotations.

Great. But this definition is a bit broad. We might want to focus on computer science or software development. No problem; there are several definitions to choose from as well:

A system is a collection of software components that interact to perform a specific function or set of functions.

That is a lot better. If we dive into this a bit further, we can distinguish between different groups of systems:

  • Software systems: This is an integrated set of software components that work together to carry out a specific function or set of functions. Those components can be a database server, micro-services, and a frontend. Those components form the complete system, such as a CRM system, source control repository system, and others like that.
  • Operating systems (OSs): You probably know what an OS is. I think you have seen that term so often that there is a fair chance you didn’t even realize it is a system. But it most definitely is an OS that contains many parts and components, such as drivers, tools, helpers, and logs. Together, they deliver a system you as a user can use to run your software on, independently of the hardware.
  • Distributed systems: We often refer to loosely connected components on a network as a distributed system. Each part is isolated from the others, but they must collaborate to achieve something worthwhile. For example, Azure DevOps runs on many different servers in the Azure cloud. All the components run potentially on different servers and machines, and these components can even be running in different parts of the world. However, they work together to form a complete solution for the end user.
  • Embedded systems: An embedded system is usually a combination of hardware and software. The components are tightly coupled with each other. Developers usually write the software to match specific specifications so it uses the hardware best. Think, for instance, about the systems in your car. If you have a reasonably recent car, you undoubtedly have an entertainment system on board. The word “system” in “entertainment system” is a bit of a giveaway: it consists of many distinct components. There is very likely a device that can collect electromagnetic waves from the air (we call that a radio). That device is connected to some software that interprets those waves and turns them into an electrical signal to feed the speakers. Next to that, a component shows you, as the user, what you are listening to. I am sure you can find a lot of other systems in your car and probably in your TV, your phone, or your refrigerator.

There are many more examples, but I hope you see that a system always consists of individual components that are not useful on their own but, when combined, deliver a solution to a problem.

But hold on. We are not done yet.

Given these definitions and examples, you might think that the art of systems programming is just the programming of these systems, and you would not be wrong. But that is, in general, not what systems programming means. It most certainly is not what I mean by that.

Very, very roughly, we can divide software into two types:

  • User-facing software: This is software written to be used by people. It has a user interface (UI) with buttons, lists, labels, and more. People interact with the software by using various means of input modalities.
  • Software-facing software: This is software designed to be used by other software. There are no UIs since we have no users. We could say other components are the users, but when I say users, I mean people. Software-facing software interacts with other components through APIs, RPC (Remote Procedure Calls) calls, file transfer, and many other ways. No humans are involved in this.

It is the second type we are most interested in here in this book – software meant to be used by other software.

When is a system user-facing and when is it not?

It is not always clear when people are the primary users of some code or when other processes are. We could be very rigorous and say that anything with a UI is user-oriented; anything else is systems-oriented. That will make life easier for us if we want a clear definition. However, in the real world, the boundaries tend to blur.

Let me give you an example. Have a look at this Visual Studio Solution:

Figure 0.1: Solution Explorer with the Calculator project

Figure 0.1: Solution Explorer with the Calculator project

We have a very, very simple solution here. It has a main program called MyAwesomeCalculator that contains the main code. This is the entry point of our app, using the console as the UI. All logic is in the MathFunctions class library. This is where the magic happens.

If we go back to our definition of Systems programming, we could say that writing the MathFunctions class library is part of Systems programming. After all, no user will ever interact with the classes and interfaces in that library. It is the code in MyAwesomeCalculator that actually uses it.

Great! This means writing the MathFunctions library is systems programming! Well, not so fast. We might come to another conclusion if we look at the sequence diagram that explains the flow. Figure 0.2 shows this sequence diagram.

Figure 0.2: Sequence diagram for our calculations

Figure 0.2: Sequence diagram for our calculations

As you can see in Figure 0.2, the user initiates an operation: they want to add up two numbers. They enter it in the UI of the Main class. The Main class then instantiates an instance of the Adder class. After that creation, the Main class calls the AddUp(a,b) method. The result is passed back to the Main class and shown to the user. After all this, we could discard the Adder instance.

Great. Where are the boundaries? If we look at it this way, we could say that the code in Adder and, thus, in the MathFunctions library is immediately tied to user actions. So, it is user-facing code instead of systems-facing code.

I still like to use the question of who is using the code to determine what kind of software we are writing. But apparently, this is not enough. We need to go a bit deeper.

The code in MyAwesomeCalculator and MathFunctions are in separate assemblies. The user interacts with one assembly; the other is accessed through code only. But they can still be seen as one. If we run the application, the runtime creates AppDomain for us.

AppDomain in .NET is different than AppDomain in .NET Framework. The latter had more ways to isolate code from each other. That was nice, but it was a typical Windows feature. That did not translate well to other platforms. So, to make .NET applications run on other platforms, they needed to redesign this. This results in AppDomain being less restrictive than it used to be. Still, AppDomain remains a logical boundary between different processes. Code runs in one app domain and cannot access other app domains directly.

Here, we have another clue: our MyAwesomeCalculator app and the associated MathFunctions assembly all run in the same AppDomain. To the OS, they are one. Since we decided that actual people use the Main method, the same applies to all other pieces of code in that particular AppDomain.

Let’s rewrite our solution a bit. See the following screenshot.

Figure 0.3: Our solution with a worker process

Figure 0.3: Our solution with a worker process

We removed the class library with the code that did all the work. Instead, we created a new project. That project is a worker process. Technically, I should have kept that class library and referenced that, but I wanted to keep things simple.

A worker process is a background process that runs all the time (not technically true, but for now, this is true enough). It just sits there doing nothing. Then, suddenly, something of interest happens, and it comes to life, does its job, and goes to idle mode again.

As shown in Figure 0.4, the sequence diagram in this case is also slightly different.

Figure 0.4: Sequence diagram for the new revised architecture

Figure 0.4: Sequence diagram for the new revised architecture

MyAwesomeCalculator and the MathFunctionServices worker are now independent of each other. They each run in their own AppDomain. When the user wants to perform the calculation, they enter this in the UI, which invokes the service. The Worker class picks up the command, creates an instance of the Adder class, calls the AddUp method, and then calls the MyAwesomeCalculator again with the results.

As you can see, the calls between all classes are synchronous (designated by a line with a solid arrowhead) except for the call between Main and Worker. That is asynchronous (designated by a line and an open arrowhead).

That makes sense; the calculator cannot know whether the command has arrived or the service is listening. It just does a fire-and-forget, crosses its digital fingers, and hopes for the best.

This is more like it. This is genuinely writing software used by other software (I am talking about MathFunctionServices here, not MyAwesomeCalculator).

I have not shown you how the code in Main calls Worker and how the result flows back from Worker to Main. After all, they are in separate app domains. So, they cannot share memory, right? That is correct. I did not show you that. But do not worry. I have a couple of chapters dedicated to this.

It is important to realize that MathFunctionServices does not have a UI in the ordinary sense of the word. No user ever touches this code. It lies there, dormant, until its services are required. If we compare that to the first example, we see the differences. That first example had all code loaded on the user’s demand, and it somehow all responded to the users’ actions.

A better definition

So, if we combine all of this, we can determine that systems programming is the art of writing components that can perform a function or a set of functions but interact only with other components.

That is what this book is all about. We will learn how to write software that is to be consumed by other software. That is a whole other way of looking at software, requirements, design considerations, and more compared to software meant for humans.

Writing software for software means other ways of thinking about communications, performance, memory usage, security, and so on. All those topics are covered here in this book. Now, you might say: “But wait a minute. Software written for users should also keep performance in mind!” You are right, but software communicating with software has unique needs.

Later chapters show how you can achieve the desired performance and explain why this is important. Let us agree that a component, potentially called thousands of times per second, could use more thought about performance than a screen with a button that a user might click once an hour. I am exaggerating here, but I am sure you get the point.

The same applies to memory consumption. I believe we should always write all software with memory consumption in mind. However, a component that gets used frequently by many other systems tends to be much more vulnerable to issues with memory than other software programs.

Performance and memory pressure are essential when we think about writing embedded systems. Embedded software usually runs on very limited hardware, so we have to try and take advantage of every trick in the book to get it running as fast as possible and using as little memory as possible.

As promised, we will spend much time looking at ways to communicate with these types of software.

To me, Systems programming is the purest form of software development. It is all about algorithms, tweaks, and trying out every trick in the book to get the most out of it. systems programming is the major league of software development. When you have this covered, all other software you write will also benefit from your newfound knowledge. What you learn when writing systems software will become second nature, and you will improve your overall software writing skills. Does this sound exciting? Then, let’s get started!

Using C# and .NET in systems programming

We already run into a problem. You most likely are a C# developer. Maybe you are a VB.Net developer. But no matter what language, you are a .NET developer. After all, that is what this book is about.

Traditionally, Systems programming is done in Assembly, C, and C++. Systems programming has always been the realm of hardcore developers who know the systems they are working on inside out. In the early 50s of the last century, people wrote systems software using switches. A switch in the up position meant a 1, and a switch in the down position meant a 0. These early computers had 8, 16, or even more switches that pointed to the memory address to read or write. Then, 8 switches represented all the bits in a byte for that memory address. Above these switches, there were little lights (no, not LEDS: that invention happened later). Those little lights, if illuminated, meant a 1 in that byte (and a 0 if not illuminated). That way, you could read the contents of that memory address.

Do not worry; that kind of low-level programming is not the topic of this book. If you are interested, there are good remakes of the original Altair 8800 that started a company called Microsoft. You can program that computer in this way: use the switches and lights on the front panel to enter your software. That is how Bill Gates and Paul Allen wrote their first software. But we have other tools at our disposal.

Since systems software relies on efficient, fast, and memory-aware code, people often use programming languages close to the metal. That usually means using language such as machine code – such as the switches I mentioned earlier. Assembly language is another language used, especially in the seventies and eighties of the last century. C and later C++ are other examples of languages that can take advantage of the specifics of the hardware. Most parts of Windows, for instance, are written in C.

However, systems developers do not restrict themselves to low-level languages only. Let me give you an example.

Higher-level languages for systems programming

In 1965, IBM published a manual called PL/I Language Specifications. C28-6571. This relatively obscure title is a fascinating read: it outlines the specifications of the PL/I programming language. PL/I, a sort of abbreviation for Programming Language One, is a higher-level programming language. It contains block structures to allow for recursion, many different datatypes, exception handling, and many other features we take for granted today. It truly was a high-level language. However, they used it to write parts of the early OSs inside IBM. Remember, this was in the sixties when every microsecond counted. Machines were extremely slow compared to modern systems, so they had to utilize every trick in the book to make things work. Yet, a high-level language was considered appropriate. That means there is no reason not to use a high-level language today, especially considering memory profilers’ compiler techniques and advantages.

Kernel mode and user mode

OSs and drivers are usually not built using .NET. The reason for this is that drivers and most parts of the OS run in kernel mode.

The CPU in your computer can run in two modes: kernel or system mode and user mode. User mode is where most of the applications run. The CPU shields the applications from using other memory or process spaces. The CPU protects the applications by placing them in a sandbox. That is precisely what you would want: it would be very undesirable for a program to snoop around in another application’s memory. The processor handles this level of security.

Kernel mode, however, does not have those limitations. Software running in kernel mode is less restricted, controlled, and trusted. That makes sense: parts of an OS should be able to run in all parts of the system, including in the space of other applications.

However, to run in kernel, the compiled code needs to have certain flags set, and the layout of the binaries should be very specific. That is the problem we face. Our C# code relies heavily on the .NET Runtime, and that runtime is not built to be used in Kernel mode. So, even if we could compile our code so that the OS would accept it, it still would not work due to the app not loading the runtime.

There are ways around this. There are ways to pre-compile and include the runtime classes in your binary. Then, you can modify that binary to run in kernel mode. However, the results may vary, and the whole thing would be unreliable. Unreliable code is the exact opposite of what a device driver or OS part should be, so we will not get into this in this book. It’s a hack, not a standard way of working.

Although this book does not deal with kernel-mode apps, I want to give you some insight. Especially since systems programming is usually programming “close to the metal,” so to speak, we are interacting with systems that are running in kernel mode.

Kernel mode is a mode in the CPU. A system can request the CPU to turn on kernel mode. If the code requesting it has the proper privileges, the CPU will do so, thus unlocking parts of the memory previously unavailable. The code does what it needs to do, and then the CPU returns to user mode. Since the code is still in memory doing all sorts of things, it is quite wrong to say an app is a kernel or user-mode app. Some apps can switch the CPU into that state, but the app is almost always running in mixed mode: most of the time, it is in user mode, sometimes kernel mode. Oh, and when I say CPU, I mean logical CPU. This toggling happens on that level, not on the chip itself (but it can also do that).

I have Adobe Creative Cloud installed on my machine. We all know Photoshop, Illustrator, and Premiere, but these apps are meant to be accessed through the Creative Cloud app. This app monitors the system and launches any app you need when you need it. It also updates the background and keeps track of your fonts, files, colors, and other things like that.

Whenever you read something like “runs in the background,” you might expect some systems programming going on, and indeed, there is.

For example, I get this image if I start Performance Monitor on my system and add the % Privileged Time and % User Time counters for the Adobe Desktop Service process.

Figure 0.5: Performance Monitor showing kernel and user times

Figure 0.5: Performance Monitor showing kernel and user times

The red line in Figure 0.5 shows how much time the Adobe Desktop Service spends in user time. The green line, however, shows how long the service is running in privileged time, and privileged time is just a fancy term for kernel time.

As you can see, this app is doing much work in the kernel time. Although I have to admit, I have no clue what it is doing there, but I am sure it is all for a good reason.

We will encounter kernel mode later in other chapters but we will not build apps that run in it.

Why use .NET?

So, we established that we cannot build an OS or a device driver in .NET. That might lead to the question: “Can we use .NET for systems programming?” The answer is a big yes. Otherwise, this would have been a very thin and short book.

Shall we have a look at our recently discovered definition of systems programming? “Writing software used by other software, as a part of a bigger system that works together to achieve a certain goal.” I have shortened the definition, but it is all about this.

Looking at it this way, we can use .NET to write that software. Better yet: I bet .NET is one of the best choices to do so.

.NET offers many advantages over plain C or even C++ (not the managed kind of C++, that is still .NET.)

Back in the day, when we used .NET-Framework-based applications, it would have been a bad idea to use that for systems programming. However, with the introduction of the latest versions of .NET, many disadvantages have been taken care of. With many disadvantages out of the way, .NET-based systems are a viable choice for these kinds of systems.

C and C++ still are excellent languages for low-level systems code. However, C# and .NET Core have their advantages as well.

This table lays out the differences.

Topic

C# and .net core

C/C++

Performance

.NET Core has improved performance compared to .NET Framework, but there may still be overhead due to its runtime. This won’t be an issue for most applications, but it could matter for highly performance-critical systems.

C/C++ provides direct control over hardware and, with careful optimization, can yield superior performance in performance-critical systems.

Memory management

.NET Core still provides automatic garbage collection, reducing the chance of memory leaks, but it gives less control to the developer. This is more suitable for application-level programming.

C/C++ gives developers direct control over memory allocation and deallocation, making it more suitable for systems programming that requires fine-grained memory management.

System-level programming

Some system-level programming tasks may still be more difficult in .NET Core due to its higher-level abstractions and safety features.

C/C++ is often used for system-level programming because it allows for direct hardware access and low-level system calls, which are essential for kernel development, device drivers, and so on.

Portability

.NET Core applications can run on multiple platforms without recompilation, but you must install the .NET Runtime on the target machine. This is an improvement over .NET Framework.

C and C++ code can be compiled and run on virtually any system but often requires careful management of platform-specific differences.

Runtime requirement

.NET Core applications still require the .NET Core Runtime to be installed on the target machine. This can limit its use on systems with limited resources.

C and C++ applications compile down to machine code and don’t require a separate runtime. This can be beneficial for system-level applications or when working with resource-constrained systems.

Direct control

C# and .NET Core still provide many abstractions that can increase productivity, but these abstractions can limit direct control over the system and how code runs.

C/C++ provides more direct control over the system, allowing for finely tuned optimizations and precise control over how your code runs.

Community and support

.NET Core and C# have a growing community and plenty of support resources, including for cross-platform development.

C/C++ has a large, established community, many open-source projects, and a vast amount of existing system-level code.

Table 0.1: Comparison of C# and C/C++

As you can see, both options have advantages and disadvantages. However, most of the disadvantages of .NET Core can be removed using clever tricks and smart programming. Those are the topics of the rest of this book.

C# is a very mature and well-designed language. The capabilities far exceed what developers had when they used C to build, for example, the Unix OS.

What is .NET anyway?

.NET Core is the next version of the over two decades old framework that was meant to help developers get their work done quickly.

It all started with .NET Framework 1 back in 2002. Microsoft presented it as the end-all solution to many issues developers were facing. Fun fact: the project had the internal code name Project 42. You get bonus points if you know why they chose that name.

In the years following the introduction, we have seen many different functions of .NET Framework. Microsoft released the last version of .NET Framework on April 18, 2019.

Before that, Microsoft realized they needed to support other platforms as well. They wanted .NET to be available everywhere, including Linux, Macintosh, and most mobile devices. That meant they had to make fundamental changes to the runtime and the framework. Instead of having different runtime versions for each platform, they decided to have a unified version. That became .NET Core. Microsoft released this in June 2016.

.NET Standard was a set of specifications. The specifications told all developers which features of the runtime were available in which version of the runtime. Most developers did not understand the purpose of .NET Standard and assumed it was yet another version of the runtime. But once they got the idea behind this, it made a lot of sense. If you need a specific API, look it up in the documentation, see what version of .NET Standard it was supported, and then check whether your desired runtime supported that version of .NET Standard.

An example might be helpful here. Let’s say you build an app that does some fancy drawing on the screen. You have worked with System.Drawing.Bitmap before, so you want to use that again. However, your new app should be running on .NET Core. Can you reuse your code? If you look up the documentation of the System.Drawing.Bitmap class, you see the following:

Product

Versions

.NET framework

1.1, 2.0, 3.0, 3.5, 4.0, 4.5, 4.5.1, 4.5.2, 4.6, 4.6.1, 4.6.2, 4.7, 4.7.1, 4.7.2, 4.8, 4.8.1

. NET platform extensions

2.1, 2.2, 3.0, 3.1, 5, 6, 7, 8

Windows desktop

3.0, 3.1, 5, 6, 7, 8

Table 0.2: Support for System.Drawing.Bitmap

Darn. This class is not part of .NET Standard. It is not available in all runtimes out there. You need to find another way to draw your images.

Your app also communicates with the outside world. It uses the HttpClient class, found in the System.Net.Http namespace. Can you move that to other platforms? Again, we need to look up the documentation of that class. There, we see this table:

Product

Versions

.NET

Core 1.0, core 1.1, core 2.0, core 2.1, core 2.2, core 3.0, core 3.1, 5, 6, 7, 8

.NET framework

4.5, 4.5.1, 4.5.2, 4.6, 4.6.1, 4.6.2, 4.7, 4.7.1, 4.7.2, 4.8, 4.8.1

.NET standard

1.1, 1.2, 1.3, 1.4, 1.6, 2.0, 2.1

Uwp

10.0

Xamarin.ios

10.8

Xamarin.mac

3.0

Table 0.3: Support for Sstem.Net.Http.HttpClient

Now, that is more like it. HttpClient is part of the .NET Standard specification, which means that all runtimes that support the mentioned versions of .NET Standard implement this class. You are good to go!

.NET, .NET Framework, .NET Standard – what is all this?

Table 0.3 shows .NET Framework, .NET Standard, and .NET but not .NET Core. We do see .NET, though. What is this all about?

.NET Core was introduced to sit next to .NET Framework. Microsoft intended for .NET Framework to support Windows devices. However, as I explained, Microsoft later decided to support other devices, OSs, and other hardware architectures; hence the introduction of .NET Core. Then, they realized that this complicated things a lot. People lost track of what they could use and where they could use it. The solution to this was the introduction of the .NET Standard specifications, but that only worsened things – even the people who were not confused initially lost track of what was going on.

The version numbering was an issue as well. We have .NET Framework version 4.8.1 that matched .NET Standard 2.1. .NET Core 3.1 also supported .NET Standard 2.1. Many people had no idea what was happening. They could not understand why a .NET (Core) version of 3.0 was newer than .NET 4.5.

Microsoft saw this problem as well. They also had internal issues: they had to backport a lot of the code in the libraries so it would be available everywhere. To eliminate this mess once and for all, they announced that .NET Framework 4.8 would be the last version. .NET Core 3.1 would be the last version. From now on, it was all unified in something called .NET. Then, to prevent issues with the numbering, .NET started with the number 5.

They also made it easier to track when new versions would come out. Every single year, there will be a new version of .NET. So far, the odd numbers are under Long Term Support (LTS); the even numbers are under Standard Term Support (STS). STS is 18 months, and LTS is 3 years.

.NET 5 was an STS version, and since it was released in November 2020, the support ended in May 2022. .NET 6 was an LTS version. Released in November 2021, support ends November 2024. .NET 7 is again an STS, released in November 2022, with an end of life in May 2024.

By the time of writing this book, the preview versions of .NET 8 are out, and that will be an LTS version.

This is what I use in this book.

Now, the versioning is clear. The release cycle is understood. We can finally let that go. We can focus on building cool stuff instead of worrying about versions.

Programming languages – a choice to make

We are not done yet. We have figured out which version of the runtime we need. But the runtime is just that: a runtime. A set of libraries that we can use. Those libraries have a lot of tools and pre-built classes available, so we do not have to write that. That is awesome. However, we still have to write some code ourselves. We do that in a programming language, and then link to the libraries, compile the code, and have a binary we can deploy and run.

What language should we use?

Microsoft offers us three choices. Others have made their own .NET-compatible languages, but we ignore them. These days, the main languages to write .NET code are C#, F#, and Visual Basic.

F# is a language used for functional programming. This is a different approach to programming than most people are used to, but the financial domain and data-intensive systems use it a lot.

Visual Basic is an excellent language for people just getting started in development. Back in the nineties, at the end of the last century, it was one of the few options people had to build GUI systems rapidly. When .NET came along, Microsoft quickly ported Visual Basic to support this framework, so developers did not have as steep a learning curve. However, usage of Visual Basic is dwindling now that Microsoft stopped co-evolving it with C#.

C# is the language we use in this book.

Although not coupled with the available runtime, Microsoft seems to release a new version of the language around the same time they release a new version of .NET. Version 11 of the language came out in November 2022. Version 12 of C# is now in preview when writing this book.

Each new version of the language has improvements, but many are syntactic. That means that if you cannot use the latest language version, you can still use all the features in the runtime. They are officially decoupled. Sometimes, it is just a bit more typing work.

The .NET Runtime is an excellent foundation for building all sorts of systems. The ecosystem surrounding .NET is very extensive. Next, a huge group of people contributes to the framework daily. It is hard to think of a task that cannot be performed with .NET or one of the thousands of NuGet packages available.

Again, real Kernel mode systems, such as device drivers, are best built with non-managed languages. However, for all other purposes, .NET and C# are an excellent choice.

Now what?

Congratulations! You have made the first steps towards becoming a systems programmer. You now know what systems programming is and how it differs from the usual day-to-day programming you might be used to doing.

You know about the background of programming and the challenges our predecessors faced, and you know why .NET is such an awesome tool to build systems software in.

We are ready to take the next step. We will dive into the nitty-gritty details. However, before we do that, we will need to talk about APIs and .NET Framework, its upsides, and its downsides. So, let’s go!

Setting up your development environment

I asked you to follow along. I requested that you open up your development environment and do what I do. However, to do that, you need to set up the right kind of development environment so that you can actually do what I do.

Let me help you with that.

I use Visual Studio 2022 Enterprise. There is no particular reason I use the Enterprise version besides having that on my machine. There are two other versions: The Professional and the free Community edition. All three versions are fine for the things we want to do. However, the Enterprise edition does have some debugging tools we might need when discussing debugging. When that time comes, I will pinpoint the differences and show you other ways of achieving your goals.

Alternatives such as JetBrains Rider and Visual Studio Code also work, but you might have to do more work yourself when we go into performance tuning and debugging. Again, I will tell you about these when we get there.

I have limited experience with Rider, so I cannot tell you precisely what you need to do, but I am sure that when you are an experienced developer, you can translate what I am showing you into the tools you know and love.

Use what you have and what you know. I am cool with that.

If you decide to go with Visual Studio, which I highly recommend, you should use version 2022 instead of 2019. The latest versions of .NET and C# offer a lot related to performance tuning and memory optimizations. Those versions are only available in the 2022 version of Visual Studio. So, make sure you have that one on your device.

Next to that, we will be doing a lot of console stuff. That means using PowerShell: gone are the days of using cmd.exe.

I highly recommend downloading Windows Terminal. With Terminal, you can have all sorts of consoles. We will use PowerShell most of the time, but when we talk about Linux, we will use the WSL feature to use our machines as Linux machines.

Downloading and installing Terminal is a breeze: you can find it on the Microsoft Store.

Make sure to install Windows Subsystem for Linux as well. Instructions on how to do that are all over the internet; I will not repeat that here.

Once you have installed all of your favorite tools, you can select any one of them in your Terminal. Mine looks like this:

Figure 0.6: Windows Terminal with different shells

Figure 0.6: Windows Terminal with different shells

As you can see, I have PowerShell, Command Prompt, Ubuntu, Azure Cloud Shell, and some more installed. Selecting one of them is a matter of clicking.

Switching between Linux and Windows has never been easier!

Another tool we will be using later on is WinDbg. WinDbg is an extremely powerful external debugger. It can give you a lot of information about the processes you are interested in. It runs standalone, so you do not have to attach Visual Studio to the process. There are versions available for both X86 and ARM, so it is usable on many devices. You can find WinDbg on the Microsoft website at https://learn.microsoft.com/en-us/windows-hardware/drivers/debugger/. Download and install that. WinDbg might become one of your latest best friends.

Next, you might want to install PerfView. It is a free and open-source performance monitoring tool from Microsoft, specially built for analyzing performance on .NET applications.

You can find the source code at https://github.com/Microsoft/perfview. You can download the sources and build the tool yourself or grab one of the pre-build versions. Those are also on that same site. I would suggest building yourself and looking through the source code: there are some terrific examples of how to build software like this. I do not intend to describe how the tool works internally, but I will use it when discussing performance.

Now, all you need is a cup of your favorite beverage, and we are good to go!

Left arrow icon Right arrow icon
Download code icon Download Code

Key benefits

  • Engage in hands-on exercises to effectively apply systems programming concepts
  • Gain insights into Linux and embedded systems and broaden your development capabilities
  • Learn how to deploy and maintain applications securely in diverse production environments
  • Purchase of the print or Kindle book includes a free PDF eBook

Description

If you want to explore the vast potential of C# and .NET to build high-performance applications, then this book is for you. Written by a 17-time awardee of the Microsoft MVP award, this book delves into low-level programming with C# and .NET. The book starts by introducing fundamental concepts such as low-level APIs, memory management, and performance optimization. Each chapter imparts practical skills, guiding you through threads, file I/O, and network protocols. With a focus on real-world applications, you’ll learn how to secure systems, implement effective logging, and deploy applications seamlessly. The book particularly emphasizes debugging, profiling, and addressing challenges unique to multithreaded and asynchronous code. You’ll also gain insights into cybersecurity essentials to help you safeguard data and establish secure communications. Moreover, a dedicated chapter on systems programming in Linux will help you broaden your horizons and explore cross-platform development. For those venturing into embedded systems, the final chapter offers hands-on guidance. By the end of this book, you’ll be ready to deploy, distribute, and maintain applications in production systems.

Who is this book for?

This book is for C# developers and programmers looking to deepen their expertise in systems programming with .NET Core. Professionals aspiring to architect high-performance applications, system engineers, and those involved in deploying and maintaining applications in production environments will also find this book useful. A basic understanding of C# and .NET Core is recommended, making it suitable for developers who are getting started with systems programming in C# and .NET Core.

What you will learn

  • Explore low-level APIs for enhanced control and performance
  • Optimize applications with memory management strategies
  • Develop secure, efficient networking applications using C# and .NET
  • Implement effective logging, monitoring, and metrics for system health
  • Navigate Linux environments for cross-platform proficiency
  • Interact with hardware devices, GPIO pins, and embedded systems
  • Deploy and distribute apps securely with continuous integration and continuous deployment (CI/CD) pipelines
  • Debug and profile efficiently, addressing multithreaded challenges

Product Details

Country selected
Publication date, Length, Edition, Language, ISBN-13
Publication date : Jul 30, 2024
Length: 474 pages
Edition : 1st
Language : English
ISBN-13 : 9781835083284
Category :
Languages :
Tools :

What do you get with eBook?

Product feature icon Instant access to your Digital eBook purchase
Product feature icon Download this book in EPUB and PDF formats
Product feature icon Access this title in our online reader with advanced features
Product feature icon DRM FREE - Read whenever, wherever and however you want
Product feature icon AI Assistant (beta) to help accelerate your learning

Product Details

Publication date : Jul 30, 2024
Length: 474 pages
Edition : 1st
Language : English
ISBN-13 : 9781835083284
Category :
Languages :
Tools :

Packt Subscriptions

See our plans and pricing
Modal Close icon
$19.99 billed monthly
Feature tick icon Unlimited access to Packt's library of 7,000+ practical books and videos
Feature tick icon Constantly refreshed with 50+ new titles a month
Feature tick icon Exclusive Early access to books as they're written
Feature tick icon Solve problems while you work with advanced search and reference features
Feature tick icon Offline reading on the mobile app
Feature tick icon Simple pricing, no contract
$199.99 billed annually
Feature tick icon Unlimited access to Packt's library of 7,000+ practical books and videos
Feature tick icon Constantly refreshed with 50+ new titles a month
Feature tick icon Exclusive Early access to books as they're written
Feature tick icon Solve problems while you work with advanced search and reference features
Feature tick icon Offline reading on the mobile app
Feature tick icon Choose a DRM-free eBook or Video every month to keep
Feature tick icon PLUS own as many other DRM-free eBooks or Videos as you like for just $5 each
Feature tick icon Exclusive print discounts
$279.99 billed in 18 months
Feature tick icon Unlimited access to Packt's library of 7,000+ practical books and videos
Feature tick icon Constantly refreshed with 50+ new titles a month
Feature tick icon Exclusive Early access to books as they're written
Feature tick icon Solve problems while you work with advanced search and reference features
Feature tick icon Offline reading on the mobile app
Feature tick icon Choose a DRM-free eBook or Video every month to keep
Feature tick icon PLUS own as many other DRM-free eBooks or Videos as you like for just $5 each
Feature tick icon Exclusive print discounts

Frequently bought together


Stars icon
Total $ 107.97 137.97 30.00 saved
Systems Programming with C# and .NET
$32.99 $47.99
Functional Programming with C#
$32.99 $47.99
Effective .NET Memory Management
$41.99
Total $ 107.97 137.97 30.00 saved Stars icon

Table of Contents

17 Chapters
Overview of Systems Programming Chevron down icon Chevron up icon
Chapter 1: The One with the Low-Level Secrets Chevron down icon Chevron up icon
Chapter 2: The One Where Speed Matters Chevron down icon Chevron up icon
Chapter 3: The One with the Memory Games Chevron down icon Chevron up icon
Chapter 4: The One with the Thread Tangles Chevron down icon Chevron up icon
Chapter 5: The One with the Filesystem Chronicles Chevron down icon Chevron up icon
Chapter 6: The One Where Processes Whisper Chevron down icon Chevron up icon
Chapter 7: The One with the Operating System Tango Chevron down icon Chevron up icon
Chapter 8: The One with the Network Navigation Chevron down icon Chevron up icon
Chapter 9: The One with the Hardware Handshakes Chevron down icon Chevron up icon
Chapter 10: The One with the Systems Check-Ups Chevron down icon Chevron up icon
Chapter 11: The One with the Debugging Dances Chevron down icon Chevron up icon
Chapter 12: The One with the Security Safeguards Chevron down icon Chevron up icon
Chapter 13: The One with the Deployment Dramas Chevron down icon Chevron up icon
Chapter 14: The One with the Linux Leaps Chevron down icon Chevron up icon
Index Chevron down icon Chevron up icon
Other Books You May Enjoy Chevron down icon Chevron up icon
Get free access to Packt library with over 7500+ books and video courses for 7 days!
Start Free Trial

FAQs

How do I buy and download an eBook? Chevron down icon Chevron up icon

Where there is an eBook version of a title available, you can buy it from the book details for that title. Add either the standalone eBook or the eBook and print book bundle to your shopping cart. Your eBook will show in your cart as a product on its own. After completing checkout and payment in the normal way, you will receive your receipt on the screen containing a link to a personalised PDF download file. This link will remain active for 30 days. You can download backup copies of the file by logging in to your account at any time.

If you already have Adobe reader installed, then clicking on the link will download and open the PDF file directly. If you don't, then save the PDF file on your machine and download the Reader to view it.

Please Note: Packt eBooks are non-returnable and non-refundable.

Packt eBook and Licensing When you buy an eBook from Packt Publishing, completing your purchase means you accept the terms of our licence agreement. Please read the full text of the agreement. In it we have tried to balance the need for the ebook to be usable for you the reader with our needs to protect the rights of us as Publishers and of our authors. In summary, the agreement says:

  • You may make copies of your eBook for your own use onto any machine
  • You may not pass copies of the eBook on to anyone else
How can I make a purchase on your website? Chevron down icon Chevron up icon

If you want to purchase a video course, eBook or Bundle (Print+eBook) please follow below steps:

  1. Register on our website using your email address and the password.
  2. Search for the title by name or ISBN using the search option.
  3. Select the title you want to purchase.
  4. Choose the format you wish to purchase the title in; if you order the Print Book, you get a free eBook copy of the same title. 
  5. Proceed with the checkout process (payment to be made using Credit Card, Debit Cart, or PayPal)
Where can I access support around an eBook? Chevron down icon Chevron up icon
  • If you experience a problem with using or installing Adobe Reader, the contact Adobe directly.
  • To view the errata for the book, see www.packtpub.com/support and view the pages for the title you have.
  • To view your account details or to download a new copy of the book go to www.packtpub.com/account
  • To contact us directly if a problem is not resolved, use www.packtpub.com/contact-us
What eBook formats do Packt support? Chevron down icon Chevron up icon

Our eBooks are currently available in a variety of formats such as PDF and ePubs. In the future, this may well change with trends and development in technology, but please note that our PDFs are not Adobe eBook Reader format, which has greater restrictions on security.

You will need to use Adobe Reader v9 or later in order to read Packt's PDF eBooks.

What are the benefits of eBooks? Chevron down icon Chevron up icon
  • You can get the information you need immediately
  • You can easily take them with you on a laptop
  • You can download them an unlimited number of times
  • You can print them out
  • They are copy-paste enabled
  • They are searchable
  • There is no password protection
  • They are lower price than print
  • They save resources and space
What is an eBook? Chevron down icon Chevron up icon

Packt eBooks are a complete electronic version of the print edition, available in PDF and ePub formats. Every piece of content down to the page numbering is the same. Because we save the costs of printing and shipping the book to you, we are able to offer eBooks at a lower cost than print editions.

When you have purchased an eBook, simply login to your account and click on the link in Your Download Area. We recommend you saving the file to your hard drive before opening it.

For optimal viewing of our eBooks, we recommend you download and install the free Adobe Reader version 9.