Swift "Array"内存分配,加上切换显示镜像

Swift "Array" memory allocation, plus toggle display mirroring

注意:请参阅下面的修改后的 post:硬件镜像

我写了两个 Swift 函数来切换 OSX 中的显示镜像。两者都有效;它们之间的区别只是处理指针时的语法。为了方便那些有兴趣学习如何在 Swift 中切换镜像的人,我在下面包含了 playground 文件的文本。

我的问题是关于内存分配的。这是感兴趣的部分:

切换镜像丑化

    // allocate space for array
    let displayListPtr = displayIDListPtr.alloc(Int(displayCount)) //see typealias above
    // fill the list
    postError(CGGetActiveDisplayList(displayCount, displayListPtr, &activeCount))

切换镜像

    // allocate space for list of displays
    var displayIDList = Array<CGDirectDisplayID>(count: Int(displayCount), repeatedValue: kCGNullDirectDisplay)
    // fill the list
    postError(CGGetActiveDisplayList(displayCount, &displayIDList, &activeCount))

CGGetActiveDisplayList 是一个低级函数调用,它依赖于排列在连续内存位置的数据。我有理由相信丑陋版本的“alloc”是连续的。根据经验,“Array(...)”调用似乎也是连续的,但我可以相信它始终为真吗(例如,如果显示的数量增加)?这个关于 Swift 数组初始值设定项形式不佳的假设吗?


这是所有代码;为格式问题道歉。请注意,只应调用两个函数中的一个;否则,你会回到起点。

//: Playground - noun: a place where people can play

import Cocoa

// apparently not defined in Swift version of SDK 10.11 (XCode 7.3.1), so add manually
let kCGNullDirectDisplay = CGDirectDisplayID(0)
let kCGDirectMainDisplay = CGMainDisplayID()        // not used here, just for the record

let maxDisplays:UInt32 = 20    // not used
var onlineCount:UInt32 = 0     // not used

func postError(error : CGError){
    if error != CGError.Success {
        print("got an error")
    }
}


// this toggles all active displays, online or not
func toggleMirroring(){
    var displayCount:UInt32 = 0
    var activeCount:UInt32 = 0
    //var onlineCount:UInt32 = 0      //not used

    //get count of active displays (by passing nil to CGGetActiveDisplayList
    postError(CGGetActiveDisplayList(0, nil, &displayCount))

    if displayCount < 2 { return }  // no point in any mirroring functions

    //***
    // allocate space for list of displays
    var displayIDList = Array<CGDirectDisplayID>(count: Int(displayCount), repeatedValue: kCGNullDirectDisplay)

    // fill the list
    postError(CGGetActiveDisplayList(displayCount, &displayIDList, &activeCount))
    //***

    // determine if mirroring is active
    // hack to convert from boolean_t (aka UInt32) to swift's bool
    let displaysMirrored = CGDisplayIsInMirrorSet(CGMainDisplayID()) != 0

    // set master based on current mirroring state
    // if mirroring, master = null, if not, master = main display
    let master = (true == displaysMirrored) ? kCGNullDirectDisplay : CGMainDisplayID()

    // start the configuration
    var configRef:CGDisplayConfigRef = nil  //swift 3 syntax

    postError(CGBeginDisplayConfiguration(&configRef));

    for i in 0..<Int(displayCount) {
        let currentDisplay = CGDirectDisplayID(displayIDList[i])
        if CGMainDisplayID() != currentDisplay {
        CGConfigureDisplayMirrorOfDisplay(configRef, currentDisplay, master);
        }
    }

    if (false){     // change to true in order to execute the toggle
        postError(CGCompleteDisplayConfiguration (configRef,CGConfigureOption.Permanently))
    }

// The first entry in the list of active displays is the main display. In case of mirroring, the first entry is the largest drawable display or, if all are the same size, the display with the greatest pixel depth.
// The "Permanently" option might not survive reboot when run from playground, but does when run in an application
}

func toggleMirroringUgly(){
// just to decrease eye strain
typealias displayIDListPtr = UnsafeMutablePointer<CGDirectDisplayID>
typealias configurationRefPtr = UnsafeMutablePointer<CGDisplayConfigRef>

//get count of active displays (by passing nil to CGGetActiveDisplayList
postError(CGGetActiveDisplayList(0, nil, &displayCount))

if displayCount < 2 { return }  // no point in any mirroring functions

// ***
// allocate space for array
let displayListPtr = displayIDListPtr.alloc(Int(displayCount)) //see typealias above
// fill the list
postError(CGGetActiveDisplayList(displayCount, displayListPtr, &activeCount))
// ***

// determine if mirroring is active
// hack to convert from boolean_t (aka UInt32) to swift's bool
let displaysMirrored = CGDisplayIsInMirrorSet(CGMainDisplayID()) != 0
// set master based on current mirroring state
// if mirroring master = null, if not, master = main display
let master = (true == displaysMirrored) ? kCGNullDirectDisplay : CGMainDisplayID()

// make room for the configuration reference
let configRefPtr = configurationRefPtr.alloc(1)     //see typealias above
// start the configuration
postError(CGBeginDisplayConfiguration (configRefPtr));

for i in 0..<displayCount {
    let currentDisplay = CGDirectDisplayID(displayListPtr[Int(i)])
    if CGMainDisplayID() != currentDisplay {
        CGConfigureDisplayMirrorOfDisplay(configRefPtr[0], currentDisplay, master);
    }
}

if (false){         //change to true in order to flip the mirroring
    // make it happen
    postError(CGCompleteDisplayConfiguration (configRefPtr[0],CGConfigureOption.Permanently));
}
// The first entry in the list of active displays is the main display. In case of mirroring, the first entry is the largest drawable display or, if all are the same size, the display with the greatest pixel depth.
// The "Permanently" option might not survive reboot when run from playground, but does when run in an application
}


