views:

6956

answers:

6

I'm trying to follow a tutorial for a C++ interface in the Mac OS X API (Audio Queue Services), but in a Cocoa (well, actually just Foundation) application (well, actually just a 'tool'). It has a struct that looks like this:

static const int kNumberBuffers = 3;                              // 1
struct AQPlayerState {
    AudioStreamBasicDescription   mDataFormat;                    // 2
    AudioQueueRef                 mQueue;                         // 3
    AudioQueueBufferRef           mBuffers[kNumberBuffers];       // 4
    AudioFileID                   mAudioFile;                     // 5
    UInt32                        bufferByteSize;                 // 6
    SInt64                        mCurrentPacket;                 // 7
    UInt32                        mNumPacketsToRead;              // 8
    AudioStreamPacketDescription  *mPacketDescs;                  // 9
    bool                          mIsRunning;                     // 10
};

I'm having a lot of trouble with translating item 4 into Objective-C, because I can't figure out how to @synthesize a C array. Specifically, this is what I have so far:

PlayerState.h

#import <Foundation/Foundation.h>
#import <AudioToolbox/AudioQueue.h>

@interface PlayerState : NSObject {
  AudioStreamBasicDescription   dataFormat;
  AudioQueueRef                 queue;
  AudioQueueBufferRef           _buffers[3];
  int                           audioFile; // make this an actual type?
  UInt32                        bufferByteSize;
  SInt64                        currentPacket;
  UInt32                        numPacketsToRead;
  AudioStreamPacketDescription* packetDescs;
  bool                          isRunning;
}

@property(assign) AudioStreamBasicDescription   dataFormat;
@property(assign) AudioQueueRef                 queue;
@property(assign) AudioQueueBufferRef           buffers;
@property(assign) int                           audioFile;
@property(assign) UInt32                        bufferByteSize;
@property(assign) SInt64                        currentPacket;
@property(assign) UInt32                        numPacketsToRead;
@property(assign) AudioStreamPacketDescription* packetDescs;
@property(assign) bool                          isRunning;

@end

PlayerState.m

#import "PlayerState.h"

@implementation PlayerState

@synthesize dataFormat;
@synthesize queue;
@synthesize buffers;
@synthesize audioFile;
@synthesize bufferByteSize;
@synthesize currentPacket;
@synthesize numPacketsToRead;
@synthesize packetDescs;
@synthesize isRunning;

@end

@synthesize buffers fails to compile as follows: "error: synthesized property 'buffers' must either be named the same as a compatible ivar or must explicitly name an ivar"

This is obviously because the corresponding ivar is named _buffers and not buffers - but this is necessary, because I can't define a property as an array (can I? @property(assign) *AudioQueueBufferRef buffers is a syntax error)

What can I do to either define the ivar as an array of AudioQueueBufferRef structs, or synthesize the property such that it refers to the _buffers array?

+2  A: 

Edit: Peter Hosey has pointed out that an array in C is not the same thing as a pointer. (see this document for details). That would explain the error you are seeing, and would make the code that I posted wrong.

The other SO question that gs links to in his answer suggests a work-around, which I have copied, in the context of this question:

// PlayerState.h:
@interface PlayerState : NSObject 
{
    AudioQueueBufferRef           _buffers[3];
}

@property(readonly) AudioQueueBufferRef * buffers;


// PlayerState.m:
@implementation PlayerState

@dynamic buffers;
- (AudioQueueBufferRef *)buffers { return _buffers; }

@end

This would allow you to access buffers as if it were a pointer to an array of AuidoQueueBufferRef objects.

