Objective-C integrating C Library and passing String value - ARC Error - c++

I'm trying to integrate a 3rd party C library into my project, I've never done this before and experiencing a problem with something that should be so simple, passing a string value to the C function.
Below is my code and the line that is failing generates the following error:
'Implicit conversion of an Objective-C pointer to 'const char *' is disallowed with ARC'
My Code:
NSString *myMapCode = #"GBR H4J.XLL";
double lat = 0.0;
double lng = 0.0;
returnValue = mc2coord(&lat, &lng, "GBR H4J.XLL", 0); // This works perfectly
returnValue = mc2coord(&lat, &lng, myMapCode, 0); // This is my Problem code
Thanks for your help.

Here you pass an NSString, not a C string:
returnValue = mc2coord(&lat, &lng, myMapCode, 0);
If that function expects a C string, you can either change
NSString *myMapCode = #"GBR H4J.XLL";
to
const char *myMapCode = "GBR H4J.XLL";
or convert the NSString to a C string:
NSString *myMapCode = #"GBR H4J.XLL";
returnValue = mc2coord(&lat, &lng, [myMapCode UTF8String], 0);

The mc2coordfunction does not take an NSString object, but rather a C string, or const char *. Above, don't save myMapCode as an NSString but rather the C string and you will be fine.

Related

UIImage with NSData initWithData returns nil

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.

UTF-8 to UCS-2 conversion with icu library

I'm currently working on and hitting an issue with converting a UTF-8 string to a UCS-2 string with the icu library. There are several number of ways to do this in the library, but so far none of them seem to be working, but considering the popularity of this library I'm under the assumption that I'm doing something wrong.
First off is the common code. In all cases I'm creating and passing a string on an object, but until it reaches the conversion steps there is no manipulation.
The currently utf-8 string being used is simply "ĩ".
For the sake of simplicity I'll represent the string being used as uniString in this code
UErrorCode resultCode = U_ZERO_ERROR;
UConverter* m_pConv = ucnv_open("ISO-8859-1", &resultCode);
// Change the callback to error out instead of the default
const void* oldContext;
UConverterFromUCallback oldFromAction;
UConverterToUCallback oldToAction;
ucnv_setFromUCallBack(m_pConv, UCNV_FROM_U_CALLBACK_STOP, NULL, &oldFromAction, &oldContext, &resultCode);
ucnv_setToUCallBack(m_pConv, UCNV_TO_U_CALLBACK_STOP, NULL, &oldToAction, &oldContext, &resultCode);
int32_t outputLength = 0;
int bodySize = uniString.length();
int targetSize = bodySize * 4;
char* target = new char[targetSize];
printf("Body: %s\n", uniString.c_str());
if (U_SUCCESS(resultCode))
{
// outputLength = ucnv_convert("ISO-8859-1", "UTF-8", target, targetSize, uniString.c_str(), bodySize, &resultCode);
outputLength = ucnv_fromAlgorithmic(m_pConv, UCNV_UTF8, target, targetSize, uniString.c_str(),
uniString.length(), &resultCode);
ucnv_close(m_pConv);
}
printf("ISO-8859-1 DGF just tried to convert '%s' to '%s' with error '%i' and length '%i'", uniString.c_str(),
outputLength ? target : "invalid_char", resultCode, outputLength);
if (resultCode == U_INVALID_CHAR_FOUND || resultCode == U_ILLEGAL_CHAR_FOUND || resultCode == U_TRUNCATED_CHAR_FOUND)
{
if (resultCode == U_INVALID_CHAR_FOUND)
{
printf("Unmapped input character, cannot be converted to Latin1");
m_pConv = ucnv_open("UCS-2", &resultCode);
if (U_SUCCESS(resultCode))
{
// outputLength = ucnv_convert("UCS-2", "UTF-8", target, targetSize, uniString.c_str(), bodySize, &resultCode);
outputLength = ucnv_fromAlgorithmic(m_pConv, UCNV_UTF8, target, targetSize, uniString.c_str(),
uniString.length(), &resultCode);
ucnv_close(m_pConv);
}
printf("UCS-2 DGF just tried to convert '%s' to '%s' with error '%i' and length '%i'", uniString.c_str(),
outputLength ? target : "invalid_char", resultCode, outputLength);
if (U_SUCCESS(resultCode))
{
pdus = SegmentText(target, pText, SEGMENT_SIZE_UNICODE_MAX, true);
}
}
else
{
printf("DecodeText(): Text contents does not appear to be valid UTF-8");
}
}
else
{
printf("DecodeText(): Text successfully converted to Latin1");
std::string newBody(target, outputLength);
pdus = SegmentText(newBody, pPdu, SEGMENT_SIZE_MAX);
}
The problem is the ucnv_fromAlgorithmic function is throwing an error U_INVALID_CHAR_FOUND for the ucs-2 conversion. This makes sense for the ISO-8859-1 attempt, but not the ucs-2.
The other attempt was to use ucnv_convert which you can see is commented out. This function attempted conversion, but didn't fail on the ISO-8859-1 attempt as it should.
So the question is, does anyone have experience with these function and see something incorrect or is there something incorrect about the assumption of conversion for this character?
You need to reset resultCode to U_ZERO_ERROR before calling ucnv_open. Quote from manual:
"ICU functions that take a reference (C++) or a pointer (C) to a UErrorCode first test if(U_FAILURE(errorCode)) { return immediately; } so that in a chain of such functions the first one that sets an error code causes the following ones to not perform any operation"

