Introduction
One of the interesting things that C#9 brought is the introduction of Code Generators. When compiling the code, the C# compiler can generate extra code and add it to your project, thus complementing your code. This has a lot of possible uses:
- Add an attribute to your code to generate boilerplate code: you can add an attribute to a private member and the code generator will add the property and the INotifyPropertyChanged to your class.
- Discover the dependencies needed at compile time and wire them in the executable. This can improve execution time, because wiring the dependencies using reflection is very slow. In this case, the dependencies would be already set up.
- Parse files and generate code for them: you could have a json file with data and the compiler would add the classes without the need of creating them manually. Once the file has changed (and thus the class structure), a new class would be created.
One interesting use of this feature was used by the Windows SDK Team for using the Win32 APIs in our C# code. Instead of using the traditional PInvoke (which you must go to http://pinvoke.net to get the signatures and structures, add them as external methods and call them), you can use C#/Win32, developed to simplify the usage of Win32 APIs.
Using C#/Win32
To use C#/Win32, you must have .NET 5.102 installed in your machine and you must be using Visual Studio 16.8 or newer. With these prerequisites, you can create a new console application in the command line with:
dotnet new console -o UseWin32
This line will create a new console project in the UseWin32 folder. Then you must add the CsWin32 NuGet package with:
dotnet add package Microsoft.Windows.CsWin32 --prerelease
Now you are ready to open the project in Visual Studio or Visual Studio Code and use it. This first project will enumerate the files in the current directory. Yes, I know that .NET already has methods to do that, but we can also do it using Win32.
Create a new text file and name it NativeMethods.txt. In this file, add the names of the thre API functions that we need:
FindFirstFile
FindNextFile
FindClose
Now, we are ready to use these methods in our program. In Program.cs, erase everything and add this code:
using System;
using System.Linq;
using Microsoft.Windows.Sdk;
var handle = PInvoke.FindFirstFile("*.*", out var findData);
if (handle.IsInvalid)
return;
We are using another feature of C#9, Top Level Statements. We can see that CsWin32 has generated a new class named PInvoke and has added the FindFirstFile method. If you hover the mouse over it, you will see something like this:
As you can see, it has added the function and also added the documentation for it. If you right-click in the function and select Go to Definition, it will open the generated code:
We can then continue the code to enumerate the files:
using System;
using System.Linq;
using Microsoft.Windows.Sdk;
var handle = PInvoke.FindFirstFile("*.*", out var findData);
if (handle.IsInvalid)
return;
bool result;
do
{
Console.WriteLine(ConvertFileNameToString(findData.cFileName.AsSpan()));
result = PInvoke.FindNextFile(handle, out findData);
} while (result);
PInvoke.FindClose(handle);
string ConvertFileNameToString(Span<ushort> span)
{
return string.Join("", span.ToArray().TakeWhile(i => i != 0).Select(i => (char)i));
}
This code opens the enumeration with FindFirstFile. If the returned handle is invalid, then there are no files in the folder, so the program exits. Then, it will print the file name to the console and continue the enumeration until the last file, when it calls FindClose, to close the handle. The filename is returned as a structure named __ushort_260, that can be converted to a Span
If you use this code, you will see that FindClose(handle) has an error. That’s because the FileClose function receives a parameter of type HANDLE, while the handle variable is of the FileCloseSafeHandle type and both are not compatible (FileCloseSafeHandle has a handle field, but it’s protected and cannot be used). The solution, in this case, is to dispose the handle variable, that will call FindClose. This code shows how this is done:
using var handle = PInvoke.FindFirstFile("*.*", out var findData);
if (handle.IsInvalid)
return;
bool result;
do
{
Console.WriteLine(ConvertFileNameToString(findData.cFileName.AsSpan()));
result = PInvoke.FindNextFile(handle, out findData);
} while (result);
We are using here the C#8’s Using Statement, so we don’t need to use a block. When you run this code, you will see the enumeration of the files in the console:
Function callbacks
As you can see, there is no need to dig to use the Win32 APIs, but there are some APIs that are more complex and use a callback function. These can also be used in the same way. to see that, we can enumerate all resources in an executable. To do that, we load the executable with LoadLibraryEx. Once loaded, we use EnumerateResourceTypes to enumerate all resource types and, for every resource type, we enumerate the resources with EnumerateResourceNames.
Create a new project with
dotnet new console -o EnumerateResources
Then add the CsWin32 NuGet package with
dotnet add package Microsoft.Windows.CsWin32 --prerelease
Then, open the project in Visual Studio and add a new text file and name it NativeMethods.txt. Add these functions in the file:
LoadLibraryEx
FreeLibrary
EnumResourceTypes
EnumResourceNames
In Program.cs, erase all text and add this code:
using System;
using Microsoft.Windows.Sdk;
var hInst = PInvoke.LoadLibraryEx(@"C:\\Windows\\Notepad.exe",
null, LoadLibraryEx_dwFlags.LOAD_LIBRARY_AS_DATAFILE);
try
{
}
finally
{
PInvoke.FreeLibrary(hInst);
}
This code calls LoadLibraryEx to load Notepad. The LOAD_LIBRARY_AS_DATAFILE constant was changed to an enum. This function returns the Instance handle, that will be used to enumerate the resources. At the end, we free the instance using FreeLibrary.
Now, we’ll start to enumerate the resources with:
var hInst = PInvoke.LoadLibraryEx(@"C:\\Windows\\Notepad.exe",
null, LoadLibraryEx_dwFlags.LOAD_LIBRARY_AS_DATAFILE);
try
{
PInvoke.EnumResourceTypes(hInst, EnumerateTypes, 0);
}
finally
{
PInvoke.FreeLibrary(hInst);
}
You can see that the second parameter in EnumerateResourceTypes is a callback function (which I don’t even know the signature 😃). Let’s see if Visual Studio will help us with this. We type Ctrl-. and it will propose us to create the method. We select it and it is created:
BOOL EnumerateTypes(nint hModule, PWSTR lpType, nint lParam)
{
throw new NotImplementedException();
}
We can add our code to enumerate the types:
BOOL EnumerateTypes(nint hModule, PWSTR lpType, nint lParam)
{
Console.WriteLine(PwStrToString(lpType));
PInvoke.EnumResourceNames(hModule, lpType, EnumNames, 0);
return true;
}
This code will write the resource type to the console and enumerate the resources for that type. We are using a function, PwStrToString to convert the resource name (a PWSTR) to a string:
string PwStrToString(PWSTR str)
{
unsafe
{
return ((ulong)str.Value & 0xFFFF0000) == 0 ?
((ulong)str.Value).ToString() :
str.AsSpan().ToString();
}
}
This struct has a Value property, that can be an integer or a string. To know which of them we must use, we test against the high order byte and see if it’s empty. If it is, then the value is an integer and we convert it to a string. If not, we convert it to a Span and get the string from it. All this code must be marked as unsafe, as we are working with the pointers.
EnumerateResourceNames has a callback, which we get the signature in the same way we did before, by using Visual Studio refactoring:
BOOL EnumNames(nint hModule, PCWSTR lpType, PWSTR lpName, nint lParam)
{
Console.WriteLine(" " + PwStrToString(lpName));
return true;
}
Now the program is complete and we can run it to list all notepad’s resources:
As you can see, working with the Win32 API is much easier now, we don’t have to use custom P/Invokes, everything is at one place and all we have to do is to add the functions we want to the NativeMethods file. This is really a great and welcome improvement.
Ah, and if you want to know what those resource type numbers are, you can find them here.
- 4 – Menu
- 5 – Dialog
- 6 – String
- 9 – Accelerator
- 16 – Version
- 24 – Manifest
All the source code for this article is at https://github.com/bsonnino/UseWin32