Cross-platform C# / C++ interop #19365
Replies: 3 comments 4 replies
-
I think with this approach you have C# <-> C interop, not C++. As you have to use |
Beta Was this translation helpful? Give feedback.
-
Note for above simplified examples:
// C# interop
[LibraryImport(FluoroLibLibraryName)]
public static partial void FreeMemory(IntPtr dataPtr);
// C++
EXPORT void FreeMemory(void* data_ptr)
{
free(data_ptr);
} |
Beta Was this translation helpful? Give feedback.
-
This is not quite accurate. The major difference is a source generator that can generate things ahead of time instead of entirely at runtime. If you look at the code the source generator generates, in many cases it will generate fairly similar code to what you would get if you manually did a https://learn.microsoft.com/en-us/dotnet/standard/native-interop/pinvoke-source-generation
You can also use an explicit layout, it's just more tedious. It does however save you from your code breaking for no obvious reason if you decide to run some formatting tool on your C# code.
The windows API approach to solve this to call a method twice. The first time returns the size, and then you allocate some buffer for the second call. For linked lists it just returns a pointer to the next value, so you just keep reading until you run out of valid pointers. But there is probably better patterns, I am not a C expert. |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
Goal of this show & tell
These insights are not specific to Avalonia. However, Avalonia supports cross-platform UI implementation. Not in every project you have the luxury to only handle .NET dependencies. Some projects require specific implementations in unmanaged code such as C++ and p/invoke those functions. I will describe our experiences and conclusions.
General technique
In the early phases of .NET the option to p/invoke system DLLs or own C++ libraries was
DllImport
. Its cross-platform correspondance isLibraryImport
that allows for fewer automations and needs more manial control especially for non-primitive parameters in the interop functions like strucs.Cross-platform LibraryImport
C-API
Use exported functions with either primitive parameters or pointers to structs
Of course, you will have your C++ objects and their functions. Hence, the implementation of the C-API function must then call into your C++ objects
Structs
Define the structs in the C++ layer.
Function and Type Interop
Define the native function using LibaryImport in the .NET code. And define the structs that match the definition in the C++ code.
Note: in order to match the memory layout of the C++ struct, you need to use
StructLayout
withLayoutKind.Sequential
and specify the packing size.The packing size is an optional parameter that you may not need if you target only one single platform. Typically, on Windows the packing
between C# and the C++ compiler is 8 bytes and matches implicitly. However, on Linux the packing may be different and especially
seems not to automatically match. Therefore, it is safer to specify the packing size explicitly. In our case, specifying the packing
was actually mandotory to make the code work on both Windows and Linux.
Also note, the
in
andout
keywords are necessary to specify call-by-reference semantics for the parameters. A missingin
leads to acall-by-value interpretation. Again here, this can work on Windows but would be interpreted wrong on Linux. Therefore, the struct memory
would be marshalled incorrectly and the C++ code would not be able to read the data correctly.
Setting packing size in C++
In the above definition of the struct in C++, the packing size is not specified. Again, this may work nicely on Windows because MSVC comiler
and C# would use the same packing size. For cross-platform code, however, it is mandatory to define the packing size explicitly. As for some
platforms and compilers 8 bytes would not work, we found 4 bytes to be a safe choice.
Select the right native library to load
Use a
DllImportResolver
to guide the loading of the native library like in below simplified example.This allows you to load the correct library depending on the platform and debug/release mode.
C++ Build System
Obviously, .vcxproj cannot be used in a cross-platform manner. We found that CMake is a good choice to build the C++ library.
It is likely that you need to create platform-specific instructions to build the library correctly. That seems relatively easy
using CMake.
In our case, we added dummy projects to the C# solution that would run the CMake build commands as
pre-build steps. This way, the C++ library is built automatically when the C# project is built.
Beta Was this translation helpful? Give feedback.
All reactions