views:

513

answers:

6

Running a c# console app I wrote on 64 bit Vista. Here's the code:

class Class1
{
    static void Main(string[] args)
    {
        Debug.Assert(File.Exists(@"c:\test.ini"));
        StringBuilder sb = new StringBuilder(500);
        uint res = GetPrivateProfileString("AppName", "KeyName", "", sb, sb.Capacity, @"c:\test.ini");
        Console.WriteLine(sb.ToString());
    }
    [DllImport("kernel32.dll")]
    static extern uint GetPrivateProfileString(string lpAppName, string lpKeyName, string lpDefault, StringBuilder lpReturnedString, int nSize, string lpFileName);
}

I'm sure I'll get a big "DUH!" for an answer, but I'm not seeing why this fails to work. Other than the Debug.Assert, this code was cut from the c# sample at this page

A: 

According to pinvoke.net, nSize should be a UINT. Also they are using an absolute path in their example.

Other than those differences, I can't see anything else.

Providing it's throwing a invalid format exception, try setting target platform to x86 to solve the problem.

Usage example from pinvoke.net is

[DllImport("kernel32.dll", CharSet=CharSet.Unicode)]
    static extern uint GetPrivateProfileString(
    string lpAppName, 
    string lpKeyName,
    string lpDefault, 
    StringBuilder lpReturnedString, 
    uint nSize,
    string lpFileName);


 static void Main(string[] args)
 {
   StringBuilder sb = new StringBuilder(500);
   uint res = GetPrivateProfileString("AppName", "KeyName", "", sb, (uint)sb.Capacity, @"c:\test.ini");
   Console.WriteLine(sb.ToString());
 }
Aequitarum Custos
The comment section of that page also states you can use int instead of union on the size argument to prevent having to case sb.Capacity.
Charles
Are you getting an invalid format exception? Or what exception/error is being thrown/return?
Aequitarum Custos
The function appears to succeed, but returns zero. When I check Marshal.GetLastWin32Error, it has the value 1008 == ERROR_NO_TOKEN
Charles
Sorry, That's a red herring. Marshal.GetLastWin32Error is 1008 before I call GetPrivateProfileString.
Charles
Are you running this in a Service or Website?
Aequitarum Custos
Nope. A simple user launched c# console application. Compile in VS and press F5 to run.
Charles
You need to fix the path, too...
Reed Copsey
Not the path. It fails whether I specify a full path or not.
Charles
@Charles: Without SetLastError=true in your DllImport attribute, GetLastWin32Error() may return a bogus value.
Mattias S
A: 

The main thing I see is that you should be passing in an uint for nSize, as well as the return value. This is because the return and nSize parameters of GetPrivateProfileString are DWORD values, which are unsigned 32bit integers.

I personally have used the syntax on PInvoke.net:

[DllImport("kernel32.dll", CharSet=CharSet.Unicode)]
static extern uint GetPrivateProfileString(
   string lpAppName, 
   string lpKeyName,
   string lpDefault, 
   StringBuilder lpReturnedString, 
   uint nSize,
   string lpFileName);

In addition, you'll need to put the full path to the file in place, unless the file is located in the Windows directory. From the docs:

If this parameter does not contain a full path to the file, the system searches for the file in the Windows directory.

Reed Copsey
A: 

If no path is specified, GetPrivateProfileString will look for Test.ini in the Windows directory.

Old APIs like GetPrivateProfileString don't handle Unicode well (even though there's a GetPrivateProfileStringW function). If Test.ini contains a UTF header or Unicode characters, that might be enough to prevent GetPrivateProfileString from working correctly.

Also, Vista's UAC can make handling files that are in "special" places tricky (C:\, C:\Windows, C:\Program Files, etc.). Try putting Test.ini in a folder rather than in the root of the C: drive, or turn off UAC. There's a thread on CodeProject that discusses GetPrivateProfileString failing silently when trying to read an .ini from a folder controlled by UAC.

Chris R. Timmons
Nope. Not it. I copied it into the Windows directory and no luck. I also added a Debug.Assert(File.Exists("Test.ini")) - I'm trying to read the one in the current working directory.
Charles
A: 

Maybe you should look into looking at an open source solution that will do exactly that without you worrying about p/Invoke's. The project targetted for .NET is called nini I am using this in a project I am working on and I recommend it.

Hope this helps, Best regards, Tom.

tommieb75
Thanks, but I'm trying to solve my frustration, not find a work around.
Charles
@Charles: Ok...you have looked at this http://pinvoke.net/default.aspx/kernel32/GetPrivateProfileString.html which includes the sample on how to use it?
tommieb75
That's where I cut 'n pasted my code from.
Charles
@Charles: By the way...I could not help noticing...you did say you wrote this on 64 bit vista...Would the api have changed? Just trying to help you by asking the simple questions first...
tommieb75
@Charles: In the sample, it is uint res = GetPrivateProfileString(...) and in your code you have int? Hope that helps...
tommieb75
The int and uints are interchangeable per the comments on that page. If I use their c# sample as is (you have to fix the compiler error) it still doesn't work.
Charles
I know GetPrivateProfileString exists for 32 bit apps. If I force my target to be x86 (rather than "any cpu") it does not resolve the issue.
Charles
@Charles: Is the ini file in your current environment directory, e.g if you run this program in C:\temp, then the ini file should be in there, if not, the winapi will look in the windows folder...other than that I guess Vista is blocking it...have you run this with elevated permissions?
tommieb75
I've tried the ini file in current working directory, windows directory, and c:\test.ini as the sample code now shows. I have UAC disabled on my 64 bit vista machine. Thanks for helping me think through this.
Charles
@Charles: No Prob. I'm a bit mystified at how it fails? Have a look here when I googled 'GetPrivateProfileString + Vista 64' http://social.msdn.microsoft.com/forums/en-US/windowscompatibility/thread/c07c6ad1-6e1c-4254-8129-1b2345d7280a/Try a directory other than C:\, Windows, place it maybe in the 'All Users' area..OGK what is going on!!!.That is my last shot based on the discussion in the link...
tommieb75
Tried my user directory under C:\Users\xxx without luck. Thanks for the ideas.
Charles
I shall declare myself beaten by this unless the int's are 64bit, maybe changing the signature to match that of 32 bits instead? Other than that I give up... :) Take care.
tommieb75
+1  A: 

This one has been busting my chops all day, too. I think I found a work-around, which I really don't want to have to deal with: insert a blank line before the first section header in your .ini file. Now run your app and see if you don't start seeing the values you were expecting.

Considering this bug has apparently been around for years, I'm surprised MS hasn't fixed it by now. But then, .ini files were supposed to have gone away years ago. Which of course is funny, because there are a lot of places MS uses .ini file (e.g, desktop.ini). But I think MS is aware of the bug, because I notice my desktop.ini files include a leading blank line. Hmmm...

Lee
I forgot to mention, I am running this code on XP Pro 32-bit.
Lee
I just tried GetPrivateProfileSectionW and GetPrivateProfileSectionNamesW. Both fail in the same way. Also interesting, when I try to retrieve section names, only the first section is returned, whether fetched using GetPrivateProfileStringW or GetPrivateProfileSectionNamesW.I've had enough fun with these. CodePlex looks like it may have a couple replacements. If neither of those work, I'll roll my own.
Lee
Oh My Gosh! You're right! The section I'm trying to read from cannot be the first line of the INI file! Unbelievable! Who would have known? Thank you so much.
Charles
A quick clearification. The problem is the VS editor put the UTF-8 Byte Order Mark (EF BB BF) at the start of the file. The INI reading/writing functions don't process BOMs. Since the section I was reading was on the first line, GetPrivateProfileString was NOT seeing "[AppName]" but "\xEF\xBB\xBF[AppName]" therefore it never found the section I was looking for. Adding a blank line to the begining of the file put the BOM on the first line and "[AppName]" on the second so it can be found. Big DUH on me.
Charles
Thanks! I was struggling with the same problem. Couldn't figure out why it worked with another app but not my new one. Adding a blank line at the top of the ini file fixed the problem.
Slapout
A: 

Can you verify the contents of your test.ini file? Given all of the steps that you've tried, I'm beginning to suspect that your data file is not formatted correctly (misspelling, etc.) In other words, GetPrivateProfileString may be "working", but just not finding your string. Based on the code that you posted, your test.ini file should look something like this:

[AppName]
KeyName=foo

Emerick Rogul
Nope. The INI was technically correct, but Lee pointed out the fix - The section I'm reading from can't begin on the first line of the INI file.
Charles
I think you must've written your comment before you discovered the Byte Order Marker at the beginning of your .ini file. Because based on that information, I would say that the .ini file was *not* "technically correct". :-)
Emerick Rogul