|
|
@ -5,7 +5,7 @@ events in AppKit. This project demonstrates the following useful techniques:
|
|
|
|
|
|
|
|
|
|
|
|
- how to call C code from Go with cgo
|
|
|
|
- how to call C code from Go with cgo
|
|
|
|
- how to link Apple frameworks into a cgo project
|
|
|
|
- how to link Apple frameworks into a cgo project
|
|
|
|
- how to call Go code from C
|
|
|
|
- how to call Go code from C with cgo
|
|
|
|
- how to integrate the callback-based concurrency model of AppKit into Go's CSP-style concurrency model
|
|
|
|
- how to integrate the callback-based concurrency model of AppKit into Go's CSP-style concurrency model
|
|
|
|
|
|
|
|
|
|
|
|
The Go program directly links against the
|
|
|
|
The Go program directly links against the
|
|
|
@ -38,7 +38,7 @@ Accessing cgo requires importing the pseudo-package `C`. It's important to
|
|
|
|
understand that there is no literal `C` package in the Go standard library.
|
|
|
|
understand that there is no literal `C` package in the Go standard library.
|
|
|
|
Every project that uses cgo generates _its own_ `C` package transparently.
|
|
|
|
Every project that uses cgo generates _its own_ `C` package transparently.
|
|
|
|
|
|
|
|
|
|
|
|
When invoking `import "C"`, the commend that _immediately_ precedes the import
|
|
|
|
When invoking `import "C"`, the comment that _immediately_ precedes the import
|
|
|
|
directive contains a set of instructions to feed to cgo, as follows:
|
|
|
|
directive contains a set of instructions to feed to cgo, as follows:
|
|
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
```go
|
|
|
@ -50,15 +50,15 @@ directive contains a set of instructions to feed to cgo, as follows:
|
|
|
|
import "C"
|
|
|
|
import "C"
|
|
|
|
```
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
Any lines starting with `#cgo` indicate compiler directives. These are passed
|
|
|
|
Any lines starting with `#cgo` indicate cgo directives. These are passed to the
|
|
|
|
to the cgo tool and are used to invoke the necessary compiler and linker. We
|
|
|
|
cgo tool and are used to invoke the necessary compiler and linker. We use these
|
|
|
|
use this flags to indicate that we want to invoke the Objective-C compiler and
|
|
|
|
flags to indicate that we want to invoke the Objective-C compiler and link
|
|
|
|
link agains the AppKit framework.
|
|
|
|
agains the AppKit framework.
|
|
|
|
|
|
|
|
|
|
|
|
The other lines in this comment, that is, the lines that do _not_ begin with
|
|
|
|
The other lines in this comment, that is, the lines that do _not_ begin with
|
|
|
|
`#cgo` are passed to the C compiler as if thy were in a C header file. This is
|
|
|
|
`#cgo`, are passed to the C compiler as if thy were in a C header file. For our
|
|
|
|
where we `#include "procmon.h"`, the C header file for the C code that we want
|
|
|
|
project, that is just one line: the line that includes `procmon.h`, the header
|
|
|
|
to invoke.
|
|
|
|
file for the C code that we want to access.
|
|
|
|
|
|
|
|
|
|
|
|
Down in the Go program's `main` function, we spawn a goroutine to listen on a
|
|
|
|
Down in the Go program's `main` function, we spawn a goroutine to listen on a
|
|
|
|
channel for changes:
|
|
|
|
channel for changes:
|
|
|
@ -67,7 +67,7 @@ channel for changes:
|
|
|
|
go reportChanges()
|
|
|
|
go reportChanges()
|
|
|
|
```
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
the `reportChanges` function simply reads values off of a channel and prints
|
|
|
|
The `reportChanges` function simply reads values off of a channel and prints
|
|
|
|
them:
|
|
|
|
them:
|
|
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
```go
|
|
|
@ -83,7 +83,8 @@ func reportChanges() {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
```
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
Back in `main`, we invoke the C function defined in our C header file:
|
|
|
|
We then invoke the C function `MonitorProcesses`, which we declared in our C
|
|
|
|
|
|
|
|
header file:
|
|
|
|
```go
|
|
|
|
```go
|
|
|
|
C.MonitorProcesses()
|
|
|
|
C.MonitorProcesses()
|
|
|
|
```
|
|
|
|
```
|
|
|
@ -104,25 +105,25 @@ This function does two things: it starts by accessing a singleton of our
|
|
|
|
Objective-C class `ProcWatcher` (that's `[ProcWatcher shared]`, which is defined
|
|
|
|
Objective-C class `ProcWatcher` (that's `[ProcWatcher shared]`, which is defined
|
|
|
|
[here](blob/3767628a0e24c6bccd463a2616f7f5226d4e1c9c/ProcWatcher.m#L6)) and
|
|
|
|
[here](blob/3767628a0e24c6bccd463a2616f7f5226d4e1c9c/ProcWatcher.m#L6)) and
|
|
|
|
invoking its `startWatching` method. This subscribes our `ProcWatcher` instance
|
|
|
|
invoking its `startWatching` method. This subscribes our `ProcWatcher` instance
|
|
|
|
to OS notifications. We'll take a look at what the notification subscription
|
|
|
|
to OS notifications. We'll come back to how the ProcWatcher subscribes to
|
|
|
|
looks like in a bit.
|
|
|
|
events in a bit.
|
|
|
|
|
|
|
|
|
|
|
|
#### sidebar: the Run Loop
|
|
|
|
#### sidebar: the Run Loop
|
|
|
|
|
|
|
|
|
|
|
|
After signing up for the notifications, we access the current processes'
|
|
|
|
After signing up for the notifications, we access the current processes'
|
|
|
|
runloop with `[NSRunLoop currentRunLoop]` and call its
|
|
|
|
Run Loop with `[NSRunLoop currentRunLoop]` and call its
|
|
|
|
[`run`](https://developer.apple.com/reference/foundation/nsrunloop/1412430-run?language=objc)
|
|
|
|
[`run`](https://developer.apple.com/reference/foundation/nsrunloop/1412430-run?language=objc)
|
|
|
|
method to run the run loop. There are two reasons why we need to start the
|
|
|
|
method to run the Run Loop. There are two reasons why we need to start the
|
|
|
|
runloop. The first has to do with the mechanics of AppKit. NSRunLoop represents
|
|
|
|
Run Loop. The first has to do with the mechanics of AppKit. NSRunLoop represents
|
|
|
|
the event loop underpinning our notification center. Without the runloop
|
|
|
|
the event loop underpinning our notification center. Without the Run Loop
|
|
|
|
running, the notification center won't ever pick up any notifications. Apple
|
|
|
|
running, the notification center won't ever pick up any notifications. Apple
|
|
|
|
has a wealth of documentation with respect to the mechanics of Run Loops. If
|
|
|
|
has a wealth of documentation with respect to the mechanics of Run Loops. If
|
|
|
|
you're _extremely curious_ about this part of the project, [this
|
|
|
|
you're _extremely curious_ about this part of the project, [this
|
|
|
|
page](https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/Multithreading/RunLoopManagement/RunLoopManagement.html)
|
|
|
|
page](https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/Multithreading/RunLoopManagement/RunLoopManagement.html)
|
|
|
|
has some great literature on how the Run Loop is operating inside of AppKit.
|
|
|
|
has some great literature on how the Run Loop is operating inside of AppKit.
|
|
|
|
|
|
|
|
|
|
|
|
The other reason we invoke the runloop in this way is that calling our
|
|
|
|
The other reason we invoke the Run Loop in this way is that calling our
|
|
|
|
runloop's run method blocks until the runloop itself terminates. Since we're
|
|
|
|
Run Loop's run method blocks until the Run Loop itself terminates. Since we're
|
|
|
|
invoking the C function from within the Go program's `main` function, we're
|
|
|
|
invoking the C function from within the Go program's `main` function, we're
|
|
|
|
blocking Go's `main` function, thus preventing `main` from returning. If `main`
|
|
|
|
blocking Go's `main` function, thus preventing `main` from returning. If `main`
|
|
|
|
returns in the Go program, the Go runtime ends the process, which is _not_ what
|
|
|
|
returns in the Go program, the Go runtime ends the process, which is _not_ what
|
|
|
|