I have a new question here! As the real problem was not in the C++ conversion but rather that I need to convert the returned string data bytes into a CGImageRef. Anybody know how to do that please go to that link to answer the follow on to this question.
Thank you.
OK. Instead of muddying the question with protobuf stuff, I have simplified my test method to simulate the call that would be made to the protobuf stuff.
This test method does the following two parts. Part 1 takes a UIImage and converts it into a std::string.
take a UIImage
get the NSData from it
convert the data to unsigned char *
stuff the unsigned char * into a std::string
The string is what we would receive from the protobuf call. Part 2 takes the data from the string and converts it back into the NSData format to populate a UIImage. Following are the steps to do that:
convert the std::string to char array
convert the char array to a const char *
put the char * into NSData
return NSData
- (NSData *)testProcessedImage:(UIImage *)processedImage
{
// UIImage to unsigned char *
CGImageRef imageRef = processedImage.CGImage;
NSData *data = (NSData *) CFBridgingRelease(CGDataProviderCopyData(CGImageGetDataProvider(imageRef)));
unsigned char *pixels = (unsigned char *)[data bytes];
unsigned long size = [data length];
// ***************************************************************************
// This is where we would call transmit and receive the bytes in a std::string
// ***************************************************************************
// unsigned char * to string
std::string byteString(pixels, pixels + size);
// string to char array to const char *
char myArray[byteString.size()+1];//as 1 char space for null is also required
strcpy(myArray, byteString.c_str());
const char *bytes = (const char *)myArray;
// put byte array back into NSData format
NSUInteger usize = byteString.length();
data = [NSData dataWithBytes:(const void *)bytes length:sizeof(unsigned char)*usize];
NSLog(#"examine data");
return data;
}
The is the code for when the data is returned:
NSData *data = [self.messageCommand testProcessedImage:processedImage];
// But when I try to alloc init a UIImage with the data, the image is nil
UIImage *image = [[UIImage alloc] initWithData:data];
NSLog(#"examine image");
Everything seems to go as planned until I try to create the UIImage with the data. Alloc initing the UIImage with that data returns nil. There must be some type of conversion that will make this work.
OK, so the problem is most likely with the repeated conversions between Objective-C, C and C++ data structures. Overall, you need to make sure to initialize the string as a byte array rather than a textual C string, and you want to get back the raw bytes without a null terminator. I think this will preserve the data correctly:
- (void)onDataReceived:(NSNotification *)note {
if ([note.name isEqualToString:#"DataReceived"]) {
NSDictionary *userData = note.userInfo;
NSData *imageData = [userData objectForKey:#"ImageData"];
// Note the two-argument string constructor -- this is necessary for non-textual data!
std::string byteString = std::string(static_cast<const char*>([imageData bytes]), imageData.length);
// We get the image back as a std::string
std::string imageStr = [self.message parseMessage:byteString ofSize:byteString.size()];
NSLog(#"examine imageStr");
// We get the data from the std::string
char *imageCStr = new char[imageStr.size()];
imageStr.copy(imageCStr, imageStr.size());
NSData *data = [NSData dataWithBytes:imageCStr length:imageStr.size()];
delete[] imageCStr;
// But when I try to alloc init a UIImage with the data, the image is nil
UIImage *image = [[UIImage alloc] initWithData:data];
NSLog(#"examine image");
}
}
I tried the answer. There were some minor changes I needed to make to get rid of errors. Also, I had changed some variable names to minimize confusion. This is still returning nil for the UIImage.
- (void)onObjectReceived:(NSNotification *)note {
if ([note.name isEqualToString:#"ObjectReceived"]) {
NSDictionary *userData = note.userInfo;
NSData *objectData = [userData objectForKey:#"ObjectData"];
// Added this because bytes is used below. Or did you mean something else?
const char *bytes = (const char *)[objectData bytes];
// Note the two-argument string constructor -- this is necessary for non-textual data!
std::string byteString = std::string(static_cast<const char*>(bytes), objectData.length);
// This is an out parameter in the parseMessage method.
long unsigned int *msgSize = (long unsigned *)malloc(sizeof(long unsigned int));
// We get the image back as a std::string
std::string imageStr = [self.message parseMessage:byteString outMsgSize:msgSize];
NSLog(#"examine imageStr");
// We get the data from the std::string
char *imageCStr = new char[imageStr.size()];
imageStr.copy(imageCStr, imageStr.size());
NSData *data = [NSData dataWithBytes:imageCStr length:imageStr.size()];
delete[] imageCStr;
// But when I try to alloc init a UIImage with the data, the image is nil
UIImage *image = [[UIImage alloc] initWithData:data];
NSLog(#"examine image");
}
}
I tried removing everything in between and it worked. Here's the code:
- (NSData *)testProcessedImage:(UIImage *)processedImage
{
// UIImage to unsigned char *
CGImageRef imageRef = processedImage.CGImage;
NSData *data1 = (NSData *) CFBridgingRelease(CGDataProviderCopyData(CGImageGetDataProvider(imageRef)));
UIImage *image = [UIImage imageWithCGImage:imageRef];
This tells me that the NSData dataWithBytes is not going to work, because I am using a CGImageRef. My image is raw data coming from the camera (not a PNG or JPEG).
I found this answer on SO that was helpful. The asker even posted this comment, "It looks quite simple to wrap (the) data in a CFData object, and then CGDataProviderCreateWithCFData."
I found another answer on SO that was also helpful. It shows how to create a CFDataRef using the string.
There is a lot of helpful information out there but still not finding what I need. I'm going to ask another question and reference it back here when I get an answer.
Thank you.
Related
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 have the following code that will not compile because XCode won't let me cast a NSArray element into a pointer in my C++ code.
The error given by XCode is: Assigning to 'UInt8 *' from incompatible type 'id'.
How am I supposed to pass an array of type [UnsafeMutablePointer<UInt8>] from Swift to Objective-C++ ?
Thank you in advance
objcfunc.h
+ (void) call: (NSArray *) arr;
objcfunc.mm
+ (void) call: (NSArray *) arr {
UInt8 *buffer;
buffer = (UInt8 *) arr[0]; // doesn't work, XCode throws an error
unsigned char *image;
image = (unsigned char *) buffer;
processImage(image); // C++ function
}
Swift
var arr: [UnsafeMutablePointer<UInt8>] = []
arr.append(someImage)
objcfunc.call(swiftArray: arr)
But if I don't use an array and directly pass the pointer, the code works fine:
objcfunc.h
+ (void) callSingle: (UInt8 *) buf;
objcfunc.mm
+(void) callSingle: (UInt8 *) buf {
unsigned char *image;
image = (unsigned char *) buf; // works fine
processImage(image);
}
Swift
let x: UnsafeMutablePointer<UInt8> buf;
// initialize buf
objcfunc.callSingle(buf);
NSArray is an array of Objective-C objects. So, you need to pass an array of instances of types that are bridged to Objective-C types. I'm not sure that Swift's UnsafeMutablePointer struct is bridged.
Because in this case you are passing an array of image buffers (if I correctly understand), you might want to consider using NSData or Data, rather than UnsafeMutablePointer<UInt8> for each image buffer. These types are specifically intended for dealing with an array of bytes, which is what an image buffer is; see
https://developer.apple.com/reference/foundation/nsdata and
https://developer.apple.com/reference/foundation/data
Here is a contrived example of how this could be done using Data and NSData:
Objective-C implementation:
#implementation MyObjC
+ (void) call: (NSArray * ) arr {
NSData * data1 = arr[0];
UInt8 * bytes1 = (UInt8 *)data1.bytes;
bytes1[0] = 222;
}
#end
Swift:
var arr: [UnsafeMutablePointer<UInt8>] = []
// This is just an example; I'm sure that actual initialization of someImage is more sophisticated.
var someImage = UnsafeMutablePointer<UInt8>.allocate(capacity: 3)
someImage[0] = 1
someImage[1] = 12
someImage[2] = 123
// Create a Data instance; we need to know the size of the image buffer.
var data = Data(bytesNoCopy: someImage, count: 3, deallocator: .none)
var arrData = [data] // For demonstration purposes, this is just a single element array
MyObjC.call(arrData) // You may need to also pass an array of image buffer sizes.
print("After the call: \(someImage[0])")
I am attempting to store std::vector to NSData and back directly. My first attempt I converted each point to an NSValue and stored them with NSKeyedUnarchiver which seems terribly inefficient. My test dataset required 64MB of human readable text (with NSKeyedUnarchiver), versus converting each std:vector to NSData the resulting stored files is a much more reasonable 896kb. I am doing as follows to store the data:
typedef std::vector<CGPoint> CGContour;
typedef std::vector<std::vector<CGPoint>> CGContours;
static CGContours contoursVector;
contoursVector = CGContours(1024); //Populated with CGContours that are populated with CGPoints datatypes above
//doing the following in a for loop, just showing record 0 for brevity
NSData *contourData([[NSData alloc]
initWithBytesNoCopy: contoursVector[0].data()
length: contoursVector[0].size()
freeWhenDone:false]);
I am able to retrieve the buffer:
const void *buffer = [contourData bytes];
size_t len = [contourData length];
However, I am unable to figure out how to populate a std::vector with the const void buffer pointer. I have tried using every possible pointer and dereference combination I can think of - the only thing that I can get to compile is this:
contoursVector[0] = *(CGContour *)[contourData bytes];
If I inspect the vector of CGPoints they are 0,0, so clearly something is not right.
EDIT: after implementing the suggested answer, sometimes it works, other times I get EXC_BAD_ACCESS. Here is the relevant back trace:
* thread #17: tid = 0x11bf7d4, 0x0000000111607551 libsystem_platform.dylib`_platform_memmove$VARIANT$Ivybridge + 49, queue = 'NSOperationQueue 0x7fa298f51000 :: NSOperation 0x7fa29f3251f0 (QOS: UTILITY)', stop reason = EXC_BAD_ACCESS (code=1, address=0x126e27000)
frame #0: 0x0000000111607551 libsystem_platform.dylib`_platform_memmove$VARIANT$Ivybridge + 49
frame #1: 0x000000010d01890f Foundation`NSCopyMemoryPages + 57
frame #2: 0x000000010cf9b737 Foundation`_NSDataCreateVMDispatchData + 103
frame #3: 0x000000010cf99cf2 Foundation`-[_NSPlaceholderData initWithBytes:length:copy:deallocator:] + 230
frame #4: 0x000000010cfa5902 Foundation`-[NSData(NSData) initWithBytes:length:] + 37
* frame #5: 0x000000010cfeabfb Foundation`+[NSData(NSData) dataWithBytes:length:] + 54
frame #6: 0x000000010c5c998a TDTPhotoLib`storePointData() + 682 at TDTContourImage.mm:562
Odd thing is both the contours and the contour being converted to data both look valid in the debugger and the issue seems to be intermittent (sometimes it works other times it doesn't but can't tell what if anything may be different)
EDIT 2:
I am able to iterate over every point, but it crashes on the NSData line.
NSMutableArray<NSData *> *groupedPointsArrayMain = [NSMutableArray new];
for(const CGContour &contour : contoursVector)
{
if (contour.size() > 0) {
// I am able to iterate over every point and store them this way
NSMutableArray *contourPoints = [NSMutableArray arrayWithCapacity:contour.size()];
for(const CGPoint &point : contour)
{
[contourPoints addObject:[NSValue valueWithCGPoint:point]];
}
//When it crashes, it will crash on this line
//despite it successfully walking over each point
//in the code directly above
NSData *data = [NSData dataWithBytes: contour.data()
length: (contour.size() * cgContourSize)];
[groupedPointsArrayMain addObject:data];
}
}
Something like this should do the trick. Please note I didn't try to compile this code since I'm on Linux atm.
typedef std::vector<CGPoint> CGContour;
typedef std::vector<CGContour> CGContours;
const size_t contourSize = sizeof(CGContour);
NSMutableArray<NSData *> *datas = [NSMutableArray new];
{ // store
CGContours contours(1024);
for(const CGContour &contour : contours)
{
NSData *data = [NSData dataWithBytes: contour.data()
length: contour.size() * contourSize];
[datas addObject:data]
}
}
{ // restore
CGContours contours;
for(NSData *data in datas)
{
const size_t count = [data length] / contourSize;
CGPoint *first = (CGPoint *)[data bytes];
CGPoint *last = first + count;
contours.emplace_back(first, last);
}
}
I try to send an NSArray via GameKit in form of a C Struct, the struct looks like this:
typedef struct {
Message message;
unsigned int numBytes;
Byte* bytes;
} MessageBlock;
Here I try to get the bytes of the array and put them into the bytes variable:
- (void)sendBlock:(NSMutableArray*)arrayBlocks {
MessageBlock messageBlock;
messageBlock.message.messageType = kMessageTypeBlock;
NSData* arrayData = [NSKeyedArchiver archivedDataWithRootObject:arrayBlocks];
messageBlock.numBytes = sizeof(arrayData);
[arrayData getBytes:&messageBlock.bytes length:messageBlock.numBytes];
NSData *data = [NSData dataWithBytes:&messageBlock
length:sizeof(MessageBlock)];
[self sendData:data];
}
Soo sending works, but when I try to retrieve the array like this:
else if (message->messageType == kMessageTypeBlock)
{
MessageBlock *messageBlock = (MessageBlock*)[data bytes];
NSMutableArray* arrayBlocks = [NSKeyedUnarchiver unarchiveObjectWithData:[NSData dataWithBytes:messageBlock->bytes length:messageBlock->numBytes]];
[self.delegate insertBlock:arrayBlocks];
}
The code breaks with an EXC_BAD_ACCESS exception. My guess: I am just sending pointers and not the actual bytes :/
So my question: How do I get the bytes of NSData into the bytes variable of the struct?
Thanks for your help!
I have a sample project here on github where I created a c++ wrapper class for an external C++ library that I want to use in Objective-C.
I don't understand why my returned pointers are sometimes correct and sometimes wrong. Here's sample output:
Test Data = 43343008
In Compress 43343008
Returned Value = 43343008
Casted Value = 43343008
Test Data = 2239023
In Compress 2239023
Returned Value = 2239023
Casted Value = 2239023
Test Data = 29459973
In Compress 29459973
Returned Value = 29459973
Casted Value = l.remote
Test Data = 64019670
In Compress 64019670
Returned Value =
Casted Value = stem.syslog.master
In the above output you can see that the 1st and 2nd click of the button outputs the results I was expecting. In each of the other clicks either the returned value or casted value are invalid. I'm assuming this is because my pointer is pointing to an address I wasn't expecting. when running the app multiple times, any button click could be right or wrong.
I also tried with a single thread but experienced similar results.
The complete code is on github but here are the important bits.
ViewController.m
#import "ViewController.h"
extern const char * CompressCodeData(const char * strToCompress);
#implementation ViewController
...
// IBAction on the button
- (IBAction)testNow:(id)sender
{
[self performSelectorInBackground:#selector(analyze) withObject:nil];
}
- (void)analyze
{
#synchronized(self) {
const char *testData = [[NSString stringWithFormat:#"%d",
(int)(arc4random() % 100000000)] UTF8String];
NSLog(#"Test Data = %s", testData);
const char *compressed = CompressCodeData(testData);
NSLog(#"Returned Value = %s", compressed);
NSString *casted = [NSString stringWithCString:compressed
encoding:NSASCIIStringEncoding];
NSLog(#"Casted Value = %#\n\n", casted);
}
}
#end
SampleWrapper.cpp
#include <iostream>
#include <string.h>
#include <CoreFoundation/CoreFoundation.h>
using namespace std;
extern "C"
{
extern void NSLog(CFStringRef format, ...);
/**
* This function simply wraps a library function so that
* it can be used in objective-c.
*/
const char * CompressCodeData(const char * strToCompress)
{
const string s(strToCompress);
// Omitted call to static method in c++ library
// to simplify this test case.
//const char *result = SomeStaticLibraryFunction(s);
const char *result = s.c_str();
NSLog(CFSTR("In Compress %s"), result);
return result;
}
}
You are returning a pointer to at object that has been deallocated.
const string s(strToCompress);
…
const char *result = s.c_str();
NSLog(CFSTR("In Compress %s"), result);
return result;
s does not exist after CompressCodeData() function is over, so the pointer to it's internal memory is invalid.
You could allocate a chunk of memory to hold the response, but it would be up to the caller to release it.
char *compressed = CompressCodeData(testData);
NSLog(#"Returned Value = %s", compressed);
NSString *casted = [NSString stringWithCString:compressed
encoding:NSASCIIStringEncoding];
free(compressed);
NSLog(#"Casted Value = %#\n\n", casted);
…
const char * CompressCodeData(const char * strToCompress)
…
char *result = strdup(s.c_str());
Another solution is to pass in the memory to store the data into.
char compressed[2048]; // Or whatever!
CompressCodeData(testData, compressed, sizeof(compressed));
NSLog(#"Returned Value = %s", compressed);
NSString *casted = [NSString stringWithCString:compressed
encoding:NSASCIIStringEncoding];
NSLog(#"Casted Value = %#\n\n", casted);
…
void CompressCodeData(const char * strToCompress, char *result, size_t size)
…
s.copy(result, size - 1);
result[s.length() < size ? s.length() : size-1] = '\0';