4 ms·
Clickbait title isn’t really accurate. You’ve always had this ability by using P/Invokes, but it’s never been easy. It requires a lot of domain knowledge (or co
by logbiscuitswave 6y ago
Clickbait title isn’t really accurate. You’ve always had this ability by using P/Invokes, but it’s never been easy. It requires a lot of domain knowledge (or copy-pasta that may cause other problems) to actually get it to work. It’s also very easy to get wrong and cause big memory leaks in the unmanaged heap.
Over the years I’ve managed to get pretty good at P/Invokes to the point where I don’t bat an eye when I need to do some interop with native code. That doesn’t mean I don’t sometimes run into a particularly finicky API that requires digging deep into stuff like GC pinning, structure packing, fixed pointers, or other challenges that will grind things to a halt while I debug what’s happening. At least I’ve come to the point where I can look at unexpected API behavior and have a good intuition for what I need to do.
It sucks, a lot.
I’m really glad that there’s finally an official and reusable abstraction to make this simpler in the future.
- kerng 6y agoI do remember depending on the site pinvoke.net quite a bit in the past - but yeah this was always possible with C#. It was even what made J++ (Microsoft's Java, before it became C#) unique compared to Sun's version in the 90s, as it allowed this kind of tight Windows integration also if I recall correctly.
- sedatk 6y agoDidn’t JNI allow that kind of integration?
- DaiPlusPlus 6y agoI think it was because MS’ Java’s standard libraries included Java wrappers over Win32 already, so programming beginners in Java can make “real”-looking Windows desktop GUI applications without knowing any C or at least knowing how to read *.h files and translate them to Java.
- josefx 6y agoMicrosoft had RNI which was comparable to JNI and could be used to write native code. It also had J/Direct which could be used to bind existing native code to a Java method declared native with only a comment, the closest to that for standard Java that I know of is JNA and apparently that wasn't around until 2007?
- int_19h 6y agoJNI is a bit different - you have to write code in C or C++ that wraps native APIs you want to call, in a certain very specific way that then makes it possible to call it via methods declared as "native" in Java. P/Invoke does not require such a layer - you simply declare enums, structs, and functions from the native API directly in your C# code, and then you can call them. For those familiar with Python, the Java way is more like writing a Python extension module in C, and the C# way is more like using ctypes (but way faster). Java has some third party libraries that are basically P/Invoke equivalent; JNA is probably the most used one.
- sn_master 6y agoIt was also possible in VB6, planet-source-code was the best source for that..
- buckminster 6y agoYou could even call the win32 api from 16-bit VB3 by going through the wow thunking api. It worked well but the declarations were quite hairy.
- sn_master 6y agoyup, you could even have multi-threads, hide inside other processes and run from there and all the C++ fun stuff..
- sandermvanvliet 6y agoMy approach has always been to create a separate dll (written in C++) that wraps all the complicated stuff and has only a few calling points exposed which were P/Invoked from the C# side. Haven’t had to do much of this but that definitely saved me from doing a lot of clunky stuff in the C# app
- yread 6y agoP/Invoke is not that bad - you basically just copy the c header declaration and adjust a bit. It starts to be interesting strings or large memory where you want to avoid copying it too much. Doing it cross-platform is a bit of pain (basically copy everything and point it to different library - .dll vs .so) it would be nice if there was a better way. I still haven't figured out how to get unicode strings to work cross platform though.
- int_19h 6y agoOne trick is to avoid smart marshaling (for C# strings, arrays etc), and use "unsafe" and raw pointers. This way, all your types are what CLR calls blittable - i.e. their representation is the same in native and managed - and the behavior is straightforward and predictable, as no copying or implicit conversions occur. This is particularly useful for cases when you have something like a struct containing a pointer to a struct containing a string, and when ownership of memory is unclear. Of course, the downside is that you have to do all the marshaling yourself then. E.g. for strings, you use the appropriate System.String constructor to create one from a raw pointer - it has overloads for null-terminated string pointers as well as pointer + length, and you can specify encoding too. Or, to pass the string to native, you pin it using "fixed" to obtain a pointer.
- skeletonjelly 6y agoVery interesting! Do you have a link or quick example you could please share?
- int_19h 6y agoIf you have any specific Win32 API in mind, I could write up a snippet to invoke it in this manner.
- spaetzleesser 6y agoI am pretty good about writing P/Invokes but I have no way of proving that what I did won’t create some memory overwrite or leak. Too many moving parts between C# and C++. Nowadays I find it better to write a layer in managed C++. This seems much safer and in a sense is more straightforward.
- munchbunny 6y agoAgreed, given the choice between P/Invoke and managed C++, managed C++ often has fewer footguns, despite being rooted in C++. You have the compiler checking your usage of the native API, and you have the compiler generating .NET interfaces which your managed code can invoke directly.
- mlazos 6y agoFor me I had a single mixed managed/native DLL which made my life a lot easier when dealing with interop. The documentation for C++/CLI was really good IMO. I wasn’t marshaling particularly complex types though so had pretty simple pinning scenarios.