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!
Related
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'm working with libuv (https://github.com/joyent/libuv) and nodejs v12. I I write native module, in this module i have struct:
struct Work_req
{
const char *data;
size_t data_length;
Isolate* isolate;
unsigned int callback_id;
Persistent<Function> callback;
};
Then i try to pass this structure to work:
Work_req* request = new Work_req;
request->data = buf->base;
request->data_length = (size_t)nread;
request->isolate = env->isolate();
request->callback_id = callback_id->ToNumber()->Value();
uv_work_t *req = new uv_work_t;
req->data = request;
uv_queue_work(env->event_loop(), req, findCallback, after_findCallback);
In the end of this code i passed work to loop and all is ok.
But when i'm trying to read data from uv_work_t *req in findCallback function, i get strange symbols there:
Work_req *s = ((struct Work_req*)req->data);
printf(s>data); //print char array from structure here
I see something like this:
����g�Kack_id":1,"error":null,"response":{"type":"test"}}�����
How i can fix it?
Thanks a lot.
I have a function like this
void UnitTestWorker::constructTestPayload(QByteArray &payload)
{
QString List = "127.0.0.1";
unsigned short Port = 12344;
unsigned int RequestId = 1;
memcpy(payload.data(),reinterpret_cast<char*>Port,sizeof(Port));
memcpy(payload.data()+sizeof(Port),reinterpret_cast<char*>RequestId ,sizeof(RequestId ));
}
But I am getting access violation error, it seems like I can't do something like reinterpret_cast<char*>Port or reinterpret_cast<char*>RequestId.
You have to ensure that QByteArray &payload has a sufficient size to receive the data you byte copy to it:
if (payload.size()<sizeof(Port)+sizeof(RequestId))
throw exception ("Ouch !! payload too small");
memcpy(payload.data(),reinterpret_cast<char*>(&Port),sizeof(Port));
memcpy(payload.data()+sizeof(Port),reinterpret_cast<char*>(&RequestId) ,sizeof(RequestId ));
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.
** 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.