Display std::wstring in Xcode using a Data Formatter
std::wstring
: unsafe, but sometimes necessary
When dealing with cross-platform code, especially Windows, it is not uncommon to encounter wchar_t
and its companion string class, std::wstring
.
Unfortunately, Xcode does not display these types natively. Yes, you can add this file to your .gdbinit
file and then you will be able to type:
pwstring mystring
but what you really want is for the values to show up in Xcode’s variable display, and in tooltips.
What you need is called a Custom Data Formatter.
In this post, I will adapt Apple’s sample wchar_t
data formatter and modify it to display std::wstring
variables. I will also explain where you should put this formatter in Xcode 3.1.
Complete source code is available on bitbucket.org, with a MIT license.
Initial Data Formatter
Xcode Data Formatters are simple bundles that are loaded by Xcode at startup. They are essentially plug-ins to extend Xcode.
Starting with Apple’s own wchar_t Data Formatter sample, we immediately notice that:
- This sample is quite outdated, almost 4 years old (the Release build actually builds only PPC!)
- It won’t work as-is with
std::wstring
since it only contains .c files.
We will update this sample to work with std::wstring
and build it as a 4-way universal binary.
Modernizing the Data Formatter
Switching to C++
Since the data formatter will use std::wstring
, we need to build it using C++. Just rename all the .c files to .cpp and voilà!
Modify CustomDataViews.plist
If you open CustomDataViews.plist, you will see that it has one entry per variable type that the formatter can handle. That entry contains a Data Formatter, much like the one you would type in Xcode itself (for example, to display the name of a notification—of type NSNotification—you can use {(NSString *)[$VAR name]}
).
What is different here is the formatter contains a string that calls back in our custom Data Formatter: for example, wchar_t
corresponds to {(char *)myWCharDataFormatter((wchar_t) $VAR, (int) $ID)}:s
.
Add two entries to that plist, for types wstring
and wstring*
, and replace the callback strings with a new unique string, for instance myWStringDataFormatter
and myWStringPointerDataFormatter
. Watch for capitalization, case matters.
Add new callback functions
Now add the two callback functions you referenced in the plist in myCustomFormatter.cpp
:
#include <string> char * myWStringDataFormatter(std::wstring wstr, int identifier) { size_t bufLen = wstr.size() + 1; return dataformatter_char_for_wchar(wstr.data(), identifier, bufLen); } char * myWStringPointerDataFormatter(std::wstring *wstr, int identifier) { size_t bufLen = wstr->size() + 1; return dataformatter_char_for_wchar(wstr->data(), identifier, bufLen); }
As you can see, we are re-using the preexisting wchar_t
code with our std::wstring
.
Build a 4-way Universal Binary
Double-click the wchardataformatter project icon to bring up the Project Inspector. Select the Release configuration, and click the “Architectures” pop-up menu. Select 32/64 bit universal, as shown below.
Modify the Test Code
Finally, update wcharTest_main.cpp
to add new std::wstring
variables to display in the debugger:
#include <stdlib.h> #include <wchar.h> #include <string> int main(int argc, char *argv[]) { wchar_t c = 'A'; wchar_t b[255]; wchar_t *d = &b[0]; char *a = "a b c d e f g"; mbstowcs(d, a , strlen(a)); std::wstring s = L"wide string"; std::wstring *ps = &s; return 0; }
Switch the target to wCharTest in Xcode, set a breakpoint on the last line, and hit “Build and Go”. Xcode should build the test application and stop in the Debugger. Since you have not installed the formatter, you should not see anything special in the Variable Display window.
Installing the Data Formatter
Build the Data Formatter (Debug or Release). Locate the bundle by control-clicking on the “Products / wcharDataFormatter.bundle” in the Xcode project.
Drag the bundle to this folder: ~/Library/Application\ Support/Developer/Shared/Xcode/CustomDataViews
, creating intermediate folders along the way if they do not exist.
You can remove the ~ to install for all users (not just you), and you can also replace “Shared” in the path with “3.0” or “3.1” if you only wanted to install the formatter for Xcode 3.0 or 3.1.
Testing the Formatter
Build the wcharTest target, and set a breakpoint in the main() function of wcharTest_main.cpp. You can step over each line, and you should see your data formatter being called and displaying in Xcode’s Variable Display, as well as in tooltips (Xcode 3.1 and later).
thank you Philippe, a really useful tip:)
Wow – this is making my coding life so much easier – thank you
I played around with this a bit because I wanted to see the actual (non ansi) unicode characters in Xcode.
I ended up with the code blow. I’m not yet sure if reusing the CFStringRef work, otherwise one would have to return local versions and let them leak.
#include
CFStringRef globalRc= 0;
// Read the DataFormatterPlugin.h header for more information on custom data formatters and allocators requirements
CFStringRef
cfstring_for_wchars(const wchar_t *pwchars, size_t charCount, int ident)
{
UniChar unibuf[charCount];
for (size_t i= 0; i<charCount && pwchars[i]!=0; i++) {
unibuf[i]= pwchars[i];
}
if (globalRc) { // Xcode will make a copy of the value, so we can release the last handle
::CFRelease(globalRc);
}
globalRc= ::CFStringCreateWithCharacters(kCFAllocatorDefault, unibuf, charCount);
return globalRc;
}
CFStringRef
see_wchar_t(wchar_t thewchar, int ident)
{
return cfstring_for_wchars(&thewchar, 1, ident);
}
CFStringRef
see_wchar_t_p(wchar_t *pwchars, int ident)
{
return cfstring_for_wchars(pwchars, wcslen(pwchars), ident);
}
I’m not quite certain of what you are trying to accomplish, I mean Xcode does not display wchar_t characters in its debugger display, that’s why they have to be converted to char.
Regarding your CFString issue, I would create a CFStringRef and call my functions “create_wchar_t()” instead of “see_wchar_t”, which would make it obvious to the caller that they now “own” the CFString and must CFRelease it themselves.
Your global will blow up in any multithreaded environment.
What I’m trying to accomplish is to actually see unicode characters beyond the Latin glyphs. Returning an CFStringRef which is made from UniChars will for example display Aisan glyphs while the char * version (in my Xcode at least) does only show hex values for non latin characters (the printf works because the console resolves utf8). I.e. I’m passing back CFStrings to Xcode (to be then shown with :s) for full unicode display.
And yes, I know that the global CFString is a kludge. I have yet to try a cocoa version with autorelease NSString objects instead.
E.g. make a wchar_t u[255]= { 0xac00, ‘b’, ‘c’, 0 }; in the test app and see how it outputs.
The formatter for wchar_t [255] would be
{see_wchar_t_p((wchar_t *) $VAR, (int) $ID)}:s
I have one with pthread protection now. It’s too fiddly to post into this window, if you (or anyone else) is interested, send me an email at nicholaz at blueflash dot cc.
Otherwise thanks for the post here, it was a very helpful starting point for my own version.
should i ignore those warnings?
[WARN]warning: no rule to process file ‘$(PROJECT_DIR)/myCustomFormatter.ccpp’ of type text for architecture x86_64
[WARN]warning: no rule to process file ‘$(PROJECT_DIR)/myCustomFormatter.ccpp’ of type text for architecture i386
[WARN]warning: no rule to process file ‘$(PROJECT_DIR)/myCustomFormatter.ccpp’ of type text for architecture ppc
@rpv
For C++ you want “,cpp” files. Your files are “.ccpp”, which Xcode doesn’t know how to compile.
Has anyone ever gotten this to work in an iphone project? Works fine for me within wcharTest in the wchardataformatter project, but in my iphone projects, I see only blank summary fields where I should see the formatted wchar_t data. Any ideas appreciated.