In SwiftHaskellLibrary.cabal, rename the executable to
match the Xcode app's name of SwiftHaskell:
executableSwiftHaskell
To combine our Haskell library with our Swift UI, we'll build
the Swift app as a framework and link to it from the Haskell
executable. Xcode will then package both up into an app bundle.
The reason for doing the linking in this direction is that
building a self-contained dynamic library is currently simpler
with Swift and Xcode than it is with Cabal.
Exporting Haskell Functions
Here's the trivial function square that we'll export as a
simple first example:
square x = x * x
Haskell functions exported via the FFI can only contain
certain types in their signatures that are compatible with C:
primitive integers, floats and doubles, and pointer types.
The full list is in section 8.7 of the Haskell
Report.
Since we'll only be using square to demonstrate the FFI, let's
assign it a FFI-compatible type directly. For more complex
functions, wrap them in a new function and convert their inputs
and outputs as needed.
importForeign.Csquare::CInt->CInt
square x = x * x
To export square, add a foreign export definition with a
calling convention of ccall:
moduleMainwhereimportForeign.Cforeignexport ccall square ::CInt->CIntsquare::CInt->CInt
square x = x * x
main::IO()
main =doputStrLn"hello world"
Importing Haskell's Generated FFI Headers into Swift
If we now stack build, in addition to building the library,
GHC will generate C header files for each module with foreign
exports. Because these are build artifacts, they're buried
somewhat deep in the file hierarchy, but we can ask stack
and find where they are:
Since we'll be importing these headers into a Swift framework,
we won't be able to use #include as we would in C. Instead,
Swift uses Clang's module format. (Swift
applications can use bridging headers,
but frameworks must use modules.) A
module.modulemap file to import Main_stub.h looks like
As the paths to these headers vary, let's use a script to
automatically copy them out and build a module map. We'll also
create a symlink to the built executable's location for later.
#!/usr/bin/env bashset -eu
# Change EXECUTABLE_NAME to the name of your Haskell executable.
EXECUTABLE_NAME=SwiftHaskell
DIST_DIR="$(stack path --dist-dir)"
GHC_VERSION="$(stack exec -- ghc --numeric-version)"
GHC_LIB_DIR="$(stack path --compiler-bin)/../lib/ghc-$GHC_VERSION"
STUB_BUILD_DIR="${DIST_DIR}/build/${EXECUTABLE_NAME}/${EXECUTABLE_NAME}-tmp"
STUB_MODULE_DIR="${EXECUTABLE_NAME}/include"
STUB_MODULE_MAP="${STUB_MODULE_DIR}/module.modulemap"# Create a module map from the generated Haskell# FFI export headers for importing into Swift.
mkdir -p "${STUB_MODULE_DIR}"
NL=""
module_map="module ${EXECUTABLE_NAME} {${NL}"forhin$(find "${STUB_BUILD_DIR}" -name '*.h');do
h_filename="${h/$STUB_BUILD_DIR\//}"
cp "$h""${STUB_MODULE_DIR}/"
module_map="${module_map} header \"${h_filename}\"${NL}"done
module_map="${module_map} export *${NL}"
module_map="${module_map}}"echo"${module_map}">"${STUB_MODULE_MAP}"# Symlink to the current GHC's header directory so we can add it# to Xcode's include path as $(PROJECT_DIR)/build/ghc/include.
mkdir -p build/ghc
ln -sf "${GHC_LIB_DIR}/include" build/ghc/
# Symlink to the Haskell executable so we can easily drag it# into Xcode to use as the executable for the app bundle.
ln -sf "../${DIST_DIR}/build/${EXECUTABLE_NAME}/${EXECUTABLE_NAME}" build/
Change the value of the EXECUTABLE_NAME variable to the
name of the executable in your .cabal file if you named it
something other than SwiftHaskell.
Save the script as link-deps.sh, run stack build, and then
run bash link-deps.sh to prepare for the next section.
Running the script will create
SwiftHaskell/include/module.modulemap and two symlinks in
the project's build/ directory:
Create a new Cocoa Framework target in the Xcode project,
with Swift as the default language.
Name the framework SwiftAppLibrary.
Framework Configuration
To move over our application code and UI, change the Target
Membership of AppDelegate.swift and MainMenu.xib in Xcode's
File Inspector in the right sidebar so that they are only
included in SwiftAppLibrary:
In AppDelegate.swift, remove the @NSApplicationMain
attribute from the AppDelegate class, as we don't want an
auto-generated main function in our framework. We will
implement an equivalent way to start Cocoa later, to be called
from the Haskell executable's main.
Xcode will place the built framework in a temporary directory
(~/Library/Developer/Xcode/DerivedData/) with an unpredictable
subpath. So that Cabal will be able to find the framework for
linking, add a new Run Script build phase
that creates a symlink to the built framework in build/:
set -u
ln -sf "${BUILT_PRODUCTS_DIR}/${FULL_PRODUCT_NAME}""${PROJECT_DIR}/build/"
App Bundle Configuration
Drag the SwiftHaskell executable we built previously with
Stack into Xcode from the build/ directory that we symlinked
it into,
but do not add it to any targets when prompted:
When Xcode creates Swift frameworks, it expects that the
application that links the framework will include the Swift
standard libraries. Xcode automatically adds these libraries to
Swift applications. Since our application executable is built
with Haskell and not Swift, we'll need to explicitly tell Xcode
to include the Swift standard libraries in our application.
In the app target's Build Settings tab, set Always Embed Swift
Standard Libraries to Yes:
In the app target's Build Phases, remove the Compile Sources
and Link Binary With Libraries phases. We are using Stack to
build the app's executable instead of Xcode.
Add a new Copy Files phase that copies the SwiftHaskell
executable into the app bundle's Executables directory:
Build the SwiftAppLibrary framework in Xcode to prepare for the
next sections.
Linking to the Framework
Add these options to the executable's section in the .cabal
file:
Because Haskell has control over the program's entry point
(main), we'll need to have it call out to Cocoa to start its
main thread. In SwiftAppLibrary.h, declare a new function
named runNSApplication and mark it as FOUNDATION_EXPORT to
indicate that it should be exported from the framework:
FOUNDATION_EXPORT voidrunNSApplication(void);
Implement the function by adding a new Objective-C .m file to
the framework target containing
This is possible to write in Swift, however as of Swift 3.0.2,
the annotation to export unmangled C symbols (@_cdecl) is not
documented as stable. Additionally, whole module optimization
will assume that @_cdecl symbols are unused and remove them.
In Main.hs, import the foreign function and call it from
the end of main:
moduleMainwhereimportForeign.Cforeignexport ccall square ::CInt->CIntsquare::CInt->CInt
square x = x * x
foreignimport ccall "runNSApplication" runNSApplication ::IO()main::IO()
main =doputStrLn"hello world"
runNSApplication
runNSApplication will not return, being busy with Cocoa's
main run loop. Use Control.Concurrent.forkIO before calling
runNSApplication to run other tasks as needed.
Build the SwiftHaskellLibrary framework in Xcode, then stack build, and then finally build and run the SwiftHaskell
app target to launch the app and see the default window from
MainMenu.xib:
Linking to the Executable
To tell Xcode where to find our module.modulemap, add
$(PROJECT_DIR)/SwiftHaskell/include to the framework target's
Swift Compiler - Search Paths, Import Paths setting in
Xcode,
and, for the GHC headers the module depends on, add
$(PROJECT_DIR)/build/ghc/include to the framework's User
Header Search Paths setting:
In order for the framework to be able to link to symbols in the
Haskell executable, we need to tell the linker to leave symbols
undefined and have them be resolved at runtime.
Add -undefined dynamic_lookup to the framework's
Other Linker Flags setting.
Be aware that this means that link errors will occur at runtime
instead of at link time. Also note that the framework linking
to symbols in the executable (and depending on the generated
headers), and the executable linking to the framework, creates
a circular dependency. When building the project clean, you
will need to build the components in this order:
stack build to generate the Haskell FFI export headers.
Linking will fail, as the Swift framework is not built yet.
Build the Swift framework.
stack build
Build the app bundle.
The first step can be skipped subsequently by committing the
generated headers to source control.
To help automate this, add a new Run Script build phase to
the beginning of the framework's build phases with the contents
We're now ready to use exported Haskell functions from Swift.
Import the module we defined in our module.modulemap,
SwiftHaskell, at the top of AppDelegate.swift:
importSwiftHaskell
Let's add a new label to the window for us to write the result
of our Haskell function square into. Open MainMenu.xib and
select the window object in the left sidebar to bring it into
view. Then drag in a label from the object library in the right
sidebar into the window:
Now option-click on AppDelegate.swift in the file list to open
it in an assistant editor. Holding the control key, drag the
label from the window into the AppDelegate class to add and
connect a new @IBOutlet:
Adding the outlet will add a new property to the AppDelegate:
@IBOutletweakvar label: NSTextField!
With the SwiftHaskell module imported and a label connected,
let's call square and display its result in the label. Add
this to applicationDidFinishLaunching:
If the build fails with Use of unresolved identifier 'square',
perform a full clean with the Product » Clean Build Folder...
⌥⇧⌘K menu command and then rebuild. (Hold ⌥ option to reveal
the menu item.) This appears to be a bug with Xcode (version
8.2 as of writing) caching some intermediate state from before
the SwiftHaskell module was fully configured, and should not
occur in future builds.
Passing Complex Data Types
Bytes
[UInt8] to ByteString
Call withUnsafeBufferPointer on a Swift Array to get
an UnsafeBufferPointer, and then read its .baseAddress
property to get an UnsafePointer pass into the exported
Haskell function. The corresponding mutable variants are
withUnsafeMutableBufferPointer, UnsafeMutableBufferPointer,
and UnsafeMutablePointer.
The generated Haskell headers use a single pointer type for all
pointers, HsPtr (void *), which is mutable (not const). If
you know that a function does not mutate through a pointer, you
can use the HsPtr(mutating:) constructor to cast a non-mutable
pointer to a mutable pointer.
importForeign.CimportForeign.PtrimportqualifiedData.ByteStringasBimportData.Wordforeignexport ccall countBytes ::Word8->PtrCChar->CSize->IOCSizecountBytes::Word8->PtrCChar->CSize->IOCSize
countBytes needle haystack haystackLen =do
s <-B.packCStringLen (haystack, fromIntegral haystackLen)
pure (B.foldl (\count b -> count +if b == needle then1else0) 0 s)
With a Swift wrapping function of
funccount(byte: UInt8, inbytes: [UInt8]) ->Int {
var r =0
bytes.withUnsafeBytes { bytesPtr in
r =Int(SwiftHaskell.countBytes(byte, HsPtr(mutating: bytesPtr.baseAddress)))
}
return r
}
ByteString to [UInt8]
To pass a ByteString to an exported Swift function that
accepts a pointer and a length, use useAsCStringLen:
importData.ByteString (ByteString)
importqualifiedData.ByteStringasBforeignimport ccall "someSwiftFunction" someSwiftFunction ::PtrCChar->CSize->IO()passByteString::ByteString->IO()
passByteString s =B.useAsCStringLen s $\(p, n) ->
someSwiftFunction p (fromIntegral n)
To return the contents of a ByteString, call mallocArray to
allocate a new array with C's malloc allocator and copy the
ByteString data into it. The Swift caller is then responsible
for calling free on the pointer. Use Foreign.Storable.poke
to also return the size by writing into a passed pointer.
importData.ByteString (ByteString)
importqualifiedData.ByteStringasBimportqualifiedData.ByteString.UnsafeasBUimportForeign.Storable (poke)
mallocCopyByteString::ByteString->IO (PtrCChar, Int)
mallocCopyByteString s =BU.unsafeUseAsCStringLen s $\(p, n) ->do
a <- mallocArray n
copyArray a p n
pure (a, n)
foreignexport ccall getSequence ::PtrCSize->IO (PtrCChar)
getSequence::PtrCSize->IO (PtrCChar)
getSequence sizePtr =do
(p, n) <- mallocCopyByteString (B.pack [1..10])
poke sizePtr (fromIntegral n)
pure p
The imported getSequence function returns a
UnsafeMutableRawPointer in Swift. To copy the elements into
a Swift array, first assign a type to the memory using the
.assumingMemoryBound(to:) method. Then wrap the pointer
and length in an UnsafeBufferPointer and pass it to the
array constructor, which copies the elements into a ne
请发表评论