e.James
The problem if I do this (`AudioQueueBufferRef* buffers[3]` in the interface, `@property(assign) AudioQueueBufferRef* buffers` in the header, and `@synthesize buffers` in the implementation) is that it tells me: "error: type of property 'buffers' does not match type of ivar 'buffers'"
elliottcable
An array is automatically a pointer type. You don't need that extra * after AudioQueueBufferRef in the interface.
e.James
So… what should I change it to? I tried juggling brackets, brackets with a number in them, and asterisks all over the place. I just can't get it to compile with any combination of the three.
elliottcable
Exactly as I show it in my code above. Don't use the * after AudioQueueBufferRef when declaring the array, but do use the * when declaring the property.
e.James
AudioQueueBufferRef buffers[3] is actually a pointer. When you declare an array of type X, you are effectively creating a variable of type X* that points to a block of memory big enough to hold the array.
e.James
Unfortunately, that leaves me with the same error: "error: type of property 'buffers' does not match type of ivar 'buffers'".
elliottcable
Well, that's just bizarre. In that case, I'd take a look at the answer from gs. I thought for sure this would work.
e.James
eJames: Not quite. Arrays *are* different from pointers. (This is one of two known errors in my pointer tutorial, which I haven't had time to fix.) I used to have a link to a page that explained all the differences, but can't find it. Here's a substitute: http://www.lysator.liu.se/c/c-faq/c-2.html
Peter Hosey
Oh, hey—found it. http://djmnet.org/lore/arrays-are-not-pointers.txt
Peter Hosey
@Peter Hosey: Wow. I learn something new every day. If I could upvote your comment, I would! Thank you for the link. I'll update my answer.
e.James
+1  A: 

Looks like this question here:

Create an array of integers property in objective-c

Georg
+1  A: 

Why are you translating the structure into an Objective-C object? Objective-C is a strict superset of C, so you can just use the given struct as-is with Objective-C.

[EDIT] In response to your comments, the compiler is complaining about the mBuffers declaration because of the rules about what's allowable as the size of a static array. The rules in C are a little stricter than the rules in C++. As an easy fix, just change the line

static const int kNumberBuffers = 3;

into

#define kNumberBuffers 3

and then the struct should compile correctly (provided, of course, that you've included the necessary headers that define all of the proper data types such as AudioStreamBasicDescription, etc.).

Adam Rosenfield
Unfortunately, that doesn't work. The struct must use some C++ magic that is unknownst to me, because copy-pasting it verbatim into a Foundation tool results in a compile error (compiler doesn't like the `AudioQueueBufferRef mBuffers[kNumberBuffers]` line, for whatever reason).
elliottcable
Did you rename your source file to .mm so that xcode knows to expect objective-c++ (a mix of the two)?
e.James
Nope, I haven't. I want to avoid getting into Objective-C++, or anything involving C++. The goal here is to write an Objective-C class to wrap the tools described in the AQS documents.I'll try that though, just for fun!
elliottcable
+3  A: 

@synthesize buffers fails to compile as follows: "error: synthesized property 'buffers' must either be named the same as a compatible ivar or must explicitly name an ivar"

Try:

@synthesize buffers = _buffers;
Peter Hosey
This makes the posted code preform the same as eJames' code does - I'd give you an upmod, except I'm out of votes for the day. Unfortunately, I end up at the same place as I'm stuck at with eJames' code (see comments above)
elliottcable
A: 

You can either have three buffer ivars (buffer0, buffer1, buffer2) or you need to make it a pointer to the AudioQueueBufferRefs and separately allocate memory to hold the three AudioQueueBufferRefs. Or you use an NSArray or NSData if you need a variable number of buffers.

But stepping back to a larger context, you probably don't need to keep track of the AudioQueueBufferRefs. If you enqueue all three buffers, you'll get pointers to them in the audio queue callbacks.

I have some audio code which records and plays back audio, and it calls AudioQueueAllocateBuffer() and AudioQueueEnqueueBuffer() and pretty much forgets about the buffers after that.

If you're trying to write a Cocoa wrapper for AudioQueue, don't do that.

Edit: But to answer your original question: You can't have an array as a property, because it's not a "Plain Ol' Data" type. See "Declared Properties" in the Objective-C 2.0 documentation in Xcode, and this: http://www.fnal.gov/docs/working-groups/fpcltf/Pkg/ISOcxx/doc/POD.html

lucius
+2  A: 

I'm just now doing a similar thing for an OpenGL app, and since this is a useful question (and so long without an answer) I want to translate my solution. Note that it's probably less effort to use use NSArray… but if you need to hone performance and/or use basic C (or GL or similar non-object) types this should help. Also, forgive me if I do something that doesn't work in OSX, as my code is actually for iPhone... I'm sure that at least the concepts are the same though. Here is an outline of steps:

  1. Declare your variable as a pointer
  2. Protect it from direct outside access with @private
  3. Declare the property as also a pointer, which will be set to point at any array we pass to the setter
  4. Write or synthesize a setter and getter method. My use of @synthesize seemed to confuse things and led to a memory leak, so I'll follow the write-your-own path here.
  5. (The missing link from some previous posts, I think) Since you are returning a C array, allocate memory using malloc. The init method is probably a good place for this, and you can use dealloc or elsewhere to call free(myBuffers) since your variable is of suitably broad scope.

I don't know that @synthesize it will handle malloc'd variables for you, but someone else may have experience with that that I lack. Therefore, I am just writing the setter and getter. But note we still get dot syntax and the vanilla look of @property in the interface.

Here's code:

In PlayerState.h:

@interface PlayerState : NSObject {
    AudioStreamBasicDescription   dataFormat;
    AudioQueueRef                 queue;
@private
    AudioQueueBufferRef           *myBuffers;
    // [...]
    @property(assign) AudioStreamBasicDescription   dataFormat;
    @property(assign) AudioQueueRef                 queue;
    @property(assign) AudioQueueBufferRef           *myBuffers;

That gives you a pointer and a property, the latter of which is equivalent to the accessor methods to the memory indicated by the pointer. We'll write our own accessors, and we will allocate memory for the pointer and give it something to point at.

Now in PlayerState.m:

-(id)init
{
    if (self = [super init]) {
        myBuffers = (AudioQueueBufferRef*)malloc(sizeof(AudioQueueBufferRef) * 3);
        /* memory is allocated. Free it elsewhere. Note that "Leaks" will
        probably falsely flag this, but you can test it in Object Allocations
        and see that it is clearly released. */
    }
    return self;
}

// return an array of structs
-(AudioQueueBufferRef*)myBuffers
{
    // you can check for values, etc here, but basically it all comes down to:
    return myBuffers;
}

    // the setter; there are several ways to write it, depending on what your objective
    -(void)setMyBuffers:(AudioQueueBufferRef*)abc
    {
    int i = 3;
    while (i > 0) {
        i--;
        myBuffers[i] = abc[i];
    }
    //another possible implementation, not tested
    //free(myBuffers);
    //myBuffers = NULL;
    //myBuffers = xyz;
    }

// if you want to set individual attributes, you can have another method like:
-(void)setMyBuffersA:(AudioQueueBufferRef)a B:(AudioQueueBufferRef)b C:(AudioQueueBufferRef)c
{
    myBuffers[0] = a;
    myBuffers[1] = b;
    myBuffers[2] = c;
}

Now you can call these from another class like this:

-(void)methodOfOtherClass
{
    PlayerState * playerState = [[PlayerState alloc] init];
    AudioQueueBufferRef abc[3] = //something

    [playerState setMyBuffers:(AudioQueueBufferRef*)abc];
    DLog(@"I'm in OtherClass and playerState.myBuffers returns %@, %@, %@", playerState.myBuffers[0],playerState.myBuffers[1],playerState.myBuffers[2])

    abc[1] = 6.22; // reassigning one of the values in our local variable
    playerState.myBuffers = (GLfloat*)abc; //call the setter again, with dot syntax this time if you like
    DLog(@"I'm in OtherClass and playerState.myBuffers returns %@, %@, %@", playerState.myBuffers[0],playerState.myBuffers[1],playerState.myBuffers[2])

    [playerState setMyBuffersA:yourStructA B:yourStructB C:yourStructC];
    DLog(@"I'm in OtherClass and playerState.myBuffers returns %@, %@, %@", playerState.myBuffers[0],playerState.myBuffers[1],playerState.myBuffers[2])
}

This tests OK for me using iPhone OS and my (admittedly slightly different) openGL types. However, I think it will work for you too. It's possible you'll have to adjust something, or that I've introduced a minor typo while copying and pasting over my original code. Also, if you like to log things while you're coding like I do, it might be more useful to log some member of the buffer structs to see that what's there is what's supposed to be there.

Also if you're used to printing objects while you code with your NSLog wrapper, ie: DLog(@"array is %@", myNSarray) you'll get an EXEC_BAD_ACCESS trying to do the same with a C array. The C array is not an Obj-C object, so keep that in mind and hold its hand a bit more than you would an Objective-C object ;)

Rab
I’ll have to take a look at this when/if I return to the project that sparked this… I don’t even have the original code, so I can’t even test if this works for the original problem :x
elliottcable