Why can't I get GetPrivateProfileString to work via pinvoke? - c#

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

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...

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.

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());
}

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.

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.

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

Related

Windows kernel32 functions in Mono on Linux

I got this awesomely simple ini class that I downloaded from somewhere a while ago, but now that I'm using mono I'm running into the issue that it's importing stuff from kernel32
[DllImport("kernel32")]
private static extern long WritePrivateProfileString(string section,
string key, string val, string filePath);
[DllImport("kernel32")]
private static extern int GetPrivateProfileString(string section,
string key, string def, StringBuilder retVal,
int size, string filePath);
Which on mono (in linux) gives the error DLLNotFoundException: kernel32
Is there any way to get this to work with mono? Maybe embed the whole thing into the assembly at compile time (if that even makes sense at all, I wouldn't know). Or will I have to create/find an ini class that doesn't use WinAPI? (Nini springs to mind).
I'd really like it if WinAPI stuff could work with Mono, any thoughts?
Mono supports C#'s P/Invoke, which is what's required for running Win32 API functions. (As long as you're running Mono on Windows--the fact that it can't find "kernel32" causes me to suspect you're not.)
The site pinvoke.net collects the necessary DllImport signatures for most of the Win32 API.
Here's what it has to say about GetPrivateProfileString.
This code worked for me using Mono 2.10.8 on Windows 7:
using System;
using System.Text;
public class MainClass
{
[DllImport("kernel32")]
private static extern long WritePrivateProfileString(string section,
string key, string val, string filePath);
[DllImport("kernel32")]
private static extern int GetPrivateProfileString(string section,
string key, string def, StringBuilder retVal,
int size, string filePath);
static void Main()
{
StringBuilder asdf = new StringBuilder();
GetPrivateProfileString("global","test","",asdf,100,#"c:\example\test.ini");
Console.WriteLine(asdf.ToString());
}
}
You'll need to rewrite the functionality of those functions in native .NET to use them on Mono/Linux (unless you can convince Mono and Wine to play nicely).
If the INI files are controlled, then you may get away with simple file/string manipulation, but then you may be better off moving to something a bit more cross platform anyway.
Change [DllImport("kernel32")] into [DllImport("kernel32.dll")]
Everything will start working like supposed to.

Reading current installed version of an application using windows api

I was trying to use windows api to find out the version info of an installed application.
I used the upgrade code to find out the product code using MsiEnumRelatedProducts api, but when I try to use MsiGetProductInfo using the product code, the version info comes back as garbage.
Here is my MsiGetProductInfo api:
[DllImport("msi.dll", CharSet = CharSet.Unicode)]
private static extern Int32 MsiGetProductInfo(
string product, string property, [Out] StringBuilder valueBuf,
ref Int32 len);
MsiGetProductInfo(sbProductCode, "INSTALLPROPERTY_INSTALLVERSION", builder, ref len);
Any thoughts on what I'm doing wrong?
In response to #JoshHetland the string to pass is the CamelCase postfix of the INSTALLPROPERTY_VERSIONSTRING - remember that MSI is case sensitive.
So:
INSTALLPROPERTY_VERSIONSTRING becomes VersionString
INSTALLPROPERTY_INSTALLDATE becomes InstallDate
and so on.
Complete list of properties available is on the MSDN page for the MsiGetProductInfo function .
Here is what I did that my solved my problem.
Int32 m_len = 11512;
StringBuilder m_versionInfo = new StringBuilder(m_len);
StringBuilder m_sbProductCode = GetProductCodeFromMsiUpgradeCode();
MsiGetProductInfo(m_sbProductCode.ToString(), "**VersionString**", m_versionInfo, ref m_len);
return m_versionInfo.ToString();
This did return me the version string ,and also converted from decimal into string format like 1.4.3.
Application.ProductVersion works for me, no need to call WinAPI manually (I am still in .Net 1.1 though)

How to open a file when file handle number is known?

I open a file in C# with FileStream, and I got the file handle number with this line:
IntPtr file_handle = fs.SafeFileHandle.DangerousGetHandle();
Now I want to pass this handle to C++ code and use this handle value to access the file. Is this possible? How to open a file with merely a file handle in C++?
Thanks.
Update
I use C# to P/Invoke into a C++ Win32 DLL(not a COM DLL). I open the file in C# as FileStream, and pass the handle to the C++. Here is some of my code in the C++ DLL:
extern "C" __declspec(dllexport)void read_file(HANDLE file_handle)
{
char buffer[64];
::printf("\nfile = %d\n",file_handle);
if(::ReadFile(file_handle,buffer,32,NULL,NULL))
{
for(int i=0;i<32;i++)
cout<<buffer[i]<<endl;
}
else
cout<<"error"<<endl;
}
And here is my C# code:
[DllImport("...",EntryPoint = "read_file", CharSet = CharSet.Auto)]
public static extern void read_file(IntPtr file_handle_arg);
But I get this error:
Unhandled Exception: System.AccessViolationException: Attempted to read or write
protected memory. This is often an indication that other memory is corrupt.
Thanks.
You can use win32 calls, the same way the filestream/file constructors do (via p/invoke).
Cracking it open in .NET Reflector, it looks like it is using this function:
[DllImport("kernel32.dll", CharSet=CharSet.Auto, SetLastError=true)]
private static extern SafeFileHandle CreateFile(
string lpFileName,
int dwDesiredAccess,
FileShare dwShareMode,
SECURITY_ATTRIBUTES securityAttrs,
FileMode dwCreationDisposition,
int dwFlagsAndAttributes,
IntPtr hTemplateFile);
Here is an official reference:
http://msdn.microsoft.com/en-us/library/aa363858(VS.85).aspx
This is just to open the file, though, as you asked when you said:
How to open a file with merely a file handle in C++
If you want to read an already open file, you might have more trouble. I'm not sure. You might be able to use this function:
[DllImport("kernel32.dll", SetLastError=true)]
internal static extern unsafe int ReadFile(
SafeFileHandle handle,
byte* bytes,
int numBytesToRead,
IntPtr numBytesRead_mustBeZero,
NativeOverlapped* overlapped
);
http://msdn.microsoft.com/en-us/library/aa365467(v=VS.85).aspx
That entirely depends -- but it's unlikely that you will be able to do this. I'm assuming that your C# code and C++ code are running in different processes -- if you're in the same process you should just be able to marshall over the IntPtr over as a HANDLE.
The problem is that file handles are specific to a process -- you won't be able to use that handle in another process.
That said, you're probably better off:
Passing the name of the file to the C++ code and opening it there
Passing the data actually contained whithin the file to the C++ and dealing with it there.
If the C++ code is C++/CLI, then don't bother with the handle at all. Just pass the FileStream object directly to your C++ code.
If the C++ is native code, then you can use the file handle anywhere you'd normally use a Windows HANDLE value for files, such as ReadFile and WriteFile. You wouldn't use the handle to open a file because it's already open. If you want another copy of the handle, or if you want to give the handle to another process, then use DuplicateHandle. If you need to the value with POSIX-like functions like _read and _write, then call _open_osfhandle to get a file descriptor. You can wrap the file descriptor into a C FILE* stream with _fdopen.
Turns out the title isn't really what the OP was after.
But if someone ever really needs to do this (say: Re-opening a file with different permissions), you can probably use a combination of GetFileInformationByHandle to get the File ID and OpenFileById.
FWIW.

ShSetFolderPath works on win7, doesn't on XP

I'm trying to use ShSetFolderPath function in C#. I work on Win7, I've managed to use ShSetKnownFolderPath and it works fine.
Since this function is unavaible in WinXP, i tried to invoke ShSetFolderPath. Because i'm not familiar with invoking, I've done some searching and found something on some French forum. I don't speak French, but this declaration makes sense (as written in Remarks of function documentation in MSDN library):
[DllImport( "Shell32.dll", CharSet = CharSet.Unicode, EntryPoint = "#232" ) ]
private static extern int SHSetFolderPath( int csidl, IntPtr hToken, uint flags, string path );
I call it like that:
private static int CSIDL_DESKTOP = 0x0000;
public static void SetDesktopPath(string path)
{
int ret;
ret = SHSetFolderPath(CSIDL_DESKTOP, IntPtr.Zero, 0, path);
if (ret != 0)
{
Console.WriteLine(ret);
Console.WriteLine(Marshal.GetExceptionForHR(ret));
}
}
It works in Win7, but in XP function returns -2147024809, which means "Value does not fall within the expected range".
My guess is, it's something wrong with Dll importing. Any idea?
Funny thing.
I've taken another look at CSIDL list. And I've realized I was trying to change some "low-level" reference (i guess) to desktop:
CSIDL_DESKTOP = 0x0000, // <desktop>
While I actually wanted to change just folder location, and i should've use this:
CSIDL_DESKTOPDIRECTORY = 0x0010, // <user name>\Desktop.
And THIS works.
It explains everything. Shame on me.
Nah, that's not it. The error code, converted to hex, is 0x80070057. The 7 indicates a Windows error, 57 is error code 87, ERROR_INVALID_PARAMETER, "The parameter is incorrect".
A couple of possible reasons. First is that entry point #232 isn't actually the entry point for SHSetFolderPath(). You might be calling a different function, it wouldn't know what to do with the argument values you pass. Hard to say, it is an unnamed entry point on XP's version of shell32.dll. Or it could be that XP just isn't happy about you changing the desktop folder path. Not that surprising, there's a heckofalot it has to do to actually implement that, refreshing all Explorer.exe views, rebuilding the desktop contents and whatnot.
Check this thread for possible help.

How to find the path of the "Shared Documents" folder from c#?

In Windows Vista, the special folder "Shared Documents" contains documents accessible by all the users in the machine. That folder was renamed to "Public Documents" in Windows 7.
How can I find its physical path from c#?
Note that Environment.GetFolderPath(Environment.SpecialFolder.xxx) doesn't have the folder I'm looking for.
The SpecialFolder enum has had a large, and long overdue, update in .NET 4 - one of the new additions is the CommonDocuments member.
What about this?
[DllImport("shell32.dll")]
static extern int SHGetFolderPath(IntPtr hwndOwner, int nFolder, IntPtr hToken,
uint dwFlags, [Out] StringBuilder pszPath);
public string GetCommonDocumentsFolder()
{
int SIDL_COMMON_DOCUMENTS = 0x002e;
StringBuilder sb = new StringBuilder();
SHGetFolderPath(IntPtr.Zero,SIDL_COMMON_DOCUMENTS,IntPtr.Zero,0x0000,sb);
return SB.ToString();
}
Answer courtesy of, er..., expert-exchange that we all love to hate.
Path.Combine(Environment.GetEnvironmentVariable("PUBLIC"), "Documents");
Environment.GetFolderPath(Environment.SpecialFolder.CommonDocuments);
The file system directory that contains documents that are common to all users. This special folder is valid for Windows NT systems, Windows 95, and Windows 98 systems with Shfolder.dll installed.
Look in ShlObj.h for more CSIDLs
interestingly enough SHGetFolderPath has been depricated...
See http://msdn.microsoft.com/en-us/library/bb762181(VS.85).aspx

Categories