Source Generators: Making API Calls Easy

DOTNET-VERSION-7@0.25x

Back in the early days of programming, making calls to the Windows API was a common way to accomplish tasks like creating message boxes, handling UI, and working with files. However, these calls could be tricky to use correctly and could cause issues if not implemented perfectly.

Thankfully, modern .NET frameworks have integrated many of these calls, allowing developers to make managed calls that are safer and easier to work with. In particular, the new API source generator in .NET 7 provides an even more streamlined and user-friendly approach to making these calls.

In this article, we’ll explore how to use the API source generator in .NET 7 to simplify your development process and improve the reliability of your code.

If you are new to making these types of calls, this is a description.

An application programming interface (API) is a set of protocols, routines, and tools for building software applications. APIs are used to specify how software components should interact with each other, providing a way for different software systems to communicate and exchange data.

In simpler terms, an API is a way for one application to talk to another application or system. APIs define how data is exchanged between applications, what actions can be performed, and what responses to expect. They are commonly used in web development, allowing different websites and services to share data and functionality.

Developers can use APIs to simplify the process of building applications by providing pre-built functionality that can be easily integrated into their software. This can save time and resources, as developers don’t have to write code from scratch to accomplish common tasks.

There are many types of APIs, including web APIs, operating system APIs, and database APIs. Each type of API is designed to meet specific needs and provide different types of functionality to developers.

As I mentioned, when I started programming, these calls were to the Windows operating system or other DLLs, usually written with the Assembly Language or C++. Now, they also can refer to HTTP calls across the internet or something similar. You might need to learn how to do this for DLLs you might be using, or you want more features from a Windows API call that .NET did not expose. Actually, .NET uses over 3,000 API calls for features that you use every day! There was a project a while ago to expose ALL Windows API calls into managed wrappers in .NET, but that did not happen.

Making API Calls with .NET

Ever since the beginning of .NET, to make these calls we use the [DLLImport] attribute. Here is an example of how to get system information from the Windows OS.

[DllImport("kernel32.dll")]
public static extern void GetSystemInfo([MarshalAs(UnmanagedType.Struct)] 
                                         ref SYSTEM_INFO lpSystemInfo);

This API call brings back information such as the number of processors the computer is using and more. We will revisit this in the next section. To me, the hardest part of making calls like this to Windows is finding the information, especially about the types being used since that is how you can easily crash an application if done improperly. Even working on this article, I had a hard time finding code that I could use in .NET. Back in the 90s and early 2000s, the bible for these was written by a friend of mine, Dan Appleman in his book “Visual Basic Programmer’s Guide to the Win32 Api”. We all had a copy of his book on our desks!

This works fine, but how can this be easier and potentially more performant? Well, that has been solved by using a new source generator in .NET 7.

LibraryImport P/Invoke Source Generator

Starting with .NET 7, we can now use the [LibraryImport] attribute that invokes a source generator that builds the code for us to safely make API calls. I will show you two Windows API calls that .NET does not fully expose to developers. The first one is to copy a file. Yes, we can copy a file using .NET, but what if you want to get notified of the progress during the call to display it back to the user? We can do this by calling the CopyFileExW method from the Kernel32.dll in Windows. I have been using this call for many years so I can display progress. I will be adding this and the GetSystemInfo call to my OOS package called Spargine, available here: https://bit.ly/Spargine.

Using the [LibraryImport] attribute is just as easy as using the [DLLImport] attribute with a few added benefits. Here is how to use it with the CopyFileExW call.

[LibraryImport("kernel32.dll", EntryPoint = "CopyFileExW", 
  SetLastError = true, StringMarshalling = StringMarshalling.Utf16)]
[return: MarshalAs(UnmanagedType.Bool)]
private static partial bool CopyFileEx(string lpExistingFileName, 
  string lpNewFileName, ProgressCallback lpProgressRoutine, 
  IntPtr lpData, ref int pbCancel, CopyFileMode dwCopyFlags);

The part of this call I am most interested in is the lpProgressRouting parameter. This is where we will use a callback to get the file copy progress. This is the code generated by the generator.

API-SOURCE GENERATOR-GENERATED CODESince these calls should always be private or internal in a DLL, I created a public method to expose it like this.

public static bool CopyFileWithEvents(string oldFile, string newFile,
                                      ProgressCallback progressCallback)
{
     int cancel;
     return CopyFileEx(oldFile, newFile, progressCallback, IntPtr.Zero, 
              ref cancel, CopyFileMode.Restartable |
              CopyFileMode.Restartable);
}

Then in the calling code, I created the callback method that will be used to display the progress to the user.

private CopyProgressResult CopyProgressHandler(long total, 
  long transferred, long streamSize, long StreamByteTrans, 
  uint dwStreamNumber, CopyProgressCallbackReason reason, 
  IntPtr hSourceFile, IntPtr hDestinationFile, IntPtr lpData)
{
  Debug.WriteLine($"Total: {total} Transfered:{transferred} 
    StreamSize:{streamSize} Stream Byte Transfered:{StreamByteTrans} 
    Stream:{dwStreamNumber} Reason:{reason}.");

     return CopyProgressResult.Continue;
}

Now we just need to call CopyFileWithEvents like this.

var result = FileSystemExamples.CopyFileWithEvents(@"c:\temp\test.pptx", 
                       @"d:\temp\test.copy.pptx", 
                       new ProgressCallback(this.CopyProgressHandler));

Here is an example of the progress data that is sent back.

API-SOURCE GENERATOR-FILE COPY OUTPUT EXAMPLE

Now I can display the progress to the user. Not sure why .NET never exposed this and that is why it will be in the next version of Spargine! Now let’s look at using this source generator with the GetSystemInfo call.

[DllImport("kernel32", EntryPoint = "GetSystemInfo", SetLastError =true)]
private extern static void GetSystemInfoDllImport(ref SystemInfo si);

Again, I created a public method to expose this call.

public static SystemInfo GetSystemInformation()
{
     var info = new SystemInfo();
     GetSystemInfo(ref info);

     return info;
}

The data returned from the Kernel32.dll looks like this.

API-SOURCE GENERATOR-SYSTEMINFO OUTPUT EXAMPLE

With this API call we can not only get the number of processors, but who made the processor, processor type, its revision, and more! When I add this to Spargine, I will convert OEM ID and the others to something that is more human-readable.

Performance

Can this source generator improve performance? My answer is maybe. I can’t really benchmark the file copy method, but I did benchmark the GetSystemInfo call. My benchmark test shows that using the source generator is about 1.024 times faster. Not a lot, but in these days of cloud computing, we need to ensure our code runs as fast as possible. As always, just don’t take my word, make sure to benchmark your code!

Summary

Now that you’ve learned how to use the new source generator for API calls in .NET 7, you have access to a powerful set of tools that can help you accomplish even more with your code. If there’s something you need to do that .NET doesn’t already offer, chances are there’s an API call available in the OS or in a third-party DLL that can help.

That said, finding and using these calls can be a challenge. In my own experience, I’ve found the https://pinvoke.net site to be a helpful resource for researching how to use Windows API calls in .NET. While it can still require some additional work to get these calls integrated into your code, the benefits can be well worth it.

If you have any tips or recommendations for useful API calls that could benefit Spargine, please feel free to leave a comment below. I’m always eager to hear from others in the community and learn more about how we can improve our development practices.

Pick up any books by David McCarter by going to Amazon.com: http://bit.ly/RockYourCodeBooks

One-Time
Monthly
Yearly

Make a one-time donation

Make a monthly donation

Make a yearly donation

Choose an amount

$5.00
$15.00
$100.00
$5.00
$15.00
$100.00
$5.00
$15.00
$100.00

Or enter a custom amount

$

Your contribution is appreciated.

Your contribution is appreciated.

Your contribution is appreciated.

DonateDonate monthlyDonate yearly

If you liked this article, please buy David a cup of Coffee by going here: https://www.buymeacoffee.com/dotnetdave

© The information in this article is copywritten and cannot be preproduced in any way without express permission from David McCarter.

One thought on “Source Generators: Making API Calls Easy

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.