3

I'm coding an app for iOS and I recently #included a C++ header file in an Objective C implementation (.m) file. I changed the extension from .m to .mm and expected everything to run smoothly.

Unexpectedly I got multiple compiler errors in the .h file of my C++ class.

Such as: "C++ requires a type specifier for all declarations" and "Duplicate member...".

Does anyone know what could be causing this?

Edit - I've added the C++ header file for context:

#ifndef __CAAudioUnitOutputCapturer_h__
#define __CAAudioUnitOutputCapturer_h__

#include <AudioToolbox/ExtendedAudioFile.h>

/*
    Class to capture output from an AudioUnit for analysis.

    example:

    CFURL fileurl = CFURLCreateWithFileSystemPath(NULL, CFSTR("/tmp/recording.caf"), kCFURLPOSIXPathStyle, false);

    CAAudioUnitOutputCapturer captor(someAU, fileurl, 'caff', anASBD);

    {
    captor.Start();
    ...
    captor.Stop();
    } // can repeat

    captor.Close(); // can be omitted; happens automatically from destructor
*/

class CAAudioUnitOutputCapturer {
public:
    enum { noErr = 0 };

    CAAudioUnitOutputCapturer(AudioUnit au, CFURLRef outputFileURL, AudioFileTypeID fileType, const AudioStreamBasicDescription &format, UInt32 busNumber = 0) :
        mFileOpen(false),
        mClientFormatSet(false),
        mAudioUnit(au),
        mExtAudioFile(NULL),
        mBusNumber (busNumber)
    {   
        CFShow(outputFileURL);
        OSStatus err = ExtAudioFileCreateWithURL(outputFileURL, fileType, &format, NULL, kAudioFileFlags_EraseFile, &mExtAudioFile);
        if (!err)
            mFileOpen = true;
    }

    void    Start() {
        if (mFileOpen) {
            if (!mClientFormatSet) {
                AudioStreamBasicDescription clientFormat;
                UInt32 size = sizeof(clientFormat);
                AudioUnitGetProperty(mAudioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, mBusNumber, &clientFormat, &size);
                ExtAudioFileSetProperty(mExtAudioFile, kExtAudioFileProperty_ClientDataFormat, size, &clientFormat);
                mClientFormatSet = true;
            }
            ExtAudioFileWriteAsync(mExtAudioFile, 0, NULL); // initialize async writes
            AudioUnitAddRenderNotify(mAudioUnit, RenderCallback, this);
        }
    }

    void    Stop() {
        if (mFileOpen)
            AudioUnitRemoveRenderNotify(mAudioUnit, RenderCallback, this);
    }

    void    Close() {
        if (mExtAudioFile) {
            ExtAudioFileDispose(mExtAudioFile);
            mExtAudioFile = NULL;
        }
    }

    ~CAAudioUnitOutputCapturer() {
        Close();
    }

private:
    static OSStatus RenderCallback( void *                          inRefCon,
                                    AudioUnitRenderActionFlags *    ioActionFlags,
                                    const AudioTimeStamp *          inTimeStamp,
                                    UInt32                          inBusNumber,
                                    UInt32                          inNumberFrames,
                                    AudioBufferList *               ioData)
    {
        if (*ioActionFlags & kAudioUnitRenderAction_PostRender) {
            CAAudioUnitOutputCapturer *This = (CAAudioUnitOutputCapturer *)inRefCon;
            static int TEMP_kAudioUnitRenderAction_PostRenderError  = (1 << 8);
            if (This->mBusNumber == inBusNumber && !(*ioActionFlags & TEMP_kAudioUnitRenderAction_PostRenderError)) {
                OSStatus result = ExtAudioFileWriteAsync(This->mExtAudioFile, inNumberFrames, ioData);
                if (result) DebugMessageN1("ERROR WRITING FRAMES: %d\n", (int)result);
            }
        }
        return noErr;
    }

    bool                mFileOpen;
    bool                mClientFormatSet;
    AudioUnit           mAudioUnit;
    ExtAudioFileRef     mExtAudioFile;
    UInt32              mBusNumber;
};