toggleMirroring()

数组不一定使用连续存储。如果您愿意,可以使用 ContiguousArray 类型,但您仍然需要处理最大尺寸与最终调用 CGGetActiveDisplayList 后返回的实际尺寸之间可能存在的差异.

清理它的一种方法可能是为 Array 创建一个自定义的便利初始值设定项:

extension Array {

    init<Size: IntegerType>(
        fillingBufferOfSize maxSize: Size,
        @noescape fillBuffer: (buffer: UnsafeMutablePointer<Element>, count: inout Size) throws -> ()) rethrows
    {
        let maxSizeAsInt = Int(maxSize.toIntMax())
        let buf = UnsafeMutablePointer<Element>.alloc(maxSizeAsInt)
        defer { buf.dealloc(maxSizeAsInt) }

        var actualCount: Size = 0
        try fillBuffer(buffer: buf, count: &actualCount)

        self.init(UnsafeBufferPointer(start: buf, count: Int(actualCount.toIntMax())))
    }

}

那么你可以使用Array(fillingBufferOfSize: ...):

var maxActive: UInt32 = 0
CGGetActiveDisplayList(0, nil, &maxActive)

let displays = Array(fillingBufferOfSize: maxActive) { (buffer, count) in
    CGGetActiveDisplayList(maxActive, buffer, &count)
}

我用新的视频卡和 NVIDIA 驱动程序升级了我的计算机,发现我上面的代码不再完全有效 - 打开但不是关闭镜像。显然,驱动程序可以选择使用硬件或软件镜像,这会改变编码。我在post下面修改了一个版本。

它仅在我的系统 (10.12.2) 和卡 (GTX 980Ti) 上进行了测试,但我认为逻辑应该适用于软件镜像和相当新的 OS 版本。如果您有 2 个以上的显示器,您可能会付出巨大的努力来修改它以反映任意组合。我的代码将只在所有其他显示器上镜像任何被认为是主显示器(或软件镜像中最低分辨率的显示器)。

尽管 jbandes 的注释 re: ContiguousArray 提供了信息,但它在这种情况下不起作用 - 请参阅代码中的注释。此代码假定分配的 UInt32 数组是连续的。 (需要做太多工作才能喜欢上 malloc 和转换,但这还没有准备好生产。)

祝 2 位可能感兴趣的人好运!

//: Playground - noun: a place where people can play
import Cocoa
import Foundation

func postError(_ error : CGError){
    if error != CGError.success {
        print("got an error")
    }
}

func disableHardwareMirroring(){
    // designed for hardware mirroring with > 1 display
    // should be no penalty for running with only 1 display, using either hardware or software mirroring drivers
    // but not tested

    // start the configuration
    var configRef:CGDisplayConfigRef? = nil
    postError(CGBeginDisplayConfiguration(&configRef))

    // only interested in the main display
    // kCGNullDirectDisplay parameter disables hardware mirroring
    CGConfigureDisplayMirrorOfDisplay(configRef, CGMainDisplayID(), kCGNullDirectDisplay)

    // may not be permanent between boots using Playgroud, but is in an application
    postError(CGCompleteDisplayConfiguration (configRef,CGConfigureOption.permanently))
}

func toggleMirroring(){
    var displayCount:UInt32 = 0
    var activeCount:UInt32 = 0      //used as a parameter, but value is ignored
    //var onlineCount:UInt32 = 0    //not used

    //get count of active displays (by passing nil to CGGetActiveDisplayList
    postError(CGGetActiveDisplayList(0, nil, &displayCount))

    if displayCount == 1 {
        // either it's hardware mirroring or who cares?
        disableHardwareMirroring()
        return
    }

    // allocate space for list of displays
    // tried to use ContiguousArray, but CGGetActiveDisplayList requires Array<CGDirectDisplayID> parameter
    // ContiguousArrays cannot be typecast to Arrays (at least not easily)

    var displayIDList = Array<CGDirectDisplayID>(repeating: kCGNullDirectDisplay, count: Int(displayCount))

    // fill the list
    postError(CGGetActiveDisplayList(displayCount, &(displayIDList), &activeCount))


    // determine if mirroring is active (only relevant for software mirroring)
    // hack to convert from boolean_t (aka UInt32) to swift's bool
    let displaysMirrored = CGDisplayIsInMirrorSet(CGMainDisplayID()) != 0

    // set master based on current mirroring state
    // if mirroring, master = null, if not, master = main display
    let master = (true == displaysMirrored) ? kCGNullDirectDisplay : CGMainDisplayID()

    // start the configuration
    var configRef:CGDisplayConfigRef? = nil

    postError(CGBeginDisplayConfiguration(&configRef))

    for i in 0..<Int(displayCount) {
        let currentDisplay = CGDirectDisplayID(displayIDList[i])
        if CGMainDisplayID() != currentDisplay {
            CGConfigureDisplayMirrorOfDisplay(configRef, currentDisplay, master)
        }
    }

    postError(CGCompleteDisplayConfiguration (configRef,CGConfigureOption.permanently))

    // The first entry in the list of active displays is the main display. In case of mirroring, the first entry is the largest drawable display or, if all are the same size, the display with the greatest pixel depth.
    // The "Permanently" option might not survive reboot when run from playground, but does when run in an application
}


if (false) {  // change to true to run the code, false to edit
    toggleMirroring()
}