Help with calling Nikon SDK libraries from Swift

I am trying to figure out how to call the Nikon SDK libraries from Swift but have little knowledge of loading and calling dynamic libraries or how to allocate memory and call functions with what appear to be lots of pointers.

I came across a recent post by @eskimo that seems to be the kind of approach I should be taking hence my post here.(How to call c function from .dylib file in SPM package swift 5)

Below is some basic information on the SDK itself and details of the files included in their sample program.

So it appears that there are different modules that can be loaded supporting different camera models and there is a MAID interface that is used by the client application to interface with the module (and camera device).

I assume the libNkPTPDriver2.dylib is the interface to the device and is used by the "Type0024 Module.bundle". I also assume that the "Type0024 Module.bundle" contains the client library that will be used by the app to access the camera device.

No idea about the other two libraries (Royalmile.framework and Carbon.framework, the latter appears to be an Apple framework!).

I have created a macOS Swift application in the same project as their sample application and included the same set of external libraries (embed&sign) and created a bridging header file and import the "Maid3.h", "Maid3d1.h" and "CtrlSample.h" files.

I have also created a function to load the libraries - modelled on the similar functions in the Function.cpp file - however the module seems to be placing in a PlugIns folder when the app gets built. I have zero experience using plugins but assume they are dynamically loaded hence the existence of the Search_Module() and Load_Module() functions in the sample.

So in an attempt to use the library from Swift I have created the class shown below.

I would appreciate it if someone could cast their eye over this and tell me if there is a better way of doing this and if this is the correct approach then were am I going wrong because calling the function currently throws an exception.

import Foundation

class NikonZ6Controller {
    
    static let shared: NikonZ6Controller = NikonZ6Controller()
    
    var gBundle: CFBundle? = nil
    var g_pMAIDEntryPoint: LPMAIDEntryPointProc?
    var g_bFileRemoved: UCHAR     = 0
    var g_ulCameraType: ULONG     = 0    // CameraType
    
    var modUrl: CFURL?
    
    var pRefMod: LPRefObj?
    var buf = [Int8](repeating: 0, count: 250)
    var ulModID: ULONG = 0
    var ulSrcID: ULONG = 0
    var wSel: UWORD = 0;
    var bRet: Bool = false;
    
    var refMod: tagRefObj?
    
    func load()->Bool{
        
        guard self.Search_Module() else {
            return false
        }
        
        guard self.Load_Module() else {
            return false
        }
        
        // Allocate memory for reference to Module object.
        self.pRefMod = UnsafeMutablePointer<RefObj>.allocate(capacity: MemoryLayout<RefObj>.size)
        
        if ( self.pRefMod == nil ) {
            print( "There is not enough memory." );
            return false
        }
        
        InitRefObj( pRef: self.pRefMod! );
        
        // Allocate memory for Module object.
        self.pRefMod?.pointee.pObject = UnsafeMutablePointer<NkMAIDObject>.allocate(capacity: MemoryLayout<NkMAIDObject>.size)
        
        if ( self.pRefMod?.pointee.pObject == nil ) {
            puts( "There is not enough memory." );
            if ( self.pRefMod != nil )  {  free( self.pRefMod ); }
            return false;
        }
        
        //    Open Module object
        self.pRefMod!.pointee.pObject!.pointee.refClient = UnsafeMutableRawPointer(self.pRefMod);
        
        let intRepresentation = UInt64(bitPattern:Int64(Int(bitPattern: self.pRefMod!.pointee.pObject!)))
        
        bRet = Command_Open(   pParentObj: nil,                    // When Module_Object will be opend, "pParentObj" is "NULL".
            pChildObj: intRepresentation,           // Pointer to Module_Object
            ulChildID: ulModID );                   // Module object ID set by Client
        
        if ( bRet == false ) {
            print( "Module object can't be opened.\n" );
            if ( self.pRefMod!.pointee.pObject != nil )  {  free( self.pRefMod!.pointee.pObject ); }
            if ( self.pRefMod != nil )  {  free( self.pRefMod ); }
            return false;
        }
        
        return true
    }
    
