PDA

View Full Version : initWithContentsOfFile crash and other file handling issues




Edoz
Nov 14, 2010, 11:40 PM
Hi,
I'm new to Objective C but I have previous experience with C++.
After creating a couple of simple programs to get used to the new syntax, I decided to embark in something just a little more complex.
I'm coding a program that encrypts a .txt file with a user-inputted password. I have already done this program in C++ under windows in the past, now I'm trying to make it for mac (altho I lost the old C++ version). For now I'm just trying to make it copy the .txt file into a new .txt file, without using the simple FileManager method because I'll eventually want to mess(encrypt) with the read buffer before writing to the new file.

well here's the code, i added comments to make it clear what im doing

class interface: TxtCrypt.h

/*
* TxtCrypt.h
* TxtCrypt
*
* Created by Edo on 11/14/10.
* Copyright 2010 __MyCompanyName__. All rights reserved.
*
*/

// extend NSString to copy input from "cin >>" and return it as NSString
@interface NSString (readFromCin)

-(NSString*) readFromCin;

@end


@interface TxtCrypt : NSObject
{

}

-(NSString*) askFileName: (bool) forInput; // asks file name for input files, or asks file name and creates file for output
-(int) askPin: (bool) forDecrypt; // asks pin to decrypt file, or (new) pin to encrypt file
-(bool) encrypt;
-(bool) decrypt;

@end

implementation: (TxtCrypt.mm)

/*
* encrypt.h
* TxtCrypt
*
* Created by Edo on 11/14/10.
* Copyright 2010 __MyCompanyName__. All rights reserved.
*
*/

#import <Foundation/Foundation.h>
#import <stdlib.h>
#import <iostream>
#import "TxtCrypt.h"

using namespace std;

// extend NSString to copy input from "cin >>" and return it as NSString
@implementation NSString (readFromCin)

-(NSString*) readFromCin {

string buff;
cin >> buff;
NSString* name = [[NSString stringWithUTF8String:buff.c_str()] retain];
return name;

}

@end


@implementation TxtCrypt

-(NSString*) askFileName: (bool) forInput {
bool ok = false;
NSString* filename = [[NSString alloc] init];
NSFileManager *unique = [NSFileManager defaultManager];

// ask name of a file to read from
if(forInput) {
cout << "Enter input file name, including extention: ";

while (ok == false) {
[filename readFromCin];
if ([unique fileExistsAtPath: @"file.txt"] == YES) { //im using @"file.txt" because i had problems identifying the file. it should however be filename. i created file.txt in the directory so it finds it"
cout << endl << "File " << filename << " successfully opened." << endl;
ok = true;
}
else
cout << "File " << filename << " does not exist. Please enter correct name: ";

}
}
// ask name and create a file to write to
else {
cout << "Enter output file name, including desired extention: ";

while (ok == false) {
[filename readFromCin];
if ([unique fileExistsAtPath: filename] == NO) {
[unique createFileAtPath:filename contents:nil attributes:nil];
cout << endl << "File " << filename << " successfully created." << endl;
ok = true;
}
else
cout << "File " << filename << " already exists. Please enter different name: ";

}
}

return filename;
}

-(int) askPin: (bool) forDecrypt {

int pin;

if (forDecrypt) {
cout << "Please enter correct pin number for decypt: ";
cin >> pin;
}
else {
cout << "Please enter desired pin number for encryption: ";
cin >> pin;
}
cout << endl;

return pin;
}

-(bool) encrypt {
NSString* inputFileName;
NSString* outputFileName;
// NSFileHandle* inputFile;
NSFileHandle* outputFile;
NSData* readBuffer;
NSData* writeBuffer = [NSData data];
NSRange* range;
int pin;

// ask file to encrypt; ask output file. initialize NSFileHandle. ask new pin to encrypt
inputFileName = [self askFileName:true];
assert(inputFileName != nil);
outputFileName = [self askFileName:false];
assert(outputFileName != nil);
outputFile = [NSFileHandle fileHandleForWritingAtPath:outputFileName];
pin = [self askPin:false];

// read entire input file to buffer. apply encryption to buffer. write buffer to output.
readBuffer = [NSData initWithContentsOfFile:inputFileName];
for (int i = 0; i <= sizeof([readBuffer lenght]); i++) {
[range NSMakeRange:i:1];
[readBuffer getBytes:writeBuffer:range];
[outputFile seekToEndOfFile];
[outputFile writeData:writeBuffer];
}
[outputFile closeFile];
return true;
}

@end

main.mm:

#import <Foundation/Foundation.h>
#import "TxtCrypt.h"

int main (int argc, const char * argv[]) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

id session = [[TxtCrypt alloc] init];

NSLog (@"Current directory is %@", currentpath);
[session encrypt];
[pool drain];
return 0;
}


you read it thru here, so thanks in advance.

first, am I doing it the way it's supposed to be done in objective c? e.g. the .h file, the two .mm files, the way im implementing the methods, is it correct objective c style?

now the real problem:
when i run, it goes fine until this part:

readBuffer = [NSData initWithContentsOfFile:inputFileName];
for (int i = 0; i <= sizeof([readBuffer lenght]); i++) {
[range NSMakeRange:i:1];
[readBuffer getBytes:writeBuffer:range];
[outputFile seekToEndOfFile];
[outputFile writeData:writeBuffer];
}

i get this crash in the console:
2010-11-14 13:37:39.598 TxtCrypt[1881:80f] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '+[NSData initWithContentsOfFile:]: unrecognized selector sent to class 0xa075702c'
*** Call stack at first throw:
(
0 CoreFoundation 0x95ee6bba __raiseError + 410
1 libobjc.A.dylib 0x93524509 objc_exception_throw + 56
2 CoreFoundation 0x95f3399b +[NSObject(NSObject) doesNotRecognizeSelector:] + 187
3 CoreFoundation 0x95e8d7e6 ___forwarding___ + 950
4 CoreFoundation 0x95e8d3b2 _CF_forwarding_prep_0 + 50
5 TxtCrypt 0x0000252e -[TxtCrypt encrypt] + 347
6 TxtCrypt 0x00002867 main + 232
7 TxtCrypt 0x00001f9d start + 53
)


i don't understand what's wrong with my call initwithcontentsoffile.
Also, I get several warnings in the following lines like:
NSData may not respond to method +initwithcontentsoffile. Isn't it a "normal" method of NSData?

also, when i do stuff like
cout << endl << "File " << filename << " successfully created." << endl; the output of filename is a memory address instead of the string associated with filename. any help here?

any help is appreciated.



Sydde
Nov 15, 2010, 12:05 AM
+initwithcontentsoffile. Isn't it a "normal" method of NSData?

I cannot ever remember seeing any "+init~" method. It is almost always "-init~". What you would want would probably start with "+data~".

kpua
Nov 15, 2010, 12:39 AM
You either need to +alloc an NSData before sending it then*-initů method, or use +dataWithContentsOfFile:, which is a properly class method.

gnasher729
Nov 15, 2010, 05:12 AM
Turn warnings on in the compiler. For example the very useful warning about sending a message that the receiver likely doesn't understand. Then remember that "+" stands for class methods (messages get sent to the class) and "-" stands for instance methods (messages get sent to an instance); they are not the same at all.

jared_kipe
Nov 15, 2010, 08:58 AM
Along the same vein of the others here. Your NSString category method -readFromCin, should be a class method +, and should return an autoreleased instance. (meaning don't bother retaining it before returning it)

Sydde
Nov 15, 2010, 04:54 PM
Along the same vein of the others here. Your NSString category method -readFromCin, should be a class method +, and should return an autoreleased instance. (meaning don't bother retaining it before returning it)

It appears to be being sent to an instance "filename", so in the sense that it is used as an instance method, that much is correct. However, it looks like the receiver is expected to take the return value, which is clearly wrong. I am not entirely sure what advantage there us to writing a category method here.

kainjow
Nov 15, 2010, 05:44 PM
also, when i do stuff like
cout << endl << "File " << filename << " successfully created." << endl; the output of filename is a memory address instead of the string associated with filename. any help here?

cout is part of C++, you can't use NS* objects with it directly. Use NSLog() instead:
NSLog(@"File %@ successfully created.", filename);

Edoz
Nov 16, 2010, 02:18 AM
thanks for the tips. I want to use cout, not nslog, because i want to make it a legitimate console program, not a program that tells you what to do in debug logs (altho for all purposes i could just do that), but i found a way to make it work anyways.

Now I get a new problem I can't solve, which is exc_bad_access. Don't worry, I run into this problem many times and I was able to fix it; This time i really don't understand how could this happen. I'll also indicate the exact line in which it happens:

note:i'm pasting entire code for reference for other people, for example now readFromCin works so others can use that code.

// extend NSString to copy input from "cin >>" and return it as NSString
@implementation NSString (readFromCin)

-(NSString*) readFromCin {

char buff[30];
scanf("%s",buff);
id name = [NSString stringWithUTF8String:buff];
//[[NSString stringWithUTF8String:buff.c_str()] retain];
return name;

}

@end


@implementation TxtCrypt

-(NSString*) askFileName: (bool) forInput {
bool ok = false;
NSString* filename = [[NSString alloc] init];
NSFileManager *unique = [NSFileManager defaultManager];

// ask name of a file to read from
if(forInput) {
cout << "Enter input file name, including extention: ";

while (ok == false) {
filename = [filename readFromCin];
if ([unique fileExistsAtPath: filename] == YES) {
cout << endl << "File " << [filename UTF8String] << " successfully opened." << endl;
ok = true;
}
else
cout << "File " << [filename UTF8String] << " does not exist. Please enter correct name: ";

}
}
// ask name and create a file to write to
else {
cout << "Enter output file name, including desired extention: ";

while (ok == false) {
filename = [filename readFromCin];
if ([unique fileExistsAtPath: filename] == NO) {
[unique createFileAtPath:filename contents:nil attributes:nil];
cout << endl << "File " << [filename UTF8String] << " successfully created." << endl;
ok = true;
}
else
cout << "File " << [filename UTF8String] << " already exists. Please enter different name: ";

}
}

return filename;
}

-(int) askPin: (bool) forDecrypt {

int pin;

if (forDecrypt) {
cout << "Please enter correct pin number for decypt: ";
cin >> pin;
}
else {
cout << "Please enter desired pin number for encryption: ";
cin >> pin;
}
cout << endl;

return pin;
}

-(bool) encrypt {
NSString* inputFileName;
NSString* outputFileName;
// NSFileHandle* inputFile;
NSFileHandle* outputFile;
NSData* readBuffer = [[NSData data] retain];
NSData* writeBuffer;
NSRange range;
int pin;

// ask file to encrypt; ask output file. initialize NSFileHandle. ask new pin to encrypt
inputFileName = [self askFileName:true];
assert(inputFileName != nil);
outputFileName = [self askFileName:false];
assert(outputFileName != nil);
outputFile = [[NSFileHandle fileHandleForWritingAtPath:outputFileName] retain];
pin = [self askPin:false];

// read entire input file to buffer. apply encryption to buffer. write buffer to output.
readBuffer = [NSData dataWithContentsOfFile:inputFileName];
unsigned int lenght = [readBuffer length];
char *character;
for (unsigned int i = 0; i <= lenght; i++) {
NSLog(@"%d",lenght);
range = NSMakeRange(i,1);
[readBuffer getBytes:character range:range];
NSLog(@"%s",character);
[outputFile seekToEndOfFile];
writeBuffer = [[NSData dataWithBytes:character length:1] retain];
NSLog(@"%s",[writeBuffer UTF8String]);
[outputFile writeData:writeBuffer];
}
[outputFile closeFile];
return true;
}

@end



error happens here, at [readBuffer getBytes:character range:range];
for (unsigned int i = 0; i <= lenght; i++) {
NSLog(@"%d",lenght);
range = NSMakeRange(i,1);
[readBuffer getBytes:character range:range];
NSLog(@"%s",character);
[outputFile seekToEndOfFile];
writeBuffer = [[NSData dataWithBytes:character length:1] retain];
NSLog(@"%s",[writeBuffer UTF8String]);
[outputFile writeData:writeBuffer];

I don't understand why I would get a exc_bad_access there. character is a pointer to 1 character that should is not(i hope?) released before the loop; range i just assigned it the line before so it's impossible that it was released - or is it?

thanks.

Comrade Yeti
Nov 16, 2010, 07:36 AM
thanks for the tips. I want to use cout, not nslog, because i want to make it a legitimate console program, not a program that tells you what to do in debug logs (altho for all purposes i could just do that), but i found a way to make it work anyways.

Now I get a new problem I can't solve, which is exc_bad_access. Don't worry, I run into this problem many times and I was able to fix it; This time i really don't understand how could this happen. I'll also indicate the exact line in which it happens:

note:i'm pasting entire code for reference for other people, for example now readFromCin works so others can use that code.

// extend NSString to copy input from "cin >>" and return it as NSString
@implementation NSString (readFromCin)

-(NSString*) readFromCin {

char buff[30];
scanf("%s",buff);
id name = [NSString stringWithUTF8String:buff];
//[[NSString stringWithUTF8String:buff.c_str()] retain];
return name;

}

@end


@implementation TxtCrypt

-(NSString*) askFileName: (bool) forInput {
bool ok = false;
NSString* filename = [[NSString alloc] init];
NSFileManager *unique = [NSFileManager defaultManager];

// ask name of a file to read from
if(forInput) {
cout << "Enter input file name, including extention: ";

while (ok == false) {
filename = [filename readFromCin];
if ([unique fileExistsAtPath: filename] == YES) {
cout << endl << "File " << [filename UTF8String] << " successfully opened." << endl;
ok = true;
}
else
cout << "File " << [filename UTF8String] << " does not exist. Please enter correct name: ";

}
}
// ask name and create a file to write to
else {
cout << "Enter output file name, including desired extention: ";

while (ok == false) {
filename = [filename readFromCin];
if ([unique fileExistsAtPath: filename] == NO) {
[unique createFileAtPath:filename contents:nil attributes:nil];
cout << endl << "File " << [filename UTF8String] << " successfully created." << endl;
ok = true;
}
else
cout << "File " << [filename UTF8String] << " already exists. Please enter different name: ";

}
}

return filename;
}

-(int) askPin: (bool) forDecrypt {

int pin;

if (forDecrypt) {
cout << "Please enter correct pin number for decypt: ";
cin >> pin;
}
else {
cout << "Please enter desired pin number for encryption: ";
cin >> pin;
}
cout << endl;

return pin;
}

-(bool) encrypt {
NSString* inputFileName;
NSString* outputFileName;
// NSFileHandle* inputFile;
NSFileHandle* outputFile;
NSData* readBuffer = [[NSData data] retain];
NSData* writeBuffer;
NSRange range;
int pin;

// ask file to encrypt; ask output file. initialize NSFileHandle. ask new pin to encrypt
inputFileName = [self askFileName:true];
assert(inputFileName != nil);
outputFileName = [self askFileName:false];
assert(outputFileName != nil);
outputFile = [[NSFileHandle fileHandleForWritingAtPath:outputFileName] retain];
pin = [self askPin:false];

// read entire input file to buffer. apply encryption to buffer. write buffer to output.
readBuffer = [NSData dataWithContentsOfFile:inputFileName];
unsigned int lenght = [readBuffer length];
char *character;
for (unsigned int i = 0; i <= lenght; i++) {
NSLog(@"%d",lenght);
range = NSMakeRange(i,1);
[readBuffer getBytes:character range:range];
NSLog(@"%s",character);
[outputFile seekToEndOfFile];
writeBuffer = [[NSData dataWithBytes:character length:1] retain];
NSLog(@"%s",[writeBuffer UTF8String]);
[outputFile writeData:writeBuffer];
}
[outputFile closeFile];
return true;
}

@end



error happens here, at [readBuffer getBytes:character range:range];
for (unsigned int i = 0; i <= lenght; i++) {
NSLog(@"%d",lenght);
range = NSMakeRange(i,1);
[readBuffer getBytes:character range:range];
NSLog(@"%s",character);
[outputFile seekToEndOfFile];
writeBuffer = [[NSData dataWithBytes:character length:1] retain];
NSLog(@"%s",[writeBuffer UTF8String]);
[outputFile writeData:writeBuffer];

I don't understand why I would get a exc_bad_access there. character is a pointer to 1 character that should is not(i hope?) released before the loop; range i just assigned it the line before so it's impossible that it was released - or is it?

thanks.

With all the mis-spellings of the variable length (lenght) I'm surprised that compiles.

chown33
Nov 16, 2010, 07:57 AM
I don't understand why I would get a exc_bad_access there. character is a pointer to 1 character that should is not(i hope?) released before the loop; range i just assigned it the line before so it's impossible that it was released - or is it?

character is uninitialized. If you want a local 1-char buffer, declare a local char, not a char*, and use &c.

You also have some serious bugs with this fragment:
writeBuffer = [[NSData dataWithBytes:character length:1] retain];
NSLog(@"%s",[writeBuffer UTF8String]);
[outputFile writeData:writeBuffer];

You're leaking writeBuffer (over-retained).

UTF8String can't possibly return a valid string for bytes in the range 0x80-0xff. You need something other than %s. See NSLog's doc on formatting specifiers.

And while not necessarily a bug, you could write the equivalent functionality in half the code if you just used FILE* and getc() putc() fread() fwrite(). It looks like you're using the Objective-C classes and methods simply to use them, not because they're the most effective thing to use. If your crypto is block oriented, and many algorithms are, you'll be processing blocks of data anyway.

It's almost as if you've forgotten how to write decent C code, in your effort to write Objective-C at all.

jared_kipe
Nov 16, 2010, 10:26 AM
It appears to be being sent to an instance "filename", so in the sense that it is used as an instance method, that much is correct. However, it looks like the receiver is expected to take the return value, which is clearly wrong. I am not entirely sure what advantage there us to writing a category method here.

I really only looked at the way the method was written, and not used.

It doesn't have anything to do with an instance of NSString itself (there are no "self" arguments in the method), and seems to be a class method.

His usage of both the empty alloc/init'd NSString *filename in two places conflict in what he expects the method to do. Once calling the method and not caring about the leaked string it returns. And once assigning that string and leaking the initially alloc/init'd empty string.

Sydde
Nov 16, 2010, 12:04 PM
error happens here, at [readBuffer getBytes:character range:range];
for (unsigned int i = 0; i <= lenght; i++) {
NSLog(@"%d",lenght);
range = NSMakeRange(i,1);
[readBuffer getBytes:character range:range];
NSLog(@"%s",character);
[outputFile seekToEndOfFile];
writeBuffer = [[NSData dataWithBytes:character length:1] retain];
NSLog(@"%s",[writeBuffer UTF8String]);
[outputFile writeData:writeBuffer];

I don't understand why I would get a exc_bad_access there. character is a pointer to 1 character that should is not(i hope?) released before the loop; range i just assigned it the line before so it's impossible that it was released - or is it?

thanks.
One thing that is kind of difficult to get about Cocoa is that not everything that begins with NS~ is an object. NSRange is actually a simple struct - it cannot be released like an object. But your problem arises in your for statement. Review again what you seem to have forgotten about zero-based arrays.
I really only looked at the way the method was written, and not used.

It doesn't have anything to do with an instance of NSString itself (there are no "self" arguments in the method), and seems to be a class method.

His usage of both the empty alloc/init'd NSString *filename in two places conflict in what he expects the method to do. Once calling the method and not caring about the leaked string it returns. And once assigning that string and leaking the initially alloc/init'd empty string.

At what point does one decide that a function might be simpler and more efficient than a method? Part of learning Objective-C is learning where a plain C technique will serve your needs better. I think over-use of categories for existing classes (as opposed to breaking your own classes into multiple source files) is a probably bad habit to develop. I would most likely write readFromCin as a function.

jared_kipe
Nov 16, 2010, 12:52 PM
At what point does one decide that a function might be simpler and more efficient than a method? Part of learning Objective-C is learning where a plain C technique will serve your needs better. I think over-use of categories for existing classes (as opposed to breaking your own classes into multiple source files) is a probably bad habit to develop. I would most likely write readFromCin as a function.

This is a style thing, more than a simpler/efficient thing IMHO.

Almost every time a C function would be faster/more efficient. To be even more efficient you could write this as an inline preprocessor macro if you wanted to really be "efficient".

I believe there are times to make categorys, for example the one I made here. http://forums.macrumors.com/showthread.php?t=977076&highlight=
I think stylistically, this is the correct time for using a category on an existing object.



I agree with you that the OP's use of a category to provide this functionality is not exactly in the spirit that Objective-C's categories strive for, but there are far worse mistakes in this code i.e. actual mistakes.