Qt does a great job at abstracting out platform-specific features into platform-independent C++ APIs. However, sometimes you still need to write platform-specific code for features that are not in Qt (e.g. to access the platform’s address book), or to access platform-specific applications (e.g. iTunes) or libraries (e.g. Sparkle). On Mac OS X, almost all interfaces are offered through the Cocoa Objective-C interface, and the interfaces that are written in C++ have been deprecated and will disappear soon in favor of Cocoa. Although the language of Cocoa is different from Qt’s, Qt and GCC make it very easy to call these interfaces from within your application. In this post, I will show how this can be done by making an auto-updating application using Sparkle.
To create our auto-updating application, we first lay down the auto-updater interface in a (pure) virtual class:
class AutoUpdater {
public:
virtual void checkForUpdates() = 0;
};
The Sparkle implementation of this interface has the following C++ header:
class SparkleAutoUpdater : public AutoUpdater {
public:
SparkleAutoUpdater(const QString& url);
~SparkleAutoUpdater();
virtual void checkForUpdates();
private:
class Private;
Private* d;
};
Note that I am using the Pimpl idiom to hide Cocoa-specific internals from the API into a private internal class.
The actual impementation of the SparkleUpdater needs to call Sparkle’s Objective-C API. However, we cannot write the implementation in Objective-C, since the header for our updater (which will be included from within our application) is written in C++. Luckily, GCC’s Objective-C++ extension allows you to mix Objective-C with C++ without any problem. So, we create an Objective-C++ implementation of our updater, and put it in a .mm file:
class SparkleAutoUpdater::Private {
public:
SUUpdater* updater;
};
SparkleAutoUpdater::SparkleAutoUpdater(const QString& aUrl) {
d = new Private;
d->updater = [[SUUpdater sharedUpdater] retain];
NSURL* url = [NSURL URLWithString:
[NSString stringWithUTF8String: aUrl.toUtf8().data()]];
[d->updater setFeedURL: url];
}
SparkleAutoUpdater::~SparkleAutoUpdater() {
[d->updater release];
delete d;
}
void SparkleAutoUpdater::checkForUpdates() {
[d->updater checkForUpdatesInBackground];
}
All we do here is create an SUUpdater (Sparkle’s updater class), set it up, and activate it in our checkForUpdates() method.
To integrate Objective-C++ classes into a Qt project, qmake provides the OBJECTIVE_SOURCES variable. Knowing this, we add our new classes to our qmake project file:
HEADERS += AutoUpdater.h
SOURCES += AutoUpdater.cpp
mac {
HEADERS += SparkleAutoUpdater.h
OBJECTIVE_SOURCES += SparkleAutoUpdater.mm
LIBS += -framework Sparkle
}
Before we integrate the updater into our application, there is still one detail we have to take care of: if you write Objective-C code that creates objects, you need to instantiate an NSAutoReleasePool to enable Cocoa’s memory management. Although we could do this in SparkleAutoUpdater, we’re going to put this in a separate initializer class, so we can share this with other Objective-C++ implementations in our application. The body of this class looks as follows:
class CocoaInitializer::Private {
public:
NSAutoreleasePool* autoReleasePool;
};
CocoaInitializer::CocoaInitializer() {
d = new CocoaInitializer::Private();
NSApplicationLoad();
d->autoReleasePool = [[NSAutoreleasePool alloc] init];
}
CocoaInitializer::~CocoaInitializer() {
[d->autoReleasePool release];
delete d;
}
Now all that’s left to do is integrate all this into our application:
int main(int argc, char* argv[]) { ... AutoUpdater* updater = 0; #ifdef Q_WS_MAC CocoaInitializer cocoaInitiarizer; updater = new SparkleUpdater("http://el-tramo.be/myapp/appcast.xml"); #endif ... if (updater) { updater->checkForUpdates(); } ... }
That’s it! If you want to try this out yourself, just download the sources of this mini-project, build them, and off you go. Don’t forget to remove ~/Library/Preferences/be.el-tramo.mixing-cocoa-and-qt.plist to clear Sparkle’s state cache.
Tags: C++, Cocoa, GCC, Mac OS X, Objective-C, Programming, Qt, Sparkle
Interesting — but could you tell me how to tell qmake where the sparke framework I want it to use is?
@Boudewijn: QMake should be looking in the standard place(s) for frameworks, which is /Library/Frameworks. If you want to add a different path, you could do this by adding ‘QMAKE_CXXFLAGS += -F/your/path/here’
hi,
i have qt3 app , i wish to use sparkle as described here.
what version of sparkle and qt can i use ? can have a small xcode example here ?
may be qmake QMAKESPEC=macx-pbuilder && qmake mixing-cocoa-and-qt.pro
Only this one worked for me:
QMAKE_CFLAGS += -F
Is it also possible to construct the complete UI of an application in Cocoa and use Qt for the logic ?
@Rens Technically, it is. I’m currently working on a project that has a small UI, written in Cocoa, but using C++ for all its logic. Maybe I’ll do a blog post about it one of these days to show the general idea and (generic C++) utility classes I used.
That said, I use pure C++ logic (with Boost), not Qt. The hard part for using Qt for your logic will probably be integrating the Cocoa event loop with the Qt event loop. It’s not unsolvable, but it’s not trivial either (although maybe Qt provides something to make it easier).
I don’t suppose you’re still checking this thread, Remko, but if you are I am trying to do something very similar. I am trying to display a live video feed in a Qt application from a webcam connected to the computer (which is a Mac). It seems so me that the QTKit capture api is the best way to do this but I am struggling to get anything to work, including your demo. Whether it’s a problem with Qt 4.6 I’m not sure. The errors I get with my own code look like they’re caused by the same problem… whatever it is. Any help would be greatly appreciated.
The pastie link below has the output when trying to compile your source as is. The sparkle framework is in place and is found by the compiler so it’s not that…
http://pastie.org/781365
any ideas?
Michael
@Michael I check every thread on my blog all the time (or at least, have e-mails telling me someone said something
It seems my QMake .pro file was missing a framework: just add “-framework AppKit” to the LIBS varable. The sources should be fixed now.
Thank you for such a quick and useful response, adding that framework works a treat
I don’t suppose you’ve ever tried to integrate a webcam into a Qt application have you? I don’t expect you to have, but it would save me an awful lot of time!
Thanks again,
Michael
@Michael I haven’t, but Psi’s latest release has webcam support in it, and it’s Qt based. It does use GStreamer though, which probably is more pain than you want.
Hi Remko,
First thanks for your post as it helped me a lot!
But i have a big problem with it.
In your example you use checkForUpdatesInBackground which works great.
Now if you try to use checkForUpdates(because you want the checking gui) then you get a crash every time! I use QT for Cocoa. Happens on 4.6, 4.6.2 and 4.7.0.preview
2010-03-25 17:30:15.046 mixing-cocoa-and-qt[66080:903] -[SUUpdater checkForUpdates]: unrecognized selector sent to instance 0×1011ad970
2010-03-25 17:30:15.048 mixing-cocoa-and-qt[66080:903] An uncaught exception was raised
2010-03-25 17:30:15.076 mixing-cocoa-and-qt[66080:903] -[SUUpdater checkForUpdates]: unrecognized selector sent to instance 0×1011ad970
2010-03-25 17:30:15.134 mixing-cocoa-and-qt[66080:903] *** Terminating app due to uncaught exception ‘NSInvalidArgumentException’, reason: ‘-[SUUpdater checkForUpdates]: unrecognized selector sent to instance 0×1011ad970′
*** Call stack at first throw:
(
0 CoreFoundation 0×00007fff80283444 __exceptionPreprocess + 180
1 libobjc.A.dylib 0×00007fff857d10f3 objc_exception_throw + 45
2 CoreFoundation 0×00007fff802dc1c0 +[NSObject(NSObject) doesNotRecognizeSelector:] + 0
3 CoreFoundation 0×00007fff8025608f ___forwarding___ + 751
4 CoreFoundation 0×00007fff802521d8 _CF_forwarding_prep_0 + 232
5 mixing-cocoa-and-qt 0×000000010000546b main + 251
6 mixing-cocoa-and-qt 0×0000000100004b32 _start + 224
7 mixing-cocoa-and-qt 0×0000000100004a51 start + 33
8 ??? 0×0000000000000001 0×0 + 1
)
terminate called after throwing an instance of ‘NSException’
The program has unexpectedly finished.
Any help?
EDIT: GOT IT!!!!! didnt see that the checkForUpdates function needed an argument
Sorry for the trouble
Finally got everything working and it s great.
Now i face one last problem. is there a way to tell the AutoUpdater to use another language?
My Qt application can be in multiple language not related to the system language. But the AutoUpdater is always in English and i dont know how to change it.
Thanks
Hi,
I want to know can I use the same code for symbian S 60 based device for updating application?
Thanks
@Ravi No, AFAIK Sparkle only has a Mac OS X version. And more importantly, this post is about Cocoa, which isn’t used on Symbian.
Thanks
for quick info.