2010年11月14日星期日

A trick to avoid Objective-C++ in Cocoa application

I'm working on a project that help me to convert the music file tags encoding from GBK, GB2312, JIS,UTF16 to UTF8~~~ iTunes (or whole mac world) only support UTF8 Encoding~ So to avoid the scrambled text, the music file tag's encoding has to be transcoded. (Actually, not only the music the subtitle of movies also need to be transcoded before used on Mac.)

To support tens of tag formats of music files, I introduce the well known open source library TagLib(http://developer.kde.org/~wheeler/taglib.html), which is well designed and documented C++ library, with C binding. 
I checked out the source code from its SVN and compiled it to a framework(Thanks to Sbooth, he submitted a XCode project structure for TagLib on github, https://github.com/sbooth/AudioFrameworks/tree/master/taglib/)~

I tried include the code into my code, but it fails to compile, it reports a lot error similar to the following:

> #include <string>
TagLib.framework/Headers/TagLib.h:33:18: error: string: No such file or directory

-> namespace TagLib {
TagLib.framework/Headers/TagLib.h:45: error: expected '=', ',', ';', 'asm' or '__attribute__' before 'TagLib'

I search a lot of documents from Apple ( I have to say, contract to MSDN, Apple's document really sucks!!!! ) and the maillist archives (http://cocoadev.com/forums/comments.php?DiscussionID=1286, this mail thread might be helpful, although the guy "mmw" speaks in a disgusting tone).
I realize the problem is that Objective-C is only a extension  of C, so it doesn't support the keyword class and similar thing. So I can just rename the file .m to be .mm, which represents Objective-C++ source file. So XCode knows that and try to compile it as a Objective-C code mixed with C++ code instead of to process it  as pure Objective-C code. It works well in my code.

But the problem continues, I import the TagLib's header files in my .h file, which cause the C++ definition spreads all over the project. I have no option but rename all of my .m files to .mm.
Later, I found a bigger problem, XCode compiles .mm file may be 2x slower than .m file. And only 2 source files were involved C++ code( I wrote a Objective-C wrapper for TagLib fileref and tags, which only expose the features that I needed, it is similar to a façade pattern).
I'm not sure whether if the problem is caused by include TagLib's header file everywhere, and which is not pre-compiled ( maybe I should included it in to the pch definition? I'm no idea~ )
So I wish I can avoid to use .mm extension in most of my code. So I try to expose TagLib types in my .h file, which means I cannot use TagLib types as property or even private fields!
It is somehow difficult, but I still did it!
This is my definition .h file

//

//  MediaFileInfo.h

//  Transcoder

//

//  Created by TimNew on 11/13/10.

//  Copyright 2010 Windy's. All rights reserved.

//


#import <Foundation/Foundation.h>

#import "NSString.TagLibIntegeration.h"


@interface MediaFileInfo : NSObject {

NSString *filePath;

NSString *title;

NSString *album;

NSString *artist;

}


@property (retain,readonly) NSString* filePath;


@property (retain, readonly) NSString *title;

@property (retain, readonly) NSString *album;

@property (retain, readonly) NSString *artist;



- (id) initWithFilePath:(NSString *) file;

- (id) initWithFilePath:(NSString*) file update:(BOOL) update;


- (void) refresh;


@end

You can find that there is no TagLib stuff at all. I moved all import instruction to the .mm file instead of put it in the .h file.
Still now everything should go well. But unfortunately it didn't!
The reason is that I import the NSString.TagLibIntegeration.h file. I just wrote a category to extend NSString, so I easily convert between NSString to TagLib::String.

//                                              

//  NSString.TagLibIntegeration.h

//  Transcoder

//                                                                                           

//  Created by Wen Di on 11/13/10.

//  Copyright 2010 Windy's. All rights reserved.

//


#import <Cocoa/Cocoa.h>


namespace TagLib {

class String;

}


@interface NSString (TagLibIntegeration) 


- (id) initWithTagLibString:(TagLib::String) tagLibString;

- (TagLib::String) ToTagLibString;


@end

I try to introduce TagLig/tstring header to my code, so I try to declare a stub for TagLib::String type~ It works well, but doesn't solve the problem at all. It is obvious that I use the keyword "class"!
So which require every source file imported this header to be .mm file again. 

After I analyze the code, it is very lucky that I found that I only used this two methods only while I need to work the TagLib types directly, which means that except the MediaFileInfo.mm, there is no source file need to import this NSString.TagLibIntegeration header file.
So once again, I moved the import instruction from MediaFileInfo.h to MediaFileInfo.mm.

Now, everything goes well! I only introduced 2 .mm files and solved all problems!

Conclusion:
1. Avoid to import every header files in .h file, only import declaration related .h file, all others can be import in .m or .mm file. 
2. When necessary, a stub definition can be used instead of import specific .h file.
3. Wrap the 3rd party types if possible, which might avoid the 3rd party types spread into our own code, which might increase the dependency of our code to 3rd party's code, which is a potential rick.
4. Always remember, Objective-C is an extension to C, not C++, so all C++ keywords such as class, const, virtual are not available.


TimNew
------------
Release your passion
To Realize your potential

I am a pessimist, I feel I'm living in a world without light.
But I am also a prayer, I believe I’m going towards a world full of sunshine!


Posted via email from 米良的草窝

没有评论:

发表评论