keep your arms and legs inside the platform
TRANSCRIPT
Keep Your Hands and Feet Inside the Platform
James BordenSoftware Engineer, Couchbase
I AM JAMES BORDENI am on the Internet as @borrrden. I develop Couchbase Lite .NET
WHAT IS ON THE AGENDA◇What is P/Invoke?◇Example I am working on◇Best Practices I have learned
WHAT IS P/INVOKE?Let’s start with that…
Platform Invocation Services, commonly referred to as P/Invoke, is a feature of Common Language Infrastructure implementations, like Microsoft's Common Language Runtime, that enables managed code to call native code.
WELL WHY DO THAT?◇You have a bazillion lines of legacy code
◇You want to share code between languages (as opposed to architectures)
◇You love pointers (just kidding)
ELABORATE ON POINT 2◇At Couchbase, we have three versions of our Lite product (Objective-C, Java, .NET)
◇Historically we have developed features for all of them side-by-side
◇That’s a pretty big waste of time
WELCOME TO THE JUNGLE…I MEAN FORESTEnter into ForestDB and CBForest
FOREST WHAT?◇ForestDB: Couchbase’s own Key-Value storage engine written in C
◇CBForest: A C++ layer adding Couchbase Lite specific functionality to ForestDB (document creation, view indexing & querying, etc)
THIS PRESENTS A PROBLEM
We now have unmanaged code being developed by a separate team that need to be utilized by our product. Initially it was easily done with Objective-C++, but what about Java and .NET?
HOW CAN I DO IT?Let’s start our journey
NO C++ ALLOWED
*C++ has no standard ABI (Application Binary Interface) and so every function signature changes based on which compiler is used to compile it. This makes C++ an insane and counterproductive idea for P/Invoke. Can you read this small text in the back? No? Good :-p
HERE IS AN API CALL IN C/** Opaque handle to an opened database. */typedef struct c4Database C4Database;
enum C4DatabaseFlags : uint32_t { kC4DB_Create = 1, /**< Create the file if it doesn't exist */ kC4DB_ReadOnly = 2, /**< Open file read-only */ kC4DB_AutoCompact = 4 /**< Enable auto-compaction */}
typedef struct { const void *buf; size_t size;} C4Slice;
typedef struct { int32_t domain; int32_t code;} C4Error;
C4Database* c4db_open(C4Slice path, C4DatabaseFlags flags, C4Error *outError);
HERE IS ONE WAY TO DO IT
IntPtr c4db_open(IntPtr path, int flags, out IntPtr outError);
DON’T DO THAT
*You will spend your entire life with bugs related to putting the wrong type into your method. FOR SHAME!
OK, WHAT SHOULD I DO?◇Mock the structure layout in C#
◇Use ‘unsafe’ code blocks◇The code will look almost identical to its C counterpart
WHAT WILL HAPPEN?◇Everything must be copied between managed and unmanaged*
◇The only inherently copyable types are primitives and one dimensional arrays of primitives
*This is also true of passing things to functions in C, and is precisely why pointers exist in the first place.
HERE IS THE PREVIOUS API IN C#/** Opaque handle to an opened database. */public struct C4Database { }
[Flags]public enum C4DatabaseFlags : uint { Create = 1, ReadOnly = 2, AutoCompact = 4}public unsafe struct C4Slice { public UIntPtr size; // Because size_t is not a fixed size public void* buf; // ZOMG, C# pointer!!}public struct C4Error { public int domain; public int code;} C4Error;
public static extern C4Database* c4db_open(C4Slice path, C4DatabaseFlags flags, C4Error *outError);
SOME NOTES◇All types are made up of purely blittable types (pointers are simply 32 or 64-bit integers)
◇The runtime is smart enough to figure out what you want (compare to Java)
WHAT ABOUT SENDING THINGS TO C?◇The garbage collector is non-deterministic and can run at any given time
◇When a garbage collection occurs, all pointers to managed objects are potentially invalidated
PINNING TO THE RESCUEpublic struct C4String : IDisposable { private GCHandle _handle; // Stores the UTF-8 bytes in a pinned location
public C4String(string s) { _handle = new GCHandle(); if(s != null) { var bytes = Encoding.UTF8.GetBytes(s); _handle = GCHandle.Alloc(bytes, GCHandleType.Pinned); } }}
public unsafe void SendBytes(byte[] bytes) { fixed(byte* ptr = bytes) { SendBytesNative(ptr); // ptr guaranteed not to move }}
WHAT ABOUT SENDING THINGS TO C#?
◇C# APIs expect C# managed objects
◇C knows nothing about C#
CATCH THINGS AT THE BOUNDARY
public unsafe string GetString(){ byte *nativeObject = GetNativeObject(); int size = GetNativeSize(); return new string((sbyte*)nativeObject, size, Encoding.UTF8);}
WHERE TO LOOK FOR NATIVE CODE?
◇The DllImport (DLL Import) attribute and extern keyword indicate native functions
◇The runtime searches a standardized search path for a given DLL
DLL? YOU MEAN WINDOWS ONLY?
◇Mono, and by extension Xamarin, provides DllMapping functionality to allow you to specify an alternate native library in place of a .dll filename
EXAMPLE
<configuration> <dllmap dll=”msvcrt.dll" target="libc.dylib" os="osx" /></configuration>
// If the names do not match, you need to manually specify the entry point// This function will look for msvcrt.dll on Windows, and libc.dylib// on OS X[DllImport(“msvcrt.dll”, CallingConvention=CallingConvention.Cdecl, EntryPoint=“memcpy”]public static extern int MemoryCopy(void* dest, void* src, UIntPtr count);
ANY QUESTIONS?
You can find me [email protected]@couchbase.com
CREDITS
Special thanks to all the people who made and released these awesome resources for free:◇ Presentation template by SlidesCarnival◇ Photographs by Unsplash &
Death to the Stock Photo (license)