I'm trying to convert a const char* that I'm getting from a C++ method that I have, to a NSString in a method in Objective-C. This is the method from c++:
#define EXPORT __attribute__((visibility("default")))
EXPORT
string getAudioWmark(const char* _filename)
{
string filename = string(_filename);
string result;
result = get_watermark(filename, "");
return (result.c_str());
}
This is what result is returning in the debugger view:
result std::string "pattern 0:00 f59e26b2f4668d02bd33e03a1c0d8892 0.774 0.774 CLIP-A\n\n"
This is the declaration of the method in the .h file from Objective-C:
extern const char* getAudioWmark(const char* filename);
#interface WatermarkLib : NSObject
- (void)getAudio:(NSString*)filename;
#end
And this is the .mm implementation of the method:
- (void)getAudio:(NSString*)filename {
const char *cString = [filename cStringUsingEncoding:NSASCIIStringEncoding];
NSString* externalString = [NSString stringWithCString:getAudioWmark(cString) encoding:NSUTF8StringEncoding];
NSLog(#"Exito! %#", externalString);
}
"externalString" is null, and that's what I'm trying to fix.
This should help you out.
const char *cString = filename.UTF8String;
But make sure filename has content otherwise it must result in null
Related
I am building a project based on STM32CubeProgrammer API. The filepath is is done like this and you have to input the filename in the code manually.
/* Download File + verification */
#ifdef _WIN32
const wchar_t* filePath = L"../test file/filename.hex";
#else
const wchar_t* filePath = L"../api/test file/filename.hex";
#endif
I want the program to show a list of available .hex files, ask for a corresponding number and then append the correct filename to the filePath. The goal is to ask for minimal input from user and keep it as simple as possible.
filePath should remain as const wchar_t*.
I wasn't able to find anything working on Google and I am not even sure how and what to search.
How can this be done?
Working solution, thanks to #Someprogrammerdude. User input not yet implemented.
std::wstring projects[] = { L"data.hex", L"blinky.hex" };
int projectNr = 0;
std::wstring file = L"../test file/" + projects[projectNr];
#ifdef _WIN32
const wchar_t* filePath = file.c_str();
#else
const wchar_t* filePath = L"../api/test file/";
#endif
I'm not sure why you need, or think that you need, const wchar_t* pointer. But instead of trying to "workaround" C++ standard and compilers by appending filename to path you can do it "other way around", i.e. instead of appending you can show only filenames, i.e. something like that:
const wchar_t *file_paths[] = {
"some path/filename1.hex",
"some path/filename2.hex",
...
};
const wchar_t* select_path() {
for (const wchar_t *path : file_paths) {
const wchar_t* filename = get_file_name_from_path(path);
out_file_name(filename);
}
return file_paths[input_number()];
}
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'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);
}
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.
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';