#endif // __CAAudioUnitOutputCapturer_h__
Orpheus Mercury
  • 1,617
  • 2
  • 15
  • 30
  • Any specific examples? I'm guessing some type hasn't been defined, or there's a name clash with one of the Objective-C runtime types. But that's the problem, I can only guess. – pmdj May 10 '12 at 17:58
  • @pmjordan I just added the header file in question. Maybe that will help. – Orpheus Mercury May 10 '12 at 18:47
  • Please give us the actual error messages you get; nobody wants to hunt through hundreds of lines of code to guess which one might be incorrect! It would also help to give us the .mm file (or at least show us what else you include before this .h file). – abarnert May 11 '12 at 01:03

2 Answers2

3

Unfortunately, if you just start making classes .mm, any class that uses that .mm's header will also need to become .mm. If you continue to just change your class extensions, you will eventually make the whole project Objective-c++. If that is your intention, then you can just change your build settings to compile for Objective-c++ (which could be a house of pain for you).

However, if you use some header magic, you will avoid a lot of hassle. Just make sure to change your Compile sources as build property to According to file type before compiling.

Here's something I did with a wrapper class I wrote to isolate a c++ class from the rest of my Objective-c classes. The c++ class is MyClass.

MyClassWrapper.h

//declare c++ impl for Obj-C++
#ifdef __cplusplus
class CppPlanterModel;
namespace com{namespace company{namespace mypackage {class MyClass;}}}
typedef com::company::mypackage::MyClass CppMyClass;
#endif

//declare obj-c impl
#ifdef __OBJC__
#ifndef __cplusplus
typedef void CppMyClass;
#endif
#endif

@interface MyClassWrapper : NSObject {
    CppMyClass* _myClass;
}
//etc etc
@end

MyClassWrapper.mm

#include "MyClass.h"
using namespace com:company:mypackage;

class CppMyClass : public MyClass {
    CppMyClass() {};
    ~CppMyClass() {};
    //other stuff you might like to have
};

@implementation MyClassWrapper
    //etc etc
@end

Here's another thing I did with a different header to handle sharing extern stuff:

Something.h

#ifdef __cplusplus
#define FV_EXTERN       extern "C" __attribute__((visibility ("default")))
#else
#define FV_EXTERN       extern __attribute__((visibility ("default")))
#endif

FV_EXTERN const int kMyInt;
FV_EXTERN int GetAnotherInt(...);

I recommend reading this blog entry about wrapping c++ (which also has links to other blog entries of a similar topic): http://robnapier.net/blog/wrapping-c-take-2-1-486

Tim Reddy
  • 4,340
  • 1
  • 40
  • 77
  • As long as you don't have any C++-specific stuff in the header, you shouldn't need to make everything else Objective-C++. – Chuck May 10 '12 at 18:38
  • If the c++ header is pretty simple, then yah that is true. You can probably get away with just changing the file to `.mm` and updating the compiler settings as I mention in my answer. – Tim Reddy May 10 '12 at 18:40
  • Thanks for your answer @TReddy. I read it after some research which led me to try changing both my compiler settings and my .m files to .mm, and neither worked. I just included the header for my c++ class in case that adds any perspective. I'm going to give your wrapper solution shot ASAP! – Orpheus Mercury May 10 '12 at 18:50
  • @TReddy Based on both the link you provided me with and on my research, it looks like your answer should do the trick. This is my first exposure to C++ however, and I'm definitely having a challenge comprehending how to implement your solution in my case! +1 though. Thank you. – Orpheus Mercury May 11 '12 at 12:07
  • @TReddy in Rob Napier's blog that you pointed me to one commenter asked this: "Why not just forward declare the class as a struct? C++ classes and structs are compatible so you can simply do: 'struct Wrap;' in your objective-c++ header instead of having to declare a wrapper for it. C (Objective-c) will think of it as a struct and C++ will think of it as a class." Rob agreed that this would work if the class didn't use a namespace and that "now that you can declare ivars in the @ implementation block, much of this technique is not required anymore". Are you able to elucidate this for me at all? – Orpheus Mercury May 11 '12 at 13:05
  • I read that blog entry about a year ago when I implemented this solution and I haven't revisited any alternative ways of doing the same. Maybe you can ask the author to write a blog entry about it? – Tim Reddy May 11 '12 at 13:18
  • @TReddy Haha, I asked him earlier today. Thanks for getting back to me. – Orpheus Mercury May 11 '12 at 16:13