    func Search_Module()->Bool
    {
        
        let moduleName = "Type0024 Module.bundle" as CFString
        
        let bundle = CFBundleGetMainBundle();
        
        if(bundle != nil)
        {
            let pluginURL = CFBundleCopyBuiltInPlugInsURL( bundle );
            
            if(pluginURL != nil)
            {
                
                let moduleURL = CFURLCreateCopyAppendingPathComponent( kCFAllocatorDefault, pluginURL, moduleName, false );
                
                if(moduleURL != nil)
                {
                    if(CFURLResourceIsReachable( moduleURL, nil ))
                    {
                        self.modUrl = moduleURL
                        
                        return true
                    }
                }
            }
        }
        return false
  
    }
    
    func Load_Module( )->Bool
    {
   
        if(self.modUrl != nil)
        {
            if(self.gBundle != nil)
            {
                self.gBundle = nil;
            }
            self.gBundle = CFBundleCreate( kCFAllocatorDefault, self.modUrl! );
        }
    
        if(self.gBundle == nil)
        {
            return false
        }
        
        // Load and link dynamic CFBundle object
        if(!CFBundleLoadExecutable(self.gBundle))
        {
            self.gBundle = nil;
            return false
        }
        
        // Get entry point from BundleRef
        // Set the pointer for Maid entry point LPMAIDEntryPointProc type variabl
        self.g_pMAIDEntryPoint = CFBundleGetFunctionPointerForName( gBundle, "MAIDEntryPoint" as CFString).load(as: LPMAIDEntryPointProc.self)
        
        return (self.g_pMAIDEntryPoint != nil)
        
    }
    func CallMAIDEntryPoint(
        _ pObject: LPNkMAIDObject?,                // module, source, item, or data object
        _ ulCommand: ULONG                ,            // Command, one of eNkMAIDCommand
        _ ulParam: ULONG                ,                // parameter for the command
        _ ulDataType: ULONG                ,            // Data type, one of eNkMAIDDataType
        _ data: NKPARAM            ,                    // Pointer or long integer
        _ pfnComplete: LPNKFUNC?            ,            // Completion function, may be NULL
        _ refComplete: NKREF?                 )->Int32?        // Value passed to pfnComplete
    {
        
        if let ep = self.g_pMAIDEntryPoint {
            let res = ep( pObject, ulCommand, ulParam, ulDataType, data, pfnComplete, refComplete );
            return res
        } else {
            return nil
        }
    }
    
    func Command_Open( pParentObj: LPNkMAIDObject?, pChildObj: NKPARAM, ulChildID: ULONG)->Bool
    {
        let lResult = CallMAIDEntryPoint( nil, kNkMAIDCommand_Open.rawValue, ulChildID,
                                          kNkMAIDDataType_ObjectPtr.rawValue, pChildObj, nil, nil );
        return lResult == kNkMAIDResult_NoError.rawValue;
    }
    
    //------------------------------------------------------------------------------------------------
    //
    func Command_Close( pObject: LPNkMAIDObject)->Bool
    {
        let nResult = CallMAIDEntryPoint( pObject, kNkMAIDCommand_Close.rawValue, 0, 0, 0, nil, nil );
    
        return nResult == kNkMAIDResult_NoError.rawValue;
    }
    
    func InitRefObj( pRef: LPRefObj )
    {
        pRef.pointee.pObject = nil;
        pRef.pointee.lMyID = 0x8000;
        pRef.pointee.pRefParent = nil;
        pRef.pointee.ulChildCount = 0;
        pRef.pointee.pRefChildArray = nil;
        pRef.pointee.ulCapCount = 0;
        pRef.pointee.pCapArray = nil;
    }
}

Further to my question above some information from the SDK documents.

Is this SDK public? If so, can you post a URL to access it?

Share and Enjoy

Quinn “The Eskimo!” @ DTS @ Apple

It's not entirely public - you have to apply for access but that didn't involve much.

Here is the link. https://sdk.nikonimaging.com/apply/

It's not entirely public - you have to apply for access

My management won’t let me do that, alas.


Looking at the code you posted the problem that jumps out is this:

self.g_pMAIDEntryPoint = CFBundleGetFunctionPointerForName( gBundle, "MAIDEntryPoint" as CFString).load(as: LPMAIDEntryPointProc.self)

By calling load(as:) you are dereferencing the pointer. What you want instead is unsafeBitCast(_:to:).

Share and Enjoy

Quinn “The Eskimo!” @ DTS @ Apple

1 Like

Thanks, I'll try that - I take it the @ DTS @ Apple means you're an Apple employee?

I take it the rest of my code is heading in the right direction then? This is taking me back to 1990 when all there was was C and assembler !

->. that seems to work, back shortly with the next problem I am sure :slight_smile:

I take it the @ DTS @ Apple means you're an Apple employee?

Yes. You can find more about what I do in my user profile.

I take it the rest of my code is heading in the right direction then?

Roughly, yes. But there’s a tonne of fiddly details here and it’s best to get them all right.

Looking through the code in a little more detail the other big problem relates to the pointer lifecycle. For example, I believe that RefObj is a C structure. You allocate it using this code:

UnsafeMutablePointer<RefObj>.allocate(capacity: MemoryLayout<RefObj>.size)

This isn’t right. The capacity property is the number of these structures you’re trying to allocate, so you should pass 1 here. The call knows how big a structure to allocate because it’s generic on the struct type.

You then call InitRefObj to fill out all the fields, which is technically illegal. The memory returned by allocate(capacity:) is not initialised, so you can’t access it (even to initialise it). You should initialise it using initialize(to:). In practice this is unlikely to cause problems because this is a trivial type imported from C, but it’s still better to do it right.

Similarly for the deinitialise and deallocate path.

There’s no doubt that wrangling unsafe pointers in Swift is a challenge (it’s a challenge in C too, it’s just that folks gloss over the details!). Members of the Swift team gave two great talks at WWDC 2020 that go into this in depth:

I strongly recommend that you watch these before continuing.

Share and Enjoy

Quinn “The Eskimo!” @ DTS @ Apple

2 Likes

Thanks - I will watch them - things get substantially more 'hairy' like the following:

// Enumerate Capabilities that the Module has.

bRet = EnumCapabilities( pRefMod->pObject, &(pRefMod->ulCapCount), &(pRefMod->pCapArray), **NULL** , **NULL** );

**if** ( bRet == **FALSE** ) {

puts( "Failed in enumeration of capabilities." );

**if** ( pRefMod->pObject != **NULL** ) free( pRefMod->pObject );

**if** ( pRefMod != **NULL** ) free( pRefMod );

**return** -1;

}

// Set the callback functions(ProgressProc, EventProc and UIRequestProc).

bRet = SetProc( pRefMod );

**if** ( bRet == **FALSE** ) {

puts( "Failed in setting a call back function." );

**if** ( pRefMod->pObject != **NULL** ) free( pRefMod->pObject );

**if** ( pRefMod != **NULL** ) free( pRefMod );

**return** -1;

}

And then some of the function setting the callbacks

//------------------------------------------------------------------------------------------------------------------------------------

//

BOOL SetProc( LPRefObj pRefObj )

