You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Let’s create a Rust object that will tell us how many people live in each USA ZIP code. We want to be able to use this logic in other languages, but we only need to pass simple primitives like integers or strings across the FFI boundary. The object will have both mutable and immutable methods. Because we can not look inside the object, this is often referred to as an opaque object or an opaque pointer.
The struct is defined in a normal way for Rust. One extern function is created for each function of the object. C has no built-in namespacing concept, so it is normal to prefix each function with a package name and/or a type name. For this example, we use zip_code_database. Following normal C conventions, a pointer to the object is always provided as the first argument.
To create a new instance of object, we box the result of the object’s constructor. This places the struct onto the heap where it will have a stable memory address. This address is converted into a raw pointer using Box::into_raw.
This pointer points at memory allocated by Rust; memory allocated by Rust must be deallocated by Rust. We use Box::from_raw to convert the pointer back into a Box<ZipCodeDatabase> when the object is to be freed. Unlike other functions, we do allow NULL to be passed, but simply do nothing in that case. This is a nicety for client programmers.
To create a reference from a raw pointer, you can use the terse syntax &*, which indicates that the pointer should be dereferenced and then re-referenced. Creating a mutable reference is similar, but uses &mut *. Like other pointers, you must ensure that the pointer is not NULL.
Note that a *const T can be freely converted to and from a *mut T and that nothing prevents the client code from never calling the deallocation function, or from calling it more than once. Memory management and safety guarantees are completely in the hands of the programmer.
To wrap the raw functions, we create a small class inheriting from AutoPointer. AutoPointer will ensure that the underlying resource is freed when the object is freed. To do this, the user must define the self.release method.
Unfortunately, because we inherit from AutoPointer, we cannot redefine the initializer. To better group concepts together, we bind the FFI methods in a nested module. We provide shorter names for the bound methods, which enables the client to just call ZipCodeDatabase::Binding.new.
We create an empty structure to represent our type. This will only be used in conjunction with the POINTER method, which creates a new type as a pointer to an existing one.
To ensure that memory is properly cleaned up, we use a context manager. This is tied to our class through the __enter__ and __exit__ methods. We use the with statement to start a new context. When the context is over, the __exit__ method will be automatically called, preventing the memory leak.
Haskell
{-# LANGUAGE ForeignFunctionInterface #-}
import Data.Word (Word32)
import Foreign.Ptr
import Foreign.ForeignPtr
import Foreign.C.String (CString(..), newCString)
data ZipCodeDatabase
foreign import ccall unsafe "zip_code_database_new"
zip_code_database_new :: IO (Ptr ZipCodeDatabase)
foreign import ccall unsafe "&zip_code_database_free"
zip_code_database_free :: FunPtr (Ptr ZipCodeDatabase -> IO ())
foreign import ccall unsafe "zip_code_database_populate"
zip_code_database_populate :: Ptr ZipCodeDatabase -> IO ()
foreign import ccall unsafe "zip_code_database_population_of"
zip_code_database_population_of :: Ptr ZipCodeDatabase -> CString -> Word32
createDatabase :: IO (Maybe (ForeignPtr ZipCodeDatabase))
createDatabase = do
ptr <- zip_code_database_new
if ptr /= nullPtr
then do
foreignPtr <- newForeignPtr zip_code_database_free ptr
return $ Just foreignPtr
else
return Nothing
populate = zip_code_database_populate
populationOf :: Ptr ZipCodeDatabase -> String -> IO (Word32)
populationOf db zip = do
zip_str <- newCString zip
return $ zip_code_database_population_of db zip_str
main :: IO ()
main = do
db <- createDatabase
case db of
Nothing -> putStrLn "Unable to create database"
Just ptr -> withForeignPtr ptr $ \database -> do
populate database
pop1 <- populationOf database "90210"
pop2 <- populationOf database "20500"
print (pop1 - pop2)
We start by defining an empty type to refer to the opaque object. When defining the imported functions, we use the Ptr type constructor with this new type as the type of the pointer returned from Rust. We also use IO as allocating, freeing, and populating the object are all functions with side-effects.
As allocation can theoretically fail, we check for NULL and return a Maybe from the constructor. This is likely overkill, as Rust currently aborts the process when the allocator fails.
To ensure that the allocated memory is automatically freed, we use the ForeignPtr type. This takes a raw Ptr and a function to call when the wrapped pointer is deallocated.
When using the wrapped pointer, withForeignPtr is used to unwrap it before passing it back to the FFI functions.
When importing the functions, we simply declare that a pointer type is returned or accepted.
To make accessing the functions cleaner, we create a simple class that maintains the pointer for us and abstracts passing it to the lower-level functions. This also gives us as opportunity to rename the functions with idiomatic JavaScript camel-case.
To ensure that the resources are cleaned up, we use a try block and call the deallocation method in the finally block.
C#
using System;
using System.Runtime.InteropServices;
internal class Native
{
[DllImport("objects")]
internal static extern ZipCodeDatabaseHandle zip_code_database_new();
[DllImport("objects")]
internal static extern void zip_code_database_free(IntPtr db);
[DllImport("objects")]
internal static extern void zip_code_database_populate(ZipCodeDatabaseHandle db);
[DllImport("objects")]
internal static extern uint zip_code_database_population_of(ZipCodeDatabaseHandle db, string zip);
}
internal class ZipCodeDatabaseHandle : SafeHandle
{
public ZipCodeDatabaseHandle() : base(IntPtr.Zero, true) {}
public override bool IsInvalid
{
get { return this.handle == IntPtr.Zero; }
}
protected override bool ReleaseHandle()
{
if (!this.IsInvalid)
{
Native.zip_code_database_free(handle);
}
return true;
}
}
public class ZipCodeDatabase : IDisposable
{
private ZipCodeDatabaseHandle db;
public ZipCodeDatabase()
{
db = Native.zip_code_database_new();
}
public void Populate()
{
Native.zip_code_database_populate(db);
}
public uint PopulationOf(string zip)
{
return Native.zip_code_database_population_of(db, zip);
}
public void Dispose()
{
db.Dispose();
}
static public void Main()
{
var db = new ZipCodeDatabase();
db.Populate();
var pop1 = db.PopulationOf("90210");
var pop2 = db.PopulationOf("20500");
Console.WriteLine("{0}", pop1 - pop2);
}
}
As the responsibilities for calling the native functions are going to be more spread out, we create a Native class to hold all the definitions.
To ensure that the allocated memory is automatically freed, we create a subclass of the SafeHandle class. This requires implementing IsInvalid and ReleaseHandle. Since our Rust function accepts freeing a NULL pointer, we can say that every pointer is valid.
We can use our safe wrapper ZipCodeDatabaseHandle as the type of the FFI functions except for the free function. The actual pointer will be automatically marshalled to and from the wrapper.
We also allow the ZipCodeDatabase to participate in the IDisposable protocol, forwarding to the safe wrapper.
Julia
using Libdl
libname = "objects"
if !Sys.iswindows()
libname = "lib$(libname)"
end
lib = Libdl.dlopen(libname)
zipcodedatabase_new_sym = Libdl.dlsym(lib, :zip_code_database_new)
zipcodedatabase_free_sym = Libdl.dlsym(lib, :zip_code_database_free)
zipcodedatabase_populate_sym = Libdl.dlsym(lib, :zip_code_database_populate)
zipcodedatabase_populationof_sym = Libdl.dlsym(lib, :zip_code_database_population_of)
struct ZipCodeDatabase
handle::Ptr{Nothing}
function ZipCodeDatabase()
handle = ccall(zipcodedatabase_new_sym, Ptr{Cvoid}, ())
new(handle)
end
function ZipCodeDatabase(f::Function)
database = ZipCodeDatabase()
out = f(database)
close(database)
out
end
end
close(database:: ZipCodeDatabase) = ccall(
zipcodedatabase_free_sym,
Cvoid, (Ptr{Cvoid},),
database.handle
)
populate!(database:: ZipCodeDatabase) = ccall(
zipcodedatabase_populate_sym,
Cvoid, (Ptr{Cvoid},),
database.handle
)
populationof(database:: ZipCodeDatabase, zipcode:: AbstractString) = ccall(
zipcodedatabase_populationof_sym,
UInt32, (Ptr{Cvoid}, Cstring),
database.handle, zipcode
)
ZipCodeDatabase() do database
populate!(database)
pop1 = populationof(database, "90210")
pop2 = populationof(database, "20500")
println(pop1 - pop2)
end
As in other languages, we hide a handler pointer behind a new data type. The method which populates the database is called populate! to follow the Julia convention of having the ! suffix on methods which modify the value.
There is currently no consensus on how Julia should handle native resources. While the inner constructor pattern for allocating the ZipCodeDatabase is suitable here, we can think of many ways to let Julia free it afterwards. In this example, we show two means of freeing the object: (1) a mapping constructor for use with the do syntax, and (2) a close overload for manually freeing the object.
The inner constructor ZipCodeDatabase(f), is both in charge of creating and freeing the object. With the do syntax, the user code becomes similar to one using Python’s with syntax. Alternatively, the programmer can use the other constructor and call the method close when it is no longer needed.
The text was updated successfully, but these errors were encountered:
Let’s create a Rust object that will tell us how many people live in each USA ZIP code. We want to be able to use this logic in other languages, but we only need to pass simple primitives like integers or strings across the FFI boundary. The object will have both mutable and immutable methods. Because we can not look inside the object, this is often referred to as an opaque object or an opaque pointer.
The
struct
is defined in a normal way for Rust. Oneextern
function is created for each function of the object. C has no built-in namespacing concept, so it is normal to prefix each function with a package name and/or a type name. For this example, we usezip_code_database
. Following normal C conventions, a pointer to the object is always provided as the first argument.To create a new instance of object, we box the result of the object’s constructor. This places the struct onto the heap where it will have a stable memory address. This address is converted into a raw pointer using
Box::into_raw
.This pointer points at memory allocated by Rust; memory allocated by Rust must be deallocated by Rust. We use
Box::from_raw
to convert the pointer back into aBox<ZipCodeDatabase>
when the object is to be freed. Unlike other functions, we do allowNULL
to be passed, but simply do nothing in that case. This is a nicety for client programmers.To create a reference from a raw pointer, you can use the terse syntax
&*
, which indicates that the pointer should be dereferenced and then re-referenced. Creating a mutable reference is similar, but uses&mut *
. Like other pointers, you must ensure that the pointer is notNULL
.Note that a
*const T
can be freely converted to and from a*mut T
and that nothing prevents the client code from never calling the deallocation function, or from calling it more than once. Memory management and safety guarantees are completely in the hands of the programmer.C
A dummy struct is created to provide a small amount of type-safety.
The
const
modifier is used on functions where appropriate, even though const-correctness is much more fluid in C than in Rust.Ruby
To wrap the raw functions, we create a small class inheriting from
AutoPointer
.AutoPointer
will ensure that the underlying resource is freed when the object is freed. To do this, the user must define theself.release
method.Unfortunately, because we inherit from
AutoPointer
, we cannot redefine the initializer. To better group concepts together, we bind the FFI methods in a nested module. We provide shorter names for the bound methods, which enables the client to just callZipCodeDatabase::Binding.new
.Python
We create an empty structure to represent our type. This will only be used in conjunction with the
POINTER
method, which creates a new type as a pointer to an existing one.To ensure that memory is properly cleaned up, we use a context manager. This is tied to our class through the
__enter__
and__exit__
methods. We use thewith
statement to start a new context. When the context is over, the__exit__
method will be automatically called, preventing the memory leak.Haskell
We start by defining an empty type to refer to the opaque object. When defining the imported functions, we use the
Ptr
type constructor with this new type as the type of the pointer returned from Rust. We also useIO
as allocating, freeing, and populating the object are all functions with side-effects.As allocation can theoretically fail, we check for
NULL
and return aMaybe
from the constructor. This is likely overkill, as Rust currently aborts the process when the allocator fails.To ensure that the allocated memory is automatically freed, we use the
ForeignPtr
type. This takes a rawPtr
and a function to call when the wrapped pointer is deallocated.When using the wrapped pointer,
withForeignPtr
is used to unwrap it before passing it back to the FFI functions.Node.js
When importing the functions, we simply declare that a
pointer
type is returned or accepted.To make accessing the functions cleaner, we create a simple class that maintains the pointer for us and abstracts passing it to the lower-level functions. This also gives us as opportunity to rename the functions with idiomatic JavaScript camel-case.
To ensure that the resources are cleaned up, we use a
try
block and call the deallocation method in thefinally
block.C#
As the responsibilities for calling the native functions are going to be more spread out, we create a
Native
class to hold all the definitions.To ensure that the allocated memory is automatically freed, we create a subclass of the
SafeHandle
class. This requires implementingIsInvalid
andReleaseHandle
. Since our Rust function accepts freeing aNULL
pointer, we can say that every pointer is valid.We can use our safe wrapper
ZipCodeDatabaseHandle
as the type of the FFI functions except for the free function. The actual pointer will be automatically marshalled to and from the wrapper.We also allow the
ZipCodeDatabase
to participate in theIDisposable
protocol, forwarding to the safe wrapper.Julia
As in other languages, we hide a handler pointer behind a new data type. The method which populates the database is called
populate!
to follow the Julia convention of having the!
suffix on methods which modify the value.There is currently no consensus on how Julia should handle native resources. While the inner constructor pattern for allocating the
ZipCodeDatabase
is suitable here, we can think of many ways to let Julia free it afterwards. In this example, we show two means of freeing the object: (1) a mapping constructor for use with thedo
syntax, and (2) aclose
overload for manually freeing the object.The inner constructor
ZipCodeDatabase(f)
, is both in charge of creating and freeing the object. With thedo
syntax, the user code becomes similar to one using Python’swith
syntax. Alternatively, the programmer can use the other constructor and call the methodclose
when it is no longer needed.The text was updated successfully, but these errors were encountered: