Objective-C to C++ reading binary file into multidimensional array of float

Viewed 382

I want to convert the following code from objective C to C++.

In the class myClass, I have this attribute:

float tab[dim1][dim2][dim3];

In an objective-C file, the multidimensional array is filled from a binary file:

NSData *dataTab=[NSData dataWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"pathOfMyTab" ofType:@""]];
[dataTab getBytes:myClass -> tab  length:[dataTab length]];

How could I translate this part into C++ ?

4 Answers

Take a look at fstream, fread and read, all read binary files, pick the approach that suits.

On my mind the simplest and fastest way is to use memcpy() to copy NSData' bytes into target array with same structure (dimensions) as a source one. See, for example:

https://github.com/Voldemarus/MultiDimensionalArrayDemo/tree/master

#import "DemoClass.h"

#define DIM1    3
#define DIM2    4
#define DIM3    2

@interface DemoClass() {
    int src[DIM1][DIM2][DIM3];  // source (initial) array

    int dst[DIM1][DIM2][DIM3];  // destination array
}
@end

@implementation DemoClass

- (instancetype) init
{
    if (self = [super init]) {
        for (int i = 0; i < DIM1; i++) {
            for (int j = 0; j < DIM2; j++) {
                for (int k = 0; k < DIM3; k++) {
                    int value = i*100 + j*10 + k;
                    src[i][j][k] = value;
                }
            }
        }
    }
    return self;
}

int getIntFromArray(int *array, int i, int j, int k) {
    int offset = j*DIM3 + i*DIM2*DIM3;
    return array[offset];
}

void putIntToArray(int *array, int i, int j, int k, int value) {
    int offset = j*DIM3 + i*DIM2*DIM3;
    array[offset] = value;
}

- (void) run
{
    // Step 1. Save array into NSData
    NSInteger s = sizeof(int)*DIM1*DIM2*DIM3;
    NSData *data = [[NSData alloc] initWithBytes:src length:s];
    NSAssert(data, @"NSData should be created");
    //Step2 - Create new array
    int *bytes = (int *)[data bytes];
    memcpy(dst,bytes,s);
    // Step 3. Compare src and dst
    for (int i = 0; i < DIM1; i++) {
        for (int j = 0; j < DIM2; j++) {
            for (int k = 0; k < DIM3; k++) {
                int template = i*100 + j*10 + k;
                int s = src[i][j][k];
                int d = dst[i][j][k];
 //               NSLog(@"i %d j %d k %d -->s = %d  d = %d",i,j,k,s,d);
                NSAssert(s == template, @"Source array should have value from template");
                NSAssert(d == s, @"Destination array should be identical to the source");
            }
        }
    }

}

@end

I am assuming that your file contains the byte-representation of the array. If this is the case, then to mimic the behaviour of your Objective-C code using only C++ (the only thing that makes this C++ is the reinterpret_cast<>, otherwise it is just straight C), you could use the following code. I have not added any error checking, but left some comments where you might want to perform some.

float tab[dim1][dim2][dim3];

CFBundleRef mainBundle = CFBundleGetMainBundle();
CFURLRef dataTabURL = CFBundleCopyResourceURL(mainBundle, CFSTR("pathOfMyTab"), NULL, NULL);

CFReadStreamRef stream = CFReadStreamCreateWithFile(NULL, dataTabURL); // check for NULL return value
CFReadStreamOpen(stream);                                              // check for errors here
CFReadStreamRead(stream, reinterpret_cast<UInt8 *>(tab), sizeof tab);  // check that this function returns the number of bytes you were expecting (sizeof tab)
CFReadStreamClose(stream);

// we own "stream" and "dataTabURL" because we obtained these through functions
// with "create" in the name, therefore we must relinquish ownership with CFRelease
CFRelease(stream);
CFRelease(dataTabURL); // ditto

If you already have the path available in a std::string, then you can use the following C++ code to mimic the behaviour of your Objective-C code:

// make sure to include this header
#include <fstream>

// ... then elsewhere in your .cpp file ...
float tab[dim1][dim2][dim3];

std::string path = "path/to/mytab"; // obtain from somewhere
std::ifstream input(path, std::ios::binary); // check that the file was successfully opened
input.read(reinterpret_cast<char *>(tab), sizeof tab); // check that input.gcount() is the number of bytes you expected

I believe in this case we have to use reinterpret_cast<> because the file contains the actual representation of the array (assuming it was previously written to the file in a similar manner).

You can use a hybrid approach, once you have the CFURLRef containing the path to the resource, you can obtain a file system representation of the URL using this function (providing a suitably sized output buffer to store the result), and from there you should be able to pass that to one of std::ifstream's constructors (although, you may need to cast to the appropriate type).

C++ doesn't support variable-length arrays (the size of arrays must be known at compile time). There is also no matrix type provided by the standard library, so if the dimensions of your table vary at run time, then you will need a completely separate approach to the one in my answer. You could consider serialising the output from Objective-C (using e.g. JSON or another format) such that the dimensions of the matrix are also written to the output, making it easier to parse the file in C++.

float tab[dim1][dim2][dim3] looks like a three-dimensional array. The standard implementation is with three nested FOR loops.

So your C++ implementation can look like this:

  • read dim1, dim2, dim3 from somewhere, usually the first values in the file (for example 12 bytes, 4 bytes for each number)
  • read the rest of the file in three nested FOR loops

Something like:

    for (size_t i = 0; i < dim1; ++i) 
       for (size_t j = 0; j < dim2; ++j)
         for (size_t k = 0; k < dim3; ++k)
           tab[i][j][k] = read_float_value(inputFile);

In Objective-C you can write the file in a similar way.

Here are some examples to get you started:

Related