{

BOOL bRet;

NkMAIDCallback stProc;

stProc.refProc = (NKREF)pRefObj;

**if** ( CheckCapabilityOperation( pRefObj, kNkMAIDCapability_ProgressProc, kNkMAIDCapOperation_Set ) ){

stProc.pProc = (LPNKFUNC)ProgressProc;

bRet = Command_CapSet( pRefObj->pObject, kNkMAIDCapability_ProgressProc, kNkMAIDDataType_CallbackPtr, (NKPARAM)&stProc, **NULL** , **NULL** );

**if** ( bRet == **FALSE** ) **return** **FALSE** ;

}

**switch** ( pRefObj->pObject->ulType ) {

**case** kNkMAIDObjectType_Module:

// If Module object supports Cap_EventProc, set ModEventProc.

**if** ( CheckCapabilityOperation( pRefObj, kNkMAIDCapability_EventProc, kNkMAIDCapOperation_Set ) ) {

stProc.pProc = (LPNKFUNC)ModEventProc;

bRet = Command_CapSet( pRefObj->pObject, kNkMAIDCapability_EventProc, kNkMAIDDataType_CallbackPtr, (NKPARAM)&stProc, **NULL** , **NULL** );

**if** ( bRet == **FALSE** ) **return** **FALSE** ;

}

// UIRequestProc is supported by Module object only.

**if** ( CheckCapabilityOperation( pRefObj, kNkMAIDCapability_UIRequestProc, kNkMAIDCapOperation_Set ) ) {

stProc.pProc = (LPNKFUNC)UIRequestProc;

bRet = Command_CapSet( pRefObj->pObject, kNkMAIDCapability_UIRequestProc, kNkMAIDDataType_CallbackPtr, (NKPARAM)&stProc, **NULL** , **NULL** );

**if** ( bRet == **FALSE** ) **return** **FALSE** ;

}

**break** ;

**case** kNkMAIDObjectType_Source:

// If Source object supports Cap_EventProc, set SrcEventProc.

**if** ( CheckCapabilityOperation( pRefObj, kNkMAIDCapability_EventProc, kNkMAIDCapOperation_Set ) ) {

stProc.pProc = (LPNKFUNC)SrcEventProc;

bRet = Command_CapSet( pRefObj->pObject, kNkMAIDCapability_EventProc, kNkMAIDDataType_CallbackPtr, (NKPARAM)&stProc, **NULL** , **NULL** );

**if** ( bRet == **FALSE** ) **return** **FALSE** ;

}

**break** ;

**case** kNkMAIDObjectType_Item:

// If Item object supports Cap_EventProc, set ItmEventProc.

**if** ( CheckCapabilityOperation( pRefObj, kNkMAIDCapability_EventProc, kNkMAIDCapOperation_Set ) ) {

stProc.pProc = (LPNKFUNC)ItmEventProc;

bRet = Command_CapSet( pRefObj->pObject, kNkMAIDCapability_EventProc, kNkMAIDDataType_CallbackPtr, (NKPARAM)&stProc, **NULL** , **NULL** );

**if** ( bRet == **FALSE** ) **return** **FALSE** ;

}

**break** ;

**case** kNkMAIDObjectType_DataObj:

// if Data object supports Cap_EventProc, set DatEventProc.

**if** ( CheckCapabilityOperation( pRefObj, kNkMAIDCapability_EventProc, kNkMAIDCapOperation_Set ) ) {

stProc.pProc = (LPNKFUNC)DatEventProc;

bRet = Command_CapSet( pRefObj->pObject, kNkMAIDCapability_EventProc, kNkMAIDDataType_CallbackPtr, (NKPARAM)&stProc, **NULL** , **NULL** );

**if** ( bRet == **FALSE** ) **return** **FALSE** ;

}

**break** ;

}

**return** **TRUE** ;

}

WRT to allocation of memory and initialising the object in the example you mentioned above would this be the correct way of doing that:

// Allocate memory for reference to Module object.
        self.pRefMod = UnsafeMutablePointer<RefObj>.allocate(capacity: 1)
        
        if ( self.pRefMod == nil ) {
            print( "There is not enough memory." );
            return false
        }
        
        self.pRefMod!.initialize(to: RefObj())
        
        InitRefObj( pRef: self.pRefMod! );

And

func InitRefObj( pRef: LPRefObj )
{
    pRef.pointee.pObject = nil;
    pRef.pointee.lMyID = 0x8000;
    pRef.pointee.pRefParent = nil;
    pRef.pointee.ulChildCount = 0;
    pRef.pointee.pRefChildArray = nil;
    pRef.pointee.ulCapCount = 0;
    pRef.pointee.pCapArray = nil;
}

With the structure as follows:

**typedef** **struct** tagRefObj

{

LPNkMAIDObject pObject;

SLONG lMyID;

LPVOID pRefParent;

ULONG ulChildCount;

LPVOID pRefChildArray;

ULONG ulCapCount;

LPNkMAIDCapInfo pCapArray;

} RefObj, *LPRefObj;

After reviewing those video's it seems one option I have is to work with Swift objects where possible and pass pointers to those objects to the C functions. So if I change the main structure as follows

struct OSRefObj
{
    var object: NkMAIDObject?
    var lMyID: SLONG = 0
    var pRefParent: LPVOID?
    var ulChildCount: ULONG
    var pRefChildArray: LPVOID?
    var ulCapCount: ULONG = 0
    var pCapArray: LPNkMAIDCapInfo?
}

And then user withUnsafeMutablePointer {} closure to as shown in the load() method I should be able to avoid memory allocation and management on at least these objects and keep things more Swift, is this correct and perhaps a better approach?

class NikonZ6Controller {
    
