Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c8959b0cf2 | ||
|
|
16bb42eedd | ||
|
|
d185c5f0c2 | ||
|
|
6f870aea9c |
@@ -1,11 +1,12 @@
|
|||||||
# Integrating Haskell with Swift Mac Apps
|
# Integrating Haskell with Swift Mac Apps
|
||||||
|
|
||||||
To start, create a new Cocoa Application Xcode project
|
To start, create a new Cocoa Application Xcode project
|
||||||
with Swift as the default language:
|
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||

|
with Swift as the default language.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
Then `cd` into the directory with the `.xcodeproj` and create a
|
Then `cd` into the directory with the `.xcodeproj` and create a
|
||||||
new stack project:
|
new stack project:
|
||||||
@@ -195,6 +196,8 @@ phases, and add a new **Copy Files** phase that copies the
|
|||||||
`SwiftHaskell` executable into the app bundle's Executables
|
`SwiftHaskell` executable into the app bundle's Executables
|
||||||
directory:
|
directory:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
Finally, in the SwiftAppLibrary framework target's Build Phases,
|
Finally, in the SwiftAppLibrary framework target's Build Phases,
|
||||||
@@ -226,35 +229,42 @@ executable SwiftHaskell
|
|||||||
executable where the dynamic linker should look for shared
|
executable where the dynamic linker should look for shared
|
||||||
libraries.
|
libraries.
|
||||||
|
|
||||||
## Starting the GUI
|
## Starting Cocoa
|
||||||
|
|
||||||
Because Haskell has control over the program's entry point
|
Because Haskell has control over the program's entry point
|
||||||
(`main`), we'll need to have it call out to Swift to
|
(`main`), we'll need to have it call out to Cocoa to start its
|
||||||
start Cocoa's main thread. Add this top level function to
|
main thread. In `SwiftAppLibrary.h`, declare a new function
|
||||||
`AppDelegate.swift`:
|
named `runNSApplication` and mark it as `FOUNDATION_EXPORT` to
|
||||||
|
indicate that it should be exported from the framework:
|
||||||
```swift
|
|
||||||
@_cdecl("runNSApplication")
|
|
||||||
func runNSApplication() {
|
|
||||||
let app = NSApplication.shared()
|
|
||||||
var topObjects: NSArray = []
|
|
||||||
NSNib.init(nibNamed: "MainMenu", bundle: Bundle(for: AppDelegate.self))!
|
|
||||||
.instantiate(withOwner: app, topLevelObjects: &topObjects)
|
|
||||||
app.run()
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
`@_cdecl` sets the symbol name of the Swift function, overriding
|
|
||||||
normal symbol mangling.
|
|
||||||
|
|
||||||
In `SwiftAppLibrary.h`, add a `FOUNDATION_EXPORT` C prototype
|
|
||||||
for the function, so that its symbol gets marked as public and
|
|
||||||
is exported from the framework:
|
|
||||||
|
|
||||||
```c
|
```c
|
||||||
FOUNDATION_EXPORT void runNSApplication(void);
|
FOUNDATION_EXPORT void runNSApplication(void);
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Implement the function by adding a new Objective-C `.m` file to
|
||||||
|
the framework target containing
|
||||||
|
|
||||||
|
```objective-c
|
||||||
|
#import "SwiftAppLibrary.h"
|
||||||
|
|
||||||
|
@interface AClassInThisFramework : NSObject @end
|
||||||
|
@implementation AClassInThisFramework @end
|
||||||
|
|
||||||
|
void runNSApplication(void) {
|
||||||
|
NSApplication *app = [NSApplication sharedApplication];
|
||||||
|
NSBundle *bundle = [NSBundle bundleForClass:[AClassInThisFramework class]];
|
||||||
|
NSArray *topObjects;
|
||||||
|
[[[NSNib alloc] initWithNibNamed:@"MainMenu" bundle:bundle]
|
||||||
|
instantiateWithOwner:app topLevelObjects:&topObjects];
|
||||||
|
[app run];
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
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
|
In `Main.hs`, import the foreign function and call it from
|
||||||
the end of `main`:
|
the end of `main`:
|
||||||
|
|
||||||
@@ -286,7 +296,7 @@ from `MainMenu.xib`:
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
## Completing Linking Setup
|
## Linking to the Executable
|
||||||
|
|
||||||
Add `$(PROJECT_DIR)/SwiftHaskell/include` to the framework
|
Add `$(PROJECT_DIR)/SwiftHaskell/include` to the framework
|
||||||
target's **Swift Compiler - Search Paths, Import Paths** setting
|
target's **Swift Compiler - Search Paths, Import Paths** setting
|
||||||
@@ -335,6 +345,7 @@ Add the Haskell sources as input files:
|
|||||||
```
|
```
|
||||||
$(PROJECT_DIR)/src/Main.hs
|
$(PROJECT_DIR)/src/Main.hs
|
||||||
$(PROJECT_DIR)/SwiftHaskellLibrary.cabal
|
$(PROJECT_DIR)/SwiftHaskellLibrary.cabal
|
||||||
|
$(PROJECT_DIR)/stack.yaml
|
||||||
```
|
```
|
||||||
|
|
||||||
And the executable as an output file:
|
And the executable as an output file:
|
||||||
|
|||||||
@@ -37,15 +37,6 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@_cdecl("runNSApplication")
|
|
||||||
func runNSApplication() {
|
|
||||||
let app = NSApplication.shared()
|
|
||||||
var topObjects: NSArray = []
|
|
||||||
NSNib.init(nibNamed: "MainMenu", bundle: Bundle(for: AppDelegate.self))!
|
|
||||||
.instantiate(withOwner: app, topLevelObjects: &topObjects)
|
|
||||||
app.run()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Swift to Haskell ByteString Example
|
// Swift to Haskell ByteString Example
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
#import "SwiftAppLibrary.h"
|
||||||
|
|
||||||
|
@interface AClassInThisFramework : NSObject @end
|
||||||
|
@implementation AClassInThisFramework @end
|
||||||
|
|
||||||
|
void runNSApplication(void) {
|
||||||
|
NSApplication *app = [NSApplication sharedApplication];
|
||||||
|
NSBundle *bundle = [NSBundle bundleForClass:[AClassInThisFramework class]];
|
||||||
|
NSArray *topObjects;
|
||||||
|
[[[NSNib alloc] initWithNibNamed:@"MainMenu" bundle:bundle]
|
||||||
|
instantiateWithOwner:app topLevelObjects:&topObjects];
|
||||||
|
[app run];
|
||||||
|
}
|
||||||
@@ -13,6 +13,7 @@
|
|||||||
BFABC4E01E4D781D006036C6 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = BFABC4DF1E4D781D006036C6 /* Assets.xcassets */; };
|
BFABC4E01E4D781D006036C6 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = BFABC4DF1E4D781D006036C6 /* Assets.xcassets */; };
|
||||||
BFABC4ED1E4D78E5006036C6 /* SwiftHaskell in Copy Files */ = {isa = PBXBuildFile; fileRef = BFABC4EA1E4D78D9006036C6 /* SwiftHaskell */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
|
BFABC4ED1E4D78E5006036C6 /* SwiftHaskell in Copy Files */ = {isa = PBXBuildFile; fileRef = BFABC4EA1E4D78D9006036C6 /* SwiftHaskell */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
|
||||||
BFABC4F01E4D7951006036C6 /* SwiftAppLibrary.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = BFABC45B1E4C1DD1006036C6 /* SwiftAppLibrary.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
BFABC4F01E4D7951006036C6 /* SwiftAppLibrary.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = BFABC45B1E4C1DD1006036C6 /* SwiftAppLibrary.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||||
|
BFE4CC421E55553000F232D6 /* SwiftAppLibrary.m in Sources */ = {isa = PBXBuildFile; fileRef = BFE4CC411E55553000F232D6 /* SwiftAppLibrary.m */; };
|
||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
/* Begin PBXContainerItemProxy section */
|
/* Begin PBXContainerItemProxy section */
|
||||||
@@ -60,6 +61,7 @@
|
|||||||
BFABC4DF1E4D781D006036C6 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
BFABC4DF1E4D781D006036C6 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||||
BFABC4E41E4D781D006036C6 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
BFABC4E41E4D781D006036C6 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||||
BFABC4EA1E4D78D9006036C6 /* SwiftHaskell */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.executable"; name = SwiftHaskell; path = build/SwiftHaskell; sourceTree = SOURCE_ROOT; };
|
BFABC4EA1E4D78D9006036C6 /* SwiftHaskell */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.executable"; name = SwiftHaskell; path = build/SwiftHaskell; sourceTree = SOURCE_ROOT; };
|
||||||
|
BFE4CC411E55553000F232D6 /* SwiftAppLibrary.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SwiftAppLibrary.m; sourceTree = "<group>"; };
|
||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
/* Begin PBXFrameworksBuildPhase section */
|
/* Begin PBXFrameworksBuildPhase section */
|
||||||
@@ -95,6 +97,7 @@
|
|||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
BFABC45E1E4C1DD1006036C6 /* SwiftAppLibrary.h */,
|
BFABC45E1E4C1DD1006036C6 /* SwiftAppLibrary.h */,
|
||||||
|
BFE4CC411E55553000F232D6 /* SwiftAppLibrary.m */,
|
||||||
BFABC4CC1E4D627E006036C6 /* AppDelegate.swift */,
|
BFABC4CC1E4D627E006036C6 /* AppDelegate.swift */,
|
||||||
BFABC4D01E4D664D006036C6 /* MainMenu.xib */,
|
BFABC4D01E4D664D006036C6 /* MainMenu.xib */,
|
||||||
BFABC45F1E4C1DD1006036C6 /* Info.plist */,
|
BFABC45F1E4C1DD1006036C6 /* Info.plist */,
|
||||||
@@ -229,9 +232,9 @@
|
|||||||
files = (
|
files = (
|
||||||
);
|
);
|
||||||
inputPaths = (
|
inputPaths = (
|
||||||
"$(PROJECT_DIR)/src/Lib.hs",
|
|
||||||
"$(PROJECT_DIR)/src/Main.hs",
|
"$(PROJECT_DIR)/src/Main.hs",
|
||||||
"$(PROJECT_DIR)/SwiftHaskellLibrary.cabal",
|
"$(PROJECT_DIR)/SwiftHaskellLibrary.cabal",
|
||||||
|
"$(PROJECT_DIR)/stack.yaml",
|
||||||
);
|
);
|
||||||
name = "stack build and link-deps";
|
name = "stack build and link-deps";
|
||||||
outputPaths = (
|
outputPaths = (
|
||||||
@@ -266,6 +269,7 @@
|
|||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
BFABC4CD1E4D627E006036C6 /* AppDelegate.swift in Sources */,
|
BFABC4CD1E4D627E006036C6 /* AppDelegate.swift in Sources */,
|
||||||
|
BFE4CC421E55553000F232D6 /* SwiftAppLibrary.m in Sources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 58 KiB |
|
Before Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 20 KiB |