3

following the code at Rob Napier's blog I did it for CAAudioUnitOutputCapturer. Thought I would share it to save other folks time.

RNWrap.h

//
//  RNWrap.h
//
//  ObjC wrapper for Wrap.cpp
//

#import <Foundation/Foundation.h>
#import <CoreFoundation/CoreFoundation.h>
#import <AudioUnit/AudioUnit.h>
#import <AudioToolbox/AudioToolbox.h>
#import <AVFoundation/AVFoundation.h>
#import <CoreAudio/CoreAudioTypes.h>

struct RNWrapOpaque;

@interface RNWrap : NSObject {
struct RNWrapOpaque *_cpp;
}

- (id) initWithAudioUnit:(AudioUnit) au andURL:(CFURLRef) outputFileURL andAudioFileTypeID:(AudioFileTypeID) fileType andAudioStreamBasicDescription: (const AudioStreamBasicDescription) asbd;
- (void) Start;
- (void) Close;
- (void) Stop;

@end

RNWrap.mm

//
//  RNWrap.mm
//  Objective C++ Wrapper Class for CAAudioUnitOutputCapturer.h
//
//  Created by Pier 23/10/12
//  Copyright 2012 DreamUpApps. All rights reserved.
//

#import "RNWrap.h"
#import "CAAudioUnitOutputCapturer.h"

@interface RNWrap ()
@property (nonatomic, readwrite, assign) RNWrapOpaque *cpp;
@end

@implementation RNWrap
@synthesize cpp = _cpp;

struct RNWrapOpaque
{
public:
    RNWrapOpaque(AudioUnit au, CFURLRef outputFileURL, AudioFileTypeID fileType, const AudioStreamBasicDescription format) : outputCapturer(au, outputFileURL, fileType,  format, 0) {}; // note added bus number = 0 at the end
CAAudioUnitOutputCapturer outputCapturer;
};

- (id)initWithAudioUnit:(AudioUnit) au andURL:(CFURLRef) outputFileURL andAudioFileTypeID:(AudioFileTypeID) fileType andAudioStreamBasicDescription: (const AudioStreamBasicDescription) format
{
self = [super init];
if (self != nil)
{
    self.cpp = new RNWrapOpaque(au, outputFileURL, fileType, format);
}
return self;
}

- (void)dealloc
{
delete _cpp;
_cpp = NULL;

//[super dealloc];
}

- (void) Start
{
self.cpp->outputCapturer.Start();
}

- (void) Stop
{
self.cpp->outputCapturer.Stop();
}

- (void) Close
{
self.cpp->outputCapturer.Close();
}

@end

You use it like this in your class.

- (void) captureInAppAudio:(AudioUnit) auToCapture
{
AudioStreamBasicDescription destFormat; 
memset( &destFormat, 0, sizeof(AudioStreamBasicDescription) );
destFormat.mSampleRate = 44100;
destFormat.mFormatID = kAudioFormatLinearPCM;
destFormat.mFormatFlags = ( kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked | kAudioFormatFlagIsBigEndian );
destFormat.mBytesPerPacket = 2;
destFormat.mFramesPerPacket = 1;
destFormat.mBytesPerFrame = 2;
destFormat.mChannelsPerFrame = 1;
destFormat.mBitsPerChannel = 16;

NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];

soundPath = [documentsDirectory stringByAppendingString:@"/recording.caf"] ;
CFURLRef fileurl = CFURLCreateWithFileSystemPath(NULL, (CFStringRef)soundPath, kCFURLPOSIXPathStyle, false);
captor = [[RNWrap alloc] initWithAudioUnit:auToCapture andURL:fileurl andAudioFileTypeID:'caff' andAudioStreamBasicDescription:destFormat];

[captor Start];
}

Hope this helps someone else out there!

Pier.

lppier
  • 1,927
  • 3
  • 24
  • 62