    static let shared: NikonZ6Controller = NikonZ6Controller()
    
    var gBundle: CFBundle? = nil
    var g_pMAIDEntryPoint: LPMAIDEntryPointProc?
    var g_bFileRemoved: UCHAR     = 0
    var g_ulCameraType: ULONG     = 0    // CameraType
    
    var modUrl: CFURL?
    
    var refMod: OSRefObj?
    var maidObj: NkMAIDObject?
    
    var buf = [Int8](repeating: 0, count: 250)
    var ulModID: ULONG = 0
    var ulSrcID: ULONG = 0
    var wSel: UWORD = 0;
    var bRet: Bool = false;
    
    func load()->Bool{
        
        guard self.Search_Module() else {
            return false
        }
        
        guard self.Load_Module() else {
            return false
        }
        
        // Allocate memory for reference to Module object.
        self.refMod = OSRefObj(object: nil, lMyID: 0x8000, pRefParent: nil, ulChildCount: 0, pRefChildArray: nil, ulCapCount: 0, pCapArray: nil)
        
        guard var refMod = self.refMod else {
            return false
        }
        
        let result: Bool = withUnsafeMutablePointer(to: &refMod) { pMod  in
            
            self.maidObj = NkMAIDObject(ulType: 0, ulID: 0, refClient: pMod, refModule: nil)
            
            return withUnsafeMutablePointer(to: &self.maidObj) { pMaid  in
                
                let intRepresentation = UInt64(bitPattern:Int64(Int(bitPattern: pMaid)))
                
                bRet = Command_Open(   pParentObj: nil,                    // When Module_Object will be opend, "pParentObj" is "NULL".
                    pChildObj: intRepresentation,           // Pointer to Module_Object
                    ulChildID: ulModID );
                
                return bRet
            }
        }
        
        
        return result
    }
    
    func Search_Module()->Bool
    {
        
        let moduleName = "Type0024 Module.bundle" as CFString
        
        let bundle = CFBundleGetMainBundle();
        
        if(bundle != nil)
        {
            let pluginURL = CFBundleCopyBuiltInPlugInsURL( bundle );
            
            if(pluginURL != nil)
            {
                
                let moduleURL = CFURLCreateCopyAppendingPathComponent( kCFAllocatorDefault, pluginURL, moduleName, false );
                
                if(moduleURL != nil)
                {
                    if(CFURLResourceIsReachable( moduleURL, nil ))
                    {
                        self.modUrl = moduleURL
                        
                        return true
                    }
                }
            }
        }
        return false
        
    }
    
    func Load_Module( )->Bool
    {
        
        if(self.modUrl != nil)
        {
            if(self.gBundle != nil)
            {
                self.gBundle = nil;
            }
            self.gBundle = CFBundleCreate( kCFAllocatorDefault, self.modUrl! );
        }
        
        if(self.gBundle == nil)
        {
            return false
        }
        
        // Load and link dynamic CFBundle object
        if(!CFBundleLoadExecutable(self.gBundle))
        {
            self.gBundle = nil;
            return false
        }
        
        // Get entry point from BundleRef
        // Set the pointer for Maid entry point LPMAIDEntryPointProc type variabl
        self.g_pMAIDEntryPoint = unsafeBitCast(CFBundleGetFunctionPointerForName( gBundle, "MAIDEntryPoint" as CFString), to:LPMAIDEntryPointProc.self)
        
        //.load(as: LPMAIDEntryPointProc.self)
        
        return (self.g_pMAIDEntryPoint != nil)
        
    }
    func CallMAIDEntryPoint(
        _ pObject: LPNkMAIDObject?,    // module, source, item, or data object
        _ ulCommand: ULONG,            // Command, one of eNkMAIDCommand
        _ ulParam: ULONG,              // parameter for the command
        _ ulDataType: ULONG,           // Data type, one of eNkMAIDDataType
        _ data: NKPARAM,                    // Pointer or long integer
        _ pfnComplete: LPNKFUNC?,           // Completion function, may be NULL
        _ refComplete: NKREF?)->Int32?      // Value passed to pfnComplete
    {
        
        if let ep = self.g_pMAIDEntryPoint {
            let res = ep( pObject, ulCommand, ulParam, ulDataType, data, pfnComplete, refComplete );
            return res
        } else {
            return nil
        }
    }
    
