I am writing an Objective-C wrapper for a C++ class. On the OC side I have to take an NSData (read from a json file) get a list out from it and convert that to std::vector< std::string >. Here is my code so far (in an mm file):
NSData *input = [NSData dataWithContentsOfFile:[NSString stringWithUTF8String:"1455469592904_acceldata742283.json"]];
NSError* error;
NSDictionary* jsonArray = [NSJSONSerialization JSONObjectWithData:input options:NSJSONReadingMutableContainers error:&error];
NSMutableArray *accdata = [jsonArray valueForKeyPath:#"data.accelerometer" ];
int size = [accdata count];
vector<string> cppAccdata;
for (int i = 0; i< size; i++){
//~ cppAccdata.push_back();
NSLog(#"%#",[accdata objectAtIndex:i]);
}
The accdata speaking loosely is a list of strings. The json file basically looks like this (with more lines but that's beside the point):
{
"data" : {
"accelerometer" : [
"1455463005.714 -8.311620700836182 -3.969735990142822 -3.737648066711425",
"1455463005.724 -8.256703700256347 -4.017769660949707 -3.680336864471435" ] }}
The output of the code is:
2016-02-15 14:18:16.212 test-OC[10300] 1455463066.241 -7.960421244812011 -4.400093738555908 -3.746177410125732
2016-02-15 14:18:16.212 test-OC[10300] 1455463066.261 -8.019677735900878 -4.377498460388184 -3.634847032928467
2016-02-15 14:18:16.212 test-OC[10300] GSCBufferString
Technically I need to convert [accdata objectAtIndex:i] to an std::string, which I guess would work through a C const char *, but I have not been able to find anything on converting a GSCBufferString into anything. How can I achieve this? There also might be a more elegant way to loop through accdata.
NSArray *accdata = [jsonArray valueForKeyPath:#"data.accelerometer" ];
vector<string> cppAccdata;
for (NSString *str in accdata) {
cppAccdata.push_back(str.UTF8String);
}
Try this:
NSArray *jsonArray;
NSArray *accData = [jsonArray valueForKeyPath:#"data.accelerometer"];
vector<string> cppAccdata;
for (NSString *str in accData){
std::string *accString = new std::string([str UTF8String]);
cppAccdata.push_back(*accString);
}
The two answer proposed were correct in the way of doing the conversion, but the loop syntax didn't compile. This workaround works though:
int size = [accdata count];
vector<string> cppAccdata;
for (int i = 0; i< size; i++){
cppAccdata.push_back([[accdata objectAtIndex:i] UTF8String]);
}
Related
I have a function that gets all contents of directory whether files or directories and I am using contentsOfDirectoryAtPath to collect the content of a directory then I save the names of files/directories into a container called contentsStore which accepts key&value items of UTF-16 string char16_t. look at the following code to make your vision clear:
NSArray *dirContents = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:_dirPath error:nil];
for(unsigned int i= 0; i< [dirContents count]; i++){
if(isDir){
// `contentsStore` is key&value container that accepts utf-16 string (char16_t)
contentsStore.Add([[dirContents objectAtIndex:i] UTF8String], "directory");
} else {
contentsStore.Add([[dirContents objectAtIndex:i] UTF8String], "file");
}
}
Note that I don't post the entire code because it's big but I just added the important parts that related to the problem. Also, I am using Objective-C just as a bridge to achieve my goal to use Cocoa in macOS but the main language that I use is C++, so, the entire code is a combination of C++/Objective-C.
How to make the objectAtIndex method to output the item's content as UTF-16 char16_t?
the following will give you an idea. The [Filemanager defaultmanager] is actually supporting your task. You can convert the path to C-string and convert then into string of char16_t aka basic_string<char16_t>.
NSString *_dirPath = [[NSBundle mainBundle] bundlePath];
NSError *error = nil;
NSFileManager *manager = [[NSFileManager defaultManager] init];
NSArray *dirContents = [manager contentsOfDirectoryAtPath:_dirPath error:&error];
if (!error && dirContents.count ) {
for(unsigned int i = 0; i < [dirContents count]; i++){
NSString *path = [dirContents objectAtIndex:i];
BOOL isDir;
std::string usingkey = "file";
if ([manager fileExistsAtPath:path isDirectory:&isDir] && isDir) {
usingkey = "directory";
}
const char *fileRepresentation = [manager fileSystemRepresentationWithPath:path];
// function declared below..
std::u16string char16string = to_utf16(fileRepresentation);
// and use it to store your C++ storageObject, value&key pair
// don't know of what datatype is usingkey in your source
// just assumed std::string
contentsStore.Add(char16string, usingkey);
}
}
you will have to include the following in your .mm implementation
#include <string>
#include <codecvt>
#include <iostream>
#implementation Yourclassname
//std::u16string is same as basic_string<char16_t>
std::u16string to_utf16( std::string str )
{ return std::wstring_convert< std::codecvt_utf8_utf16<char16_t>, char16_t >{}.from_bytes(str); }
#end
I want to hash a file (using SHA1 at the moment). Here is the function:
static inline __attribute__((always_inline)) NSString *SHA1String(NSData *data) {
uint8_t digest[CC_SHA1_DIGEST_LENGTH];
CC_SHA1(data.bytes, (CC_LONG)data.length, digest);
NSMutableString *output = [NSMutableString stringWithCapacity:CC_SHA1_DIGEST_LENGTH * 2];
for (int i = 0; i < CC_SHA1_DIGEST_LENGTH; i++) {
[output appendFormat:#"%02x", digest[i]];
}
return output;
}
And here are the two different methods of loading the data:
CFURLRef filePath = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, CFSTR("/path/to/file"), kCFURLPOSIXPathStyle, false);
CFDataRef cfFileData = CFURLCreateData(kCFAllocatorDefault, filePath, kCFStringEncodingUTF8, false);
NSString *cfFileHash = SHA1String((__bridge NSData *)cfFileData);
NSData *fileData = [NSData dataWithContentsOfURL:[NSURL fileURLWithPath:#"/path/to/file"]];
NSString *fileHash = SHA1String((__bridge NSData *)fileData);
NSLog(#"Hashes: %# - %#", cfFileHash, fileHash);
The hashes differ and I wonder what is causing this. I'd like to use the CoreFoundation API, but if the file hash differs, that'd be bad.
I use another file manager to view the sha1 of the file and it matches the one from NSData.
Any insights appreciated.
I've got a c++ function that gets a std::map object and convert it to CFMutableDisctionryRef in order to use it on method CFNotificationCenterPostNotification. Here's my implementation :
void IPCNotificationSender::send(const char *identifier, map<const char *, const char *> dict)
{
NSMutableDictionary *myDict = [NSMutableDictionary dictionary];
CFStringRef cfIdentifier = CFStringCreateWithCString(NULL, identifier,
kCFStringEncodingMacRoman);
for (std::map<const char *, const char *>::iterator it=dict.begin(); it!=dict.end(); ++it)
{
NSString *key = [NSString stringWithUTF8String:it->first];
NSString *val = [NSString stringWithUTF8String:it->second];
myDict[key] = key;
}
CFMutableDictionaryRef myCFDict = (CFMutableDictionaryRef)CFBridgingRetain(myDict);
CFNotificationCenterPostNotification(CFNotificationCenterGetDistributedCenter(), cfIdentifier, NULL, myCFDict, TRUE);
CFRelease(myCFDict);
CFRelease(cfIdentifier);
}
However, there seems to be a memory leak in the NSString *key object where it should be released automatically. I've tried to implement the conversion on top of objective-C function type and still got the same results... I tend to believe that the mixture between c++ and objective-C, although valid, causes some issues with objective-c garbage collector.
Where did I go wrong in my implementation ?
thanks
C++ issues:
this map looks bad. it should be map<string, string>
you are passing map by value not by const rerence
Objective C issue:
Based on clues which gives accepted answer I suspect what is there actual problem.
Your C++ code runs continuously without reaching auto release pool. So when you are using Objective C API where auto release pool is involved this objects are not getting released since auto release pool never gets control.
So I would write this like this:
NSString *ConvertToObjC(const string& s)
{
return [NSString stringWithUTF8String: s.c_str()];
}
NSDictionary *ConvertToObjC(const map<string, string>& cppMap)
// here I use templates which do lots of magic, but this is off topic,
{
NSMutableDictionary *result = [NSMutableDictionary dictionaryWithCapacity: cppMap.count()];
for (const auto& x : cppMap)
{
result[ConvertToObjC(x.first)] = ConvertToObjC(x.second);
}
return result;
}
void IPCNotificationSender::send(const string& identifier,
const map<string, string>& cppMap)
{
#autoreleasepool {
auto ident = ConvertToObjC(identifier);
auto myDic = ConvertToObjC(cppMap);
CFNotificationCenterPostNotification(
CFNotificationCenterGetDistributedCenter(),
(CFStringRef)CFBridgingRetain(ident),
NULL,
(CFDictionaryRef)CFBridgingRetain(myDict),
TRUE);
}
}
I have stumbled in the same problem, there seems to be a problematic behaviour of the memory management in shared c++/objective c projects.
The solution was to create objects which you can manually free them.
In your code, try the following:
for (std::map<const char *, const char *>::iterator it=dict.begin(); it!=dict.end(); ++it)
{
CFStringRef key = CFStringCreateWithCString(NULL, it->first,
kCFStringEncodingMacRoman);
CFStringRef val = CFStringCreateWithCString(NULL, it->second,
kCFStringEncodingMacRoman);
myDict[(__bridge NSString * _Nonnull __strong)(key)] = (__bridge NSString * _Nonnull __strong)(val);
CFRelease(key);
CFRelease(val);
}
What is the easiest/optimal way to transform AnsiString into Cyrillic using C++ STL!
in php it would be
<?php
// функция превода текста с кириллицы в траскрипт
function encodestring($st)
{
// Сначала заменяем "односимвольные" фонемы.
$st=strtr($st,"абвгдеёзийклмнопрстуфхъыэ_",
"abvgdeeziyklmnoprstufh'iei");
$st=strtr($st,"АБВГДЕЁЗИЙКЛМНОПРСТУФХЪЫЭ_",
"ABVGDEEZIYKLMNOPRSTUFH'IEI");
// Затем - "многосимвольные".
$st=strtr($st,
array(
"ж"=>"zh", "ц"=>"ts", "ч"=>"ch", "ш"=>"sh",
"щ"=>"shch","ь"=>"", "ю"=>"yu", "я"=>"ya",
"Ж"=>"ZH", "Ц"=>"TS", "Ч"=>"CH", "Ш"=>"SH",
"Щ"=>"SHCH","Ь"=>"", "Ю"=>"YU", "Я"=>"YA",
"ї"=>"i", "Ї"=>"Yi", "є"=>"ie", "Є"=>"Ye"
)
);
// Возвращаем результат.
return $st;
}
?>
I prefer to use STL Containers, need help!
You may use the same code style in C++ also. Look to the std::map container. In newest standard C++11 you are also able to initialize it in a very convinient manner:
map letters =
{{ "f", "ф"},
{ "h", "х"},
//and so on
};
Finally I wrote my own func and it works well! I think it'll get my job done but not so optimized! Tried to use map.find() smth like this but .....
AnsiString Latin2Cyr(AnsiString inStr)
{
int l=inStr.Length();
if (!l) return "";
AnsiString strLat[]={"a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z"};
AnsiString strCyr[]={"а","б","с","д","е","ф","г","х","и","дж","к","л","м","н","о","п","к","р","с","т","у","в","у","кс","й","з"};
AnsiString outStr="";
typedef multimap<AnsiString,AnsiString> mmp;
mmp mStr;
int ln=(sizeof(strLat))/(sizeof(AnsiString));
for (int i = 0; i < ln; i++) mStr.insert(mmp::value_type(strLat[i],strCyr[i]));
AnsiString k=""; mmp::iterator it;
for (int j = 1; j <l+1; j++) {
for ( it=mStr.begin(); it!= mStr.end();++it ){
outStr+=((*it).first==inStr.SubString(j,1))? (*it).second : k ;
}
}
return outStr;
}
any ideas to optimize it?
** This is still unsolved **
I'm trying to call an ObjC/C++ function code from C#. I've done my best to follow different example code, the latest being mostly from:
http://msdn.microsoft.com/en-us/library/ms146631(v=VS.80).aspx
This is for an iPhone/MonoTouch environment, so I'm not sure I've done everything I should. The bytes appear to be ok in the ObjC/C++ function, but the byte array I get back into C# ends up containing 0 0 0 0 0 0 etc.
** Update **
Corrected for loop initializer, and now its giving a EXC_BAD_ACCESS signal on the *returnbytes[i] = bytes[i]; line.
C# code:
[DllImport ("__Internal")]
private static extern int _getjpeg(string url,ref IntPtr thebytes);
void somefunction(string image_id) {
int maxsize = 50000;
byte[] thebytes = new byte[maxsize];
IntPtr byteptr = Marshal.AllocHGlobal(maxsize);
int imagesize = _getjpeg(image_id,ref byteptr);
Debug.Log("Getting _picturesize()... "+ image_id);
int picsize = _picturesize();
Marshal.Copy(byteptr,thebytes,0,picsize);
var texture = new Texture2D(1,1);
string bytedebug = "";
for (int i=5000 ; i < 5020 ; i++)
bytedebug+=thebytes[i] + " ";
Debug.Log("Bytes length is "+imagesize);
Debug.Log("Bytes content is "+bytedebug);
}
C++/ObjC code:
int _getjpeg(const char* url,unsigned char** returnbytes) {
ALAsset* asset = [_pictures objectForKey:[NSString stringWithUTF8String:url]];
if(asset != NULL)
NSLog(#"_getjpeg() found URL: %#",[NSString stringWithUTF8String: url]);
else {
NSLog(#"_getjpeg() could not find URL: %#",[NSString stringWithUTF8String: url]);
return NULL;
}
UIImage *image = [UIImage imageWithCGImage: [asset thumbnail]];
NSData* pictureData = UIImageJPEGRepresentation (image, 1.0);
picturesize = (int)[pictureData length];
unsigned char* bytes = (unsigned char*)[pictureData bytes];
// This test does not give EXC_BAD_ACCESS
*returnbytes[5] = (unsigned int)3;
// updated below initializer in below for loop according to Eikos suggestion
for(int i=0 ; i < picturesize ; i++) {
// below lines gives EXC_BAD_ACCESS
*returnbytes[i] = bytes[i];
}
NSString* debugstr = [NSString string];
for(int i=5000; i < 5020 ; i++) {
unsigned char byteint = bytes[i];
debugstr = [debugstr stringByAppendingString:[NSString stringWithFormat:#"%i ",byteint]];
}
NSLog(#"bytes %s",[debugstr UTF8String]);
return picturesize;
}
Thanks
Keep in mind that the JPGRepresentation is probably not exactly the same as you put into it, so the length may differ.
In
for(int i;i < picturesize;i++) {
// *** Not sure I'm doing this correctly ***
*returnbytes[i] = bytes[i];
}
you forget to initialize i, so it might start with a random value which is bigger than picturesize, so the loop won't run at all.
You want unsigned char*, not **. You are passing a pointer in that is already allocated. A ** is for when you are passing in a pointer to variable that is itself a pointer to data: i.e. when the callee will allocate the memory and the caller wants to know about it.
Just pass in unsigned char* and then use
returnbytes[i] = bytes[i];
Alternatively, allocate in the calee and use an out, not a ref.