Virgil Ming
Virgil Ming

Reputation: 559

C++: Unable to read resource files in macos bundle

Background:

I was writing a "Hello Rectangle" OpenGL application. I want it to be a standalone .app instead of just a "UNIX executable." The code runs fine from Terminal.app.

I didn't use Xcode; I'm very tight on disk space and I'm not doing iOS dev. Instead I'm using brewed toolchain, including gcc-8, glfw and glew.

I structured my release dir according to this SO question as follows:

Contents
├── Frameworks
│   ├── libGLEW.2.1.dylib
│   ├── libgcc_s.1.dylib
│   ├── libglfw.3.dylib
│   └── libstdc++.6.dylib
├── MacOS
│   ├── hello-rect    <- the actual binary
│   └── launcher.sh   <- entry wrapper for working dir as the above link suggests
├── Resources
│   ├── rect.frag
│   └── rect.vert
└── Info.plist

I also modified the reference to dylibs in binary. But it won't run.

After a few hours of Googling, this SO answer and this SO question and several others convinced me that I should use CoreFoundation to read those two shader files (text file) in Resources.

Problem:

I tried following code (pasted from the link above with slight modification), but it gets segfault:

const char* textFileReadCF(const char *&filename){
  // Get a reference to the main bundle
  CFBundleRef mainBundle = CFBundleGetMainBundle();

  // Get a reference to the file's URL
  CFStringRef filenameHandle = CFStringRef(filename);

  // ------ segfault 11 here ------
  CFURLRef fileURL = CFBundleCopyResourceURL(mainBundle, filenameHandle, NULL, NULL);

  // Convert the URL reference into a string reference
  CFStringRef filePath = CFURLCopyFileSystemPath(fileURL, kCFURLPOSIXPathStyle);

  // Get the system encoding method
  CFStringEncoding encodingMethod = CFStringGetSystemEncoding();

  // Convert the string reference into a C string
  const char *path = CFStringGetCStringPtr(filePath, encodingMethod);
  
  // the real read function, read in file and returns all as a big char* arr
  return textFileRead(path);
}

// this runs fine before packing and I didn't change, 
// just for the sake of completeness
const char *textFileRead(const char *filename){
  std::ifstream shaderFile(filename);
  std::ostringstream shaderBuffer;
  shaderBuffer << shaderFile.rdbuf();
  std::string shaderBufferStr = shaderBuffer.str();
  // Warning: safe only until shaderBufferStr is destroyed or modified
  char * ret = new char[shaderBufferStr.size()];
  std::strcpy(ret, shaderBufferStr.c_str());
  return ret;
}

textFileReadCF will be called twice with "rect.vert" and "rect.vert" (not literal, but const char* holding them).

I have no idea why it goes segfault. I made sure there is one mainBundle through assert (omitted), and that filenameHandle is valid as I can tell, but otherwise I don't know where to look.

Upvotes: 0

Views: 736

Answers (1)

Virgil Ming
Virgil Ming

Reputation: 559

Turns out I do not need CoreFoundation. I just need GLFW.

In an earlier version, my release dir looked like this:

Contents
├── Frameworks
│   ├── libGLEW.2.1.dylib
│   ├── libgcc_s.1.dylib
│   ├── libglfw.3.dylib
│   └── libstdc++.6.dylib
├── MacOS
│   ├── hello-rect    <- the actual binary
│   ├── launcher.sh   <- entry wrapper for working dir as the above link suggests
│   ├── rect.frag
│   └── rect.vert
└── Info.plist

Because the unbundled version obviously looks nowhere but cwd. I didn't even know there is Resources. At that time my code also didn't run; my Google trace let me believe that CoreFoundation is the solution. Only until I checked docs about CoreFoundation did I know there should be Resources. And only by chance did I come across this SO question, when all the dots connected.

Apparently my version of GLFW also behaves like the link states: (directly from source)

// Change to our application bundle's resources directory, if present

So all it takes to make the bundle run out of box is, just structure the release dir like in question, and directly call textFileRead with "rect.vert" and "rect.vert", and we're all set.

Upvotes: 1

Related Questions