    func Command_Open( pParentObj: LPNkMAIDObject?, pChildObj: NKPARAM, ulChildID: ULONG)->Bool
    {
        let lResult = CallMAIDEntryPoint( nil, kNkMAIDCommand_Open.rawValue, ulChildID,
                                          kNkMAIDDataType_ObjectPtr.rawValue, pChildObj, nil, nil );
        return lResult == kNkMAIDResult_NoError.rawValue;
    }
    
    //------------------------------------------------------------------------------------------------
    //
    func Command_Close( pObject: LPNkMAIDObject)->Bool
    {
        let nResult = CallMAIDEntryPoint( pObject, kNkMAIDCommand_Close.rawValue, 0, 0, 0, nil, nil );
        
        return nResult == kNkMAIDResult_NoError.rawValue;
    }
    
}

Well some things appear to be working, probably more luck than anything else though. The load() method appears to be working fine, and the API call in the enumerate..() function to query the number of capabilities is returning the correct value so I assume that creating the Swift variable ulCapCount and then using the withUnsafeMutablePointer(to: &ulCapCount) {p in ...} is the correct approach.

Attempting a similar approach by creating a Swift array of NkMAIDCapInfo objects and then using withUnsafeMutablePointer(to:&capArray) {} throws an exception when trying to access the capArray after the call has returned, apparently without any error.

The 'unsafe' video's seem to indicate that there are two ways to do things and that for the most part the Swift compiler allows you to pass Swift objects as parameters to C functions directly even when the function is expecting a pointer.

So how do I create the capArray buffer, pass the pointer to it and subsequently access the returned data?

func load()->Bool{
    
    guard self.Search_Module() else {
        return false
    }
    
    guard self.Load_Module() else {
        return false
    }
    
    // Allocate memory for reference to Module object.
    self.refMod = OSRefObj(object: nil, lMyID: 0x8000, pRefParent: nil, ulChildCount: 0, pRefChildArray: nil, ulCapCount: 0, pCapArray: nil)
    
    guard var refMod = self.refMod else {
        return false
    }
    
    let result: Bool = withUnsafeMutablePointer(to: &refMod) { pMod  in
        
        self.maidObj = NkMAIDObject(ulType: 0, ulID: 0, refClient: pMod, refModule: nil)
        
        let result: Bool = withUnsafeMutablePointer(to: &self.maidObj!) { pMaid  in
            
            let intRepresentation = UInt64(bitPattern:Int64(Int(bitPattern: pMaid)))
            
            bRet = Command_Open(   pParentObj: nil,     // When Module_Object will be opend, "pParentObj" is "NULL".
                pChildObj: intRepresentation,           // Pointer to Module_Object
                ulChildID: ulModID );
            
            if ( bRet == false ) {
                print( "Module object can't be opened.\n" );
                return false;
            }
            
            
            return self.enumerateMAIDObjectCapabilities(pObject: pMaid)
            
        }
        
        return result
    }
    
    return result
}
// enumerate the capabilities of an object
func enumerateMAIDObjectCapabilities(pObject: LPNkMAIDObject)->Bool {
    
    var nResult: Int32?
    var ulCapCount: ULONG = 0
    var capArray: [NkMAIDCapInfo?]?
    
    repeat {
        
        let result: Bool = withUnsafeMutablePointer(to: &ulCapCount) { pulCapCount in
            
            let intRepresentation = UInt64(bitPattern:Int64(Int(bitPattern: pulCapCount)))
            
            nResult = CallMAIDEntryPoint(pObject, kNkMAIDCommand_GetCapCount.rawValue, 0, kNkMAIDDataType_UnsignedPtr.rawValue,
                                         intRepresentation,
                                         nil, nil)
            
            return (nResult == kNkMAIDResult_NoError.rawValue)
        }
        
        print("nResult: \(nResult ?? -1)")
        
        guard result else {
            print("Error getting capability count")
            return false
        }
        
        // ulCapCount appears to be correct = 8
        print("ulCapCount = \(ulCapCount)")
            
        // Initialisa an array with ulCapCount items
        capArray = [NkMAIDCapInfo?](repeating: nil, count: Int(ulCapCount))
        
        let result2: Bool = withUnsafeMutablePointer(to: &capArray!) { pCapArray in
                    
            let intRepresentation = UInt64(bitPattern:Int64(Int(bitPattern: pCapArray)))
            
            nResult = CallMAIDEntryPoint(pObject, kNkMAIDCommand_GetCapInfo.rawValue, ulCapCount, kNkMAIDDataType_CapInfoPtr.rawValue,
                                     intRepresentation,
                                     nil, nil)
            

            return (nResult == kNkMAIDResult_NoError.rawValue)
            
        }
        print("result2: \(result2)")

        
    }
    // repeat the process if the number of capabilites changed between the two calls to the module
    while (nResult == kNkMAIDResult_BufferSize.rawValue)
    
    // Exception gets thrown as soon as we try and access the array
    
    guard let capArrayU = capArray else {
        print("capArray in NIL !!")
        return false
    }
    print("capArray: \(capArrayU.count)")
    
    for cap in capArrayU {
        print("capArray = \(cap?.ulType ?? 999)")
    }
    
    // return TRUE if the capabilities were successfully enumerated
    return (nResult == kNkMAIDResult_NoError.rawValue);
    
}

How is NkMAIDCapInfo defined? Is it coming in from C? If so, what does the C declaration look like?

Share and Enjoy

Quinn “The Eskimo!” @ DTS @ Apple

These are some of the typical structs

	typedef struct tagNkMAIDCapInfo
	{
		ULONG	ulID;						// one of eNkMAIDCapability or vendor specified
		ULONG	ulType;					// one of eNkMAIDCapabilityType
		ULONG	ulVisibility;			// eNkMAIDCapVisibility bits
		ULONG	ulOperations;			// eNkMAIDCapOperations bits
		SCHAR	szDescription[256];	// text describing the capability
	} NkMAIDCapInfo, FAR* LPNkMAIDCapInfo;

	typedef struct tagNkMAIDObject
	{
		ULONG	ulType;			// One of eNkMAIDObjectType
		ULONG	ulID;
		NKREF	refClient;
		NKREF	refModule;
	} NkMAIDObject, FAR* LPNkMAIDObject;

	typedef struct tagNkMAIDUIRequestInfo
	{
		ULONG				ulType;			// one of eNkMAIDUIReqestType
		ULONG				ulDefault;		// default return value one of eNkMAIDUIRequestResult
		BOOL				fSync;			// TRUE if user must respond before returning
		SCHAR FAR*		lpPrompt;		// NULL terminated text to show to user
		SCHAR FAR*		lpDetail;		// NULL terminated text indicating more detail
		LPNkMAIDObject	pObject;			// module, source, item, or data object
		NKPARAM			data;				// Pointer to an NkMAIDArray structure
	} NkMAIDUIRequestInfo, FAR* LPNkMAIDUIRequestInfo;

	typedef struct tagNkMAIDDataInfo
	{
		ULONG		ulType;				// one of eNkMAIDDataObjType
	} NkMAIDDataInfo, FAR* LPNkMAIDDataInfo;

	typedef struct tagNkMAIDImageInfo
	{
		NkMAIDDataInfo	base;
		NkMAIDSize	szTotalPixels;	// total size of image to be transfered
		ULONG			ulColorSpace;	// One of eNkMAIDColorSpace
		NkMAIDRect	rData;			// Coords of data, (0,0) = top,left
		ULONG			ulRowBytes;		// number of bytes per row of pixels
		UWORD			wBits[4];		// number of bits per plane per pixel
		UWORD			wPlane;			// Plane of the image being delivered
		BOOL			fRemoveObject;	// TRUE if the object should be removed
	} NkMAIDImageInfo, FAR* LPNkMAIDImageInfo;

And this is the interface specification:

    //================================================================================================
	//	Entrypoint and callback function definitions
	//================================================================================================

	#ifdef __cplusplus
		extern "C" {	// Tell C++ to use C linking for these functions
	#endif

	// Client/Module Interface and Callback Functions
	typedef W32EXPORT NKERROR CALLPASCAL WINAPI MAIDEntryPointProc (
		LPNkMAIDObject	pObject,				// module, source, item, or data object
		ULONG				ulCommand,			// Command, one of eNkMAIDCommand
		ULONG				ulParam,				// parameter for the command
		ULONG				ulDataType,			// Data type, one of eNkMAIDDataType
		NKPARAM			data,					// Pointer or long integer
		LPNKFUNC			pfnComplete,		// Completion function, may be NULL
		NKREF				refComplete );		// Value passed to pfnComplete
	typedef MAIDEntryPointProc FAR* LPMAIDEntryPointProc;

	typedef void CALLPASCAL CALLBACK MAIDCompletionProc (
		LPNkMAIDObject	pObject,				// module, source, item, or data object
		ULONG				ulCommand,			// Command, one of eNkMAIDCommand
		ULONG				ulParam,				// parameter for the command
		ULONG				ulDataType,			// Data type, one of eNkMAIDDataType
		NKPARAM			data,					// Pointer or long integer
		NKREF				refComplete,		// Reference set by client
		NKERROR			nResult );			// One of eNkMAIDResult
	typedef MAIDCompletionProc FAR* LPMAIDCompletionProc;

	typedef NKERROR CALLPASCAL CALLBACK MAIDDataProc (
		NKREF					refClient,		// Reference set by client
		LPVOID				pDataInfo,		// Cast to LPNkMAIDImageInfo or LPNkMAIDSoundInfo
		LPVOID				pData );
	typedef MAIDDataProc FAR* LPMAIDDataProc;

	typedef void CALLPASCAL CALLBACK MAIDEventProc (
		NKREF				refClient,			// Reference set by client
		ULONG				ulEvent,				// One of eNkMAIDEvent
		NKPARAM			data );				// Pointer or long integer
	typedef MAIDEventProc FAR* LPMAIDEventProc;

	typedef void CALLPASCAL CALLBACK MAIDProgressProc (
		ULONG				ulCommand,			// Command, one of eNkMAIDCommand
		ULONG				ulParam,				// parameter for the command
		NKREF				refComplete,		// Reference set by client
		ULONG				ulDone,				// Numerator
		ULONG				ulTotal );			// Denominator
	typedef MAIDProgressProc FAR* LPMAIDProgressProc;

	typedef ULONG CALLPASCAL CALLBACK MAIDUIRequestProc (
		NKREF							refProc,			// reference set by the client
		LPNkMAIDUIRequestInfo	pUIRequest );	// information about the UI request

	typedef MAIDUIRequestProc FAR* LPMAIDUIRequestProc;

	#ifdef __cplusplus
		}
	#endif

Ah, NkMAIDCapInfo is a struct! That explains the problem. Consider your declaration of capArray:

var capArray: [NkMAIDCapInfo?]?

It’s an optional array of optional structs. The first optional doesn’t matter because you force unwrap it it as part of the withUnsafeMutablePointer(…) call. The second optional, however, is a big deal. C does not support optional structs (as opposed to optional pointers to structs) so this array is not layout compatible with C.

To fix this you need to make capArray an array of NkMAIDCapInfo, not NkMAIDCapInfo?. That will require you to initialise the array with something other than nil. In this situation I usually extend the struct to have an empty static member:

extension NkMAIDCapInfo {
    static let empty = NkMAIDCapInfo()
}

and then populate the array like this:

capArray = [NkMAIDCapInfo](repeating: .empty, count: Int(ulCapCount))

I also suggest you audit the rest of your code for unnecessary optionality. For example, capArray doesn’t need to be an optional array because you can initialise it to an empty array.

var capArray: [NkMAIDCapInfo] = []

That change has flow-on effects, reducing the need to handle the optional value in many other places inside enumerateMAIDObjectCapabilities(…). And if you wage this War On Optional™ throughout your program, you’ll find that it’ll radically improve your Swift experience.


Finally, Swift 5.1 added a feature that let’s you initialise an array from an unsafe source without first initialising each of the elements. See SE-0245 Add an Array Initializer with Access to Uninitialized Storage for the details.

Share and Enjoy

Quinn “The Eskimo!” @ DTS @ Apple

Thanks, appreciate the help.

I am sure I tried with non-optional structs and repeating NkMAIDCapInfo() but perhaps something else was broken as well at the time.

Yep - with the change above I still get an exception when attempting to call capArray.count after the call to get the capabilities.

Duncan

Terms of Service

Privacy Policy

Cookie Policy