C
C#2w ago
voltagex

LibraryImport examples, especially on Linux

Has anyone got examples of usage of the newer LibraryImport infrastructure (?) as opposed to DllImport? I have seen https://stackoverflow.com/questions/75304403/marshalling-function-pointers-with-net-7-libraryimport which just about melted my face off and https://learn.microsoft.com/en-us/dotnet/standard/native-interop/pinvoke-source-generation isn't bad but no one seems to be using this in open source code.
Stack Overflow
Marshalling Function Pointers with .NET 7 LibraryImport
I'm trying to implement some P/Invoke code using the new LibraryImport attribute, as opposed to the old DllImport. Specifically, I am trying to marshal a WNDCLASSEXW struct for use in RegisterClass...
P/Invoke source generation - .NET
Learn about compile-time source generation for platform invokes in .NET.
55 Replies
333fred
333fred2w ago
Better, just ask @jkortech to help you
voltagex
voltagex2w ago
That doesn't seem scaleable.
333fred
333fred2w ago
I agree in general, but it'll probably be fine this time
voltagex
voltagex2w ago
I'm not sure if this is viewable, but https://git.sr.ht/~voltagex/csharp-udev/tree/main/item/Udev.cs is as far as I got before realising I was using a deprecated API The main questions I have are around how to get back to the ".NET world" - if everything is marked unsafe, I'm just writing C badly in C#
tannergooding
tannergooding2w ago
That’s a flawed/incorrect perception C# includes unsafe and always has, it is a fundamental part of the language By calling a p/invoke, you are fundamentally doing something unsafe and realistically it always should have required you to specify it as such
voltagex
voltagex2w ago
that makes sense
tannergooding
tannergooding2w ago
There are many benefits to writing code in c# and having some key unsafe parts, and overall you get more type safety, more portability, and generally better integration with other things that .net provides
voltagex
voltagex2w ago
I guess it's more a design question of how not to have unsafe leak into all of my code
tannergooding
tannergooding2w ago
You use LibraryImport to write your “safe” p/invokes taking things like string, span, T[], out T, etc The source generator then generates the unsafe code behind the scenes to make it all work It already doesn’t leak unless you explicitly make the p/invoke take a pointer type
voltagex
voltagex2w ago
hrm I need to find an "easier" Linux API to try this out on
tannergooding
tannergooding2w ago
It’s really no different than you would’ve written more classic p/invokes. You largely just use LibraryImport instead of DllImport
voltagex
voltagex2w ago
P/Invokes have always done my head in 😅
tannergooding
tannergooding2w ago
There’s a couple additional difference, like the ability to use span, the requirement to enable unsafe blocks, and the need to be explicit about Boolean marshaling
voltagex
voltagex2w ago
but I think you might have given me a good pointer (hah) there.
tannergooding
tannergooding2w ago
But for the most parts it’s a 1-1 swap
voltagex
voltagex2w ago
My LibraryImports took pointers because that's what the native side expects. I guess i need to understand the CustomMarshaller stuff?
tannergooding
tannergooding2w ago
It depends, there’s a number of built in marshallers to handle common scenarios like strings or arrays. If it gets more esoteric you may need a custom one Writing 1-1 bindings against native can be beneficial though. While it requires the caller to be more verbose, it can help improve performance and the overall readability/maintainability of the code, as you can more precisely see what’s actually happening That’s not required in many scenarios though, which is why the safe wrappers the generator creates can often be good as well It really depends on the needs and goals of your application/library and how you want to maintain your code long term
voltagex
voltagex2w ago
What do you mean by 1-1 bindings here?
tannergooding
tannergooding2w ago
Meaning a signature that is as close to what native defines as possible, generally these are fully blittable (require no marshaling), use pointers, etc
voltagex
voltagex2w ago
I went down the rabbit hole of matching an API exactly and got tangled up implementing all sorts of internal data structures. Using opaque pointers (?) got me a lot further a lot faster I wonder what I need to go learn for all this to click together in my brain.
Zombie
Zombie2w ago
Spend a few months in #allow-unsafe-blocks and the unsafe mental parasite will take over soon enough
tannergooding
tannergooding2w ago
People often use tooling to make it simpler, things like ClangSharp will take a c header and produce blittable bindings directly
Zombie
Zombie2w ago
ClangSharp is also a great learning tool if you want to see how stuff translates
voltagex
voltagex2w ago
TIL ClangSharp thank you.
Zombie
Zombie2w ago
ClangSharpPInvokeGenerator, specifically
voltagex
voltagex2w ago
rolls right off the tongue
tannergooding
tannergooding2w ago
It’s not necessarily the easiest tool to use, as it is essentially a wrapper over clang and therefore requires many options to be passed in, much as actual clang does But it’s used by a large number of projects, including at Microsoft, to maintain bindings for various languages
voltagex
voltagex2w ago
would you go from that generated code back to LibraryImport though?
tannergooding
tannergooding2w ago
No, ClangSharp and LibraryImport are basically parallel tools. They do similar things, but with overall different intents/approaches LibraryImport works great when you need a small number of C# friendly bindings that take advantage of marshaling
Zombie
Zombie2w ago
Yeah if you're using one, you typically won't be using the other for the same imports But you can definitely use ClangSharpPInvokeGenerator to save time if you have an import you want to do manually/with LibraryImport and don't want to spend a lot of time writing out the interop structures
tannergooding
tannergooding2w ago
ClangSharp tends to be used to bind against entire libraries or sdks, providing everything and as close to “#include <header.h>” as you can get in dotnet It’s often used for high performance scenarios like graphics bindings for games, rather than one off helpers for lob apps or tools
voltagex
voltagex2w ago
damn, I don't think Debian has a new enough version of clang stuck trying to get libclangsharp.so I did not expect to be compiling Clang today.
Zombie
Zombie2w ago
I thought there were already Linux binaries available for ClangSharpPInvokeGenerator + ClangSharp + the associated Clang version on NuGet
voltagex
voltagex2w ago
@Zombie I wasn't able to work out how to tell dotnet/nuget to give me the file while not in a csproj, I also couldn't find the native binaries in whatever clangsharp nupkg. I am most definitely missing something here.
Zombie
Zombie2w ago
dotnet tool install --global ClangSharpPInvokeGenerator iirc then just ClangSharpPInvokeGenerator is usable in the terminal
voltagex
voltagex2w ago
yep, that was throwing errors about not neing able to find various .so files
Zombie
Zombie2w ago
Weird
voltagex
voltagex2w ago
*****IMPORTANT*****
Failed to resolve libClang.
If you are running as a dotnet tool, you may need to manually copy the appropriate DLLs from NuGet due to limitations in the dotnet tool support. Please see https://github.com/dotnet/clangsharp for more details.
*****IMPORTANT*****

Unhandled exception: System.DllNotFoundException: Unable to load shared library 'libclang' or one of its dependencies. In order to help diagnose loading problems, consider using a tool like strace. If you're using glibc, consider setting the LD_DEBUG environment variable:
/home/voltagex/.dotnet/tools/.store/clangsharppinvokegenerator/18.1.0/clangsharppinvokegenerator/18.1.0/tools/net8.0/any/libclang.so: cannot open shared object file: No such file or directory
/
*****IMPORTANT*****
Failed to resolve libClang.
If you are running as a dotnet tool, you may need to manually copy the appropriate DLLs from NuGet due to limitations in the dotnet tool support. Please see https://github.com/dotnet/clangsharp for more details.
*****IMPORTANT*****

Unhandled exception: System.DllNotFoundException: Unable to load shared library 'libclang' or one of its dependencies. In order to help diagnose loading problems, consider using a tool like strace. If you're using glibc, consider setting the LD_DEBUG environment variable:
/home/voltagex/.dotnet/tools/.store/clangsharppinvokegenerator/18.1.0/clangsharppinvokegenerator/18.1.0/tools/net8.0/any/libclang.so: cannot open shared object file: No such file or directory
/
voltagex
voltagex2w ago
but I am not sure what it's asking me to read on https://github.com/dotnet/clangsharp
GitHub
GitHub - dotnet/ClangSharp: Clang bindings for .NET written in C#
Clang bindings for .NET written in C#. Contribute to dotnet/ClangSharp development by creating an account on GitHub.
Zombie
Zombie2w ago
I assume this part
No description
voltagex
voltagex2w ago
bah, we're way off topic now and I'm missing simple stuff. WIthout setting up a csproj somewhere I can't see how to pull down the correct package
Zombie
Zombie2w ago
Shouldn't need a csproj since ClangSharpPInvokeGenerator is a tool rather than something you import into your project i.e. it is run externally
voltagex
voltagex2w ago
yes, but I need libclang/libClangSharp which are nupkg files. I can't see past the meta-package at a glance.
Zombie
Zombie2w ago
Ah Might have to wait for Tanner to be around again I suppose
voltagex
voltagex2w ago
right! thanks @reflectronic
reflectronic
reflectronic2w ago
this has libclang.so
voltagex
voltagex2w ago
46MB, that seems more like it
reflectronic
reflectronic2w ago
and this one has libclangsharp.so the metapackage is set up in a strange way so that referencing it does not download the entire world to your hard drive
voltagex
voltagex2w ago
this maybe should be a bit clearer in the instructions on the repo @Tanner Gooding Unhandled exception: System.UnauthorizedAccessException: Access to the path '/home/voltagex/src/docker/test-pinvoke' is denied. (it's not in docker now, but I am getting the error no matter where I run ClangSharpPInvokeGenerator) ah, that's meant to be a filename, not a directory Well it was worth a shot, but I am beginning to think slogging through LibraryImport would be easier.
$ ClangSharpPInvokeGenerator --include-directory /usr/src/linux-headers-6.10.0-rc2/include/linux/ --include-directory /usr/src/linux-headers-6.10.0-rc2/include/ -f ~/src/systemd/src/systemd/sd-device.h --namespace SdDevice --output $(pwd)/
test.cs
$ ClangSharpPInvokeGenerator --include-directory /usr/src/linux-headers-6.10.0-rc2/include/linux/ --include-directory /usr/src/linux-headers-6.10.0-rc2/include/ -f ~/src/systemd/src/systemd/sd-device.h --namespace SdDevice --output $(pwd)/
test.cs
message continues, there is no way in hell I am subscribing to Discord Nitro to be able to communicate.
Diagnostics for '/home/voltagex/src/systemd/src/systemd/sd-device.h':
/usr/include/x86_64-linux-gnu/asm/stat.h:83:8: error: redefinition of 'stat'
/usr/include/x86_64-linux-gnu/asm/stat.h:132:16: error: expected ';' at end of declaration list
/usr/include/x86_64-linux-gnu/asm/stat.h:133:16: error: expected ';' at end of declaration list
/usr/include/x86_64-linux-gnu/asm/stat.h:134:16: error: expected ';' at end of declaration list
/usr/src/linux-headers-6.10.0-rc2/include/linux/stddef.h:11:2: error: expected identifier
/usr/src/linux-headers-6.10.0-rc2/include/linux/stddef.h:12:2: error: expected identifier
/usr/src/linux-headers-6.10.0-rc2/include/linux/types.h:21:25: error: typedef redefinition with different types ('__kernel_dev_t' (aka 'unsigned int') vs '__dev_t' (aka 'unsigned long'))
/usr/src/linux-headers-6.10.0-rc2/include/linux/types.h:25:15: error: typedef redefinition with different types ('u32' (aka 'unsigned int') vs '__nlink_t' (aka 'unsigned long'))
/usr/src/linux-headers-6.10.0-rc2/include/linux/types.h:35:9: error: unknown type name '_Bool'
/usr/src/linux-headers-6.10.0-rc2/include/linux/types.h:35:17: error: redeclaration of C++ built-in type 'bool'
/usr/src/linux-headers-6.10.0-rc2/include/linux/types.h:113:15: error: typedef redefinition with different types ('u64' (aka 'unsigned long long') vs '__uint64_t' (aka 'unsigned long'))
/usr/src/linux-headers-6.10.0-rc2/include/linux/types.h:115:15: error: typedef redefinition with different types ('s64' (aka 'long long') vs '__int64_t' (aka 'long'))
/usr/src/linux-headers-6.10.0-rc2/include/linux/types.h:135:13: error: typedef redefinition with different types ('u64' (aka 'unsigned long long') vs '__blkcnt_t' (aka 'long'))
/usr/src/linux-headers-6.10.0-rc2/include/linux/cache.h:6:10: fatal error: 'asm/cache.h' file not found
Skipping '/home/voltagex/src/systemd/src/systemd/sd-device.h' due to one or more errors listed above.
Diagnostics for '/home/voltagex/src/systemd/src/systemd/sd-device.h':
/usr/include/x86_64-linux-gnu/asm/stat.h:83:8: error: redefinition of 'stat'
/usr/include/x86_64-linux-gnu/asm/stat.h:132:16: error: expected ';' at end of declaration list
/usr/include/x86_64-linux-gnu/asm/stat.h:133:16: error: expected ';' at end of declaration list
/usr/include/x86_64-linux-gnu/asm/stat.h:134:16: error: expected ';' at end of declaration list
/usr/src/linux-headers-6.10.0-rc2/include/linux/stddef.h:11:2: error: expected identifier
/usr/src/linux-headers-6.10.0-rc2/include/linux/stddef.h:12:2: error: expected identifier
/usr/src/linux-headers-6.10.0-rc2/include/linux/types.h:21:25: error: typedef redefinition with different types ('__kernel_dev_t' (aka 'unsigned int') vs '__dev_t' (aka 'unsigned long'))
/usr/src/linux-headers-6.10.0-rc2/include/linux/types.h:25:15: error: typedef redefinition with different types ('u32' (aka 'unsigned int') vs '__nlink_t' (aka 'unsigned long'))
/usr/src/linux-headers-6.10.0-rc2/include/linux/types.h:35:9: error: unknown type name '_Bool'
/usr/src/linux-headers-6.10.0-rc2/include/linux/types.h:35:17: error: redeclaration of C++ built-in type 'bool'
/usr/src/linux-headers-6.10.0-rc2/include/linux/types.h:113:15: error: typedef redefinition with different types ('u64' (aka 'unsigned long long') vs '__uint64_t' (aka 'unsigned long'))
/usr/src/linux-headers-6.10.0-rc2/include/linux/types.h:115:15: error: typedef redefinition with different types ('s64' (aka 'long long') vs '__int64_t' (aka 'long'))
/usr/src/linux-headers-6.10.0-rc2/include/linux/types.h:135:13: error: typedef redefinition with different types ('u64' (aka 'unsigned long long') vs '__blkcnt_t' (aka 'long'))
/usr/src/linux-headers-6.10.0-rc2/include/linux/cache.h:6:10: fatal error: 'asm/cache.h' file not found
Skipping '/home/voltagex/src/systemd/src/systemd/sd-device.h' due to one or more errors listed above.
Zombie
Zombie2w ago
If you want some parameter examples you can check out Tanner's TerraFX repos or my own https://github.com/NewBloodInteractive/NewBlood.Interop.Steamworks Look in the .rsp files in the generation folder The main thing to note is that there is both --file and --traverse. The former means "parse this file", while the latter means "stuff in this file should have bindings generated for it" I guess the errors you have there are a result of the parameters passed to Clang, which you can control via --additional in ClangSharpPInvokeGenerator (settings.rsp in the aforementioned repos)
voltagex
voltagex2w ago
this is very much appreciated, although I am definitely at information overload now, and will go play some video games instead.
Zombie
Zombie2w ago
hahaha, understandable
voltagex
voltagex2w ago
This feels like going around the long way to learning C. I'm still musing about what the "easiest" API to generate bindings against would be. I guess there's always SWIG