Having problems converting jByteArray to .NET byte[ ]

I am having problems working with JNI and have been stuck on this issue for quite some time. I have posted about this before, but never received an answer and have done lots of research between now and then.
My JNI signature:
JNIEXPORT void JNICALL Java_MyApplet_invokeManager(JNIEnv *jniEnvPtr, jobject javaObj, jbyteArray encodedData)
Some of my code:
boolean isCopy;
jbyte* bytes = jniEnvPtr->GetByteArrayElements(encodedData, &isCopy);
jniEnvPtr->ReleaseByteArrayElements(encodedData, bytes, JNI_ABORT);
myManager->ShowQueue(encodedData);
The error message:
error C2664: 'MyModule::JniToManaged::ShowFormQueue' : cannot convert parameter 2 from 'jbyte *' to 'cli::array<Type,dimension> ^'
I have found no way to convert this to the C# byte[] that I need to pass. I have heard about casting the jbyte* object but can't figure out how to get it into the correct format.
I figured out how to convert a jbytearray to cli::array. Here is the code:
jbytearray jArray; //my array
jint len = jniEnvPtr->GetArrayLength(jArray); //get length
boolean isCopy;
jbyte* b = jniEnvPtr->GetByteArrayElements(jArray, &isCopy); //get pointer
array<byte, 1> ^myArray = gcnew array<byte, 1>(len); //create the cli::array
//loop through jbytearray and copy elements into our cli::array
for(int i = 0; i < len; i++)
{
myArray[i] = b[i];
}
jniEnvPtr->ReleaseByteArrayElements(jArray, b, JNI_ABORT); // release
I also had to convert a jstring to System::String^...
jboolean blnIsCopy;
jstring jstrOutput;
//jstring to char*
const char* strA = (jniEnvPtr)->GetStringUTFChars(theJString, &blnIsCopy);
//char* to std::string
std::string standardStr(strA);
//std:string to System::String^
System::String^ str2 = gcnew System::String(standardStr.c_str())
MessageBox(NULL, standardStr.c_str(), "Report Name!", MB_OK);
jniEnvPtr->ReleaseStringUTFChars(theJString, strA);
If there are no answers, it means (a) nobody knows, (b) nobody cares (Posting .net stuff under C++ is a sure fire way to get there. Posting an error message that should be easy to fix is another.), or (c) it might be a case of "What the heck is OP trying to do?! This makes no sense whatsoever. Nope, no head nor tail. Moving on..."

C++/CLI UTF-8 & JNI Not Converting Unicode String Properly

I have a Java class that returns a unicode string... Java has the correct version of the string but when it comes through a JNI wrapper in the form of a jstring it must be converted over to a C++ or C++/CLI string. Here is some test code I have which actually works on most languages except for the asian char sets. Chinese Simplified & Japanese characters are garbled and I can't figure out why. Here is the code snippet, I don't see anything wrong with either methods of conversion (the if statement checks os as I have two VMs with diff OS's and runs the appropriate conversion method).
String^ JStringToCliString(const jstring string){
String^ converted = gcnew String("");
JNIEnv* envLoc = GetJniEnvHandle();
std::wstring value;
jboolean isCopy;
if(string){
try{
jsize len = env->GetStringLength(string);
if(Environment::OSVersion->Version->Major >= 6) // 6 is post XP/2003
{
TraceLog::Log("Using GetStringChars() for string conversion");
const jchar* raw = envLoc->GetStringChars(string, &isCopy);
// todo add exception handling here for jvm
if (raw != NULL) {
value.assign(raw, raw + len);
converted = gcnew String(value.c_str());
env->ReleaseStringChars(string, raw);
}
}else{
TraceLog::Log("Using GetStringUTFChars() for string conversion.");
const char* raw = envLoc->GetStringUTFChars(string, &isCopy);
if(raw) {
int bufSize = MultiByteToWideChar(CP_UTF8, 0 , raw , -1, NULL , 0 );
wchar_t* wstr = new wchar_t[bufSize];
MultiByteToWideChar( CP_UTF8 , 0 , raw , -1, wstr , bufSize );
String^ val = gcnew String(wstr);
delete[] wstr;
converted = val; // partially working
envLoc->ReleaseStringUTFChars(string, raw);
}
}
}catch(Exception^ ex){
TraceLog::Log(ex->Message);
}
}
return converted;
}
Answer was to enable east asian languages in Windows XP as Win7 + Later work fine. Super easy.... waste of a entire day lol.

Using string pointers in C++ and Objective-C

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';