Cross platform C++ in C# .net Core/5 application

Viewed 3381

With the introduction of .net Core, and more recently, .net 5, cross-platform .net/C# is relatively easy. With this in mind, I'm working towards an application that would use .net 5/C#, but there is some code that I want to keep in the C++/native domain. With C# only, one can even publish to other platforms from the development one. I'm willing to give up on that.

Now, how can one keep the project cross-platform?

I've been looking everywhere an there doesn't seem to be a great option anywhere...

Maybe this solution from Xamarin? I'm not sure it'll translate to Desktop, or that it can be automatized on a CI/CD...

But other than that, everybody seems to get away with their own janky way.

Does anybody have pointers about C(++)/C# integration that's cross platform?

Edit 1:

My problem is not to make the interop per se. I have successfully used SWIG and I know about P/Invoke, C-compatible ABIs and etc.

My problem is build system/deployment. So far I have to copy files around. But how can one have an integrated build system? I want that when building my dotnet app for the C++ libraries to compile, for example.

2 Answers

You can have C++/C# without any problems. But the "bridge" methods between the C++ side and the C# side must accept/return plain C types/structs. You can't pass around std::xxxx full stop. You can pass around char *str, struct { int Foo, int Bar }, int[] and so on (but still there is a little complexity, depending on how it is done).

I maintain a github of examples about marshaling strings and structs from/to C/C++ to C# on .NET Framework/.NET Core. The project is built for Visual Studio, so there are some "microsoftnesses" inside, but the basic ideas can be copied-pasted onto other C platforms easily.

There are some additional complexities when you want to do a fully multiplaform project (I didn't think about crossplatforming when I wrote those examples):

  1. The encoding of strings: in the examples I show that you can use utf-8 strings easily, but it is still a pain, because on Windows the Windows API don't support utf-8 and are tailored for wchar_t (you can't CreateFile with an utf-8 filename). On the opposite side on Linux everyone uses char* and nearly no one uses wchar_t* (to give the same example, filesystems on Linux are agnostic about character sets, so the open primitive and the fopen method can accept char* that can be utf-8 encoded), so deciding how to handle char/wchar_t in a crossplatform way is a pain. If I had to do it, I would probably define the TCHAR macro to be char on Linux, wchar_t on Windows, use it everywhere, and use the LPTStr marshalling option for strings, and the Auto methods for the Marshal class (Marshal.PtrToStringAuto for example). The .NET Core considers LPTStr/xxxAuto to be char* on Linux and wchar_t* on Windows.

  2. While on Windows there is a great deluge of memory allocators (the main ones used by .NET Core in interop are the CoTaskMemAlloc for memory and the SysAllocString for strings, but there are niche (useles) things like GlobalHAlloc), on Linux .NET Core uses directly malloc. This is important for freeing memory allocated "from the other side", and it increases the complexity if you want to be able to let the marshaller free automatically the memory. But in the end it is always a good idea to expose a memory deallocator from the C/C++ side, because before or later you'll have to free memory allocated in the C/C++ side. And while possible I think it is a bad idea to let the marshaller free automagically the memory allocated C/C++ side (because you don't have control on the operation, and you can't easily debug it). So no char* Foo() translated in C# with string Foo(): it is possible to do it, but the .NET marshaller will use CoTaskMemFree to free it. There are other ways to do it, or you can do it manually (IntPtr Foo() and then you convert and free the IntPtr explicitly).

  3. There are some limitations about using BSTR and SAFEARRAY under Linux (the main problem is that there are no reciprocal methods C side), so if you want to program crossplatform it is a bad idea using them (but no one uses them)

There are three aspects:

  1. The .NET/native interop
  2. The deployment and packaging of the native component library
  3. Deployment of the entire application bundle, i.e. the managed and native parts

Obviously you need to maintain versions and builds of your native component for all supported platforms. That is a challenge on its own but not related to using it from .NET. With regard to 1) you will need to provide an API for your native library which is compatible with the standardized PInvoke specification. This might be challenging depending on what your component does and might cause trouble if not done right. If you can, prefer pure managed code only, if you are ready for the challenge, proceed!

Related