Click here to Skip to main content
15,867,308 members
Articles / Programming Languages / C#
Tip/Trick

Win32 API - GetFullPathName()

Rate me:
Please Sign up or sign in to vote.
5.00/5 (1 vote)
10 Jul 2011CPOL4 min read 25.5K   3   5
Getting a DllImport of kernel32's GetFullPathName to work (C#).

A number of questions on various forums dealing with the kernel32.dll function GetFullPathName() indicated that either,



  1. The 3rd argument was returning the current VS project directory, which for some reason was considered incorrect; or
  2. The 4th argument, whether null or otherwise, didn't appear to make any difference and returned no value.

Several of the questioners were using C++, a few C#. The DllImport attribute was similar, and I couldn't see anything funky about the argument types being passed to this function (but then, what do I know?). They thought their methods were supposed to work; and it looked like they should have, on the face of it.


Well, the following code does work:


C++
//  DWORD WINAPI GetFullPathName(    __in LPCTSTR lpFileName,
//        __in  DWORD nBufferLength,
//        __out LPTSTR lpBuffer,    __out LPTSTR* lpFilePart );

[DllImport( "Kernel32",  EntryPoint="GetFullPathName", 
            CharSet = CharSet.Ansi, SetLastError = true )]
static extern Int32 GetFullPathName( String ShortOrLongFilename,
          Int32 fullnameLength,
          StringBuilder fullName,     ref IntPtr FnPortionAddress );

public static void GetFullPath(STRING fn, 
          ref STRING Dir,
          ref STRING fName, ref int err) 
{
 if ((fn!=null)&&(fn.Len>0))
 {
  if (ReferenceEquals(Dir, null)) { Dir = new STRING(); }
  if (ReferenceEquals(fName, null)) { fName = new STRING(); }
  String s = fn.s(); 
  StringBuilder sB = new StringBuilder(264);
  StringBuilder sF = new StringBuilder(264);
  IntPtr zeero = IntPtr.Zero;
  err=0;

  Int32 res = GetFullPathName(s, sB.Capacity, sB, ref zeero);

  if (res==0) { err =  Marshal.GetLastWin32Error(); } // Failure
  else if (res > 264) { } // Shouldn't happen with FAT or NT names.
  else // OK
  { 
   string ss = Marshal.PtrToStringAnsi(zeero, 264);
   Dir.Set(sB.ToString()); fName.Set(Param(1, '\x00', ss));
  }
 }
}

The 4th argument will return the filename portion of the path. However, the null-termination turns out to look like:


"Test.txt\0\0\0\0\0\0\0\0\0\0\0"... 

in the VS debug quick-tip visualizer, when the string returned by Marshal.PtrToStringAnsi() is examined. So when I assign it to a STRING type, I use a call to Param() to get the first 'parameter' in the string (which is everything preceding the first null character) to clean it up first.


The types used here in the Win32 call are String, Int32, StringBuilder, and IntPtr. I've seen examples using the StringBuilder as the type for all but the 2nd argument; but whether they work I don't know because I haven't tried them.


The variable s is an instance of class String, not of type string, but it is usable as though it were. The value assigned to it comes from a STRING class conversion which calls an override of the ToString() method for that class to obtain its value. So the string value comes from a type which isn't found anywhere in the .NET Framework, and after that value is assigned to this String, the kernel32 code has no problem with it. But the character set attribute being Ansi here rather than Auto or Unicode, as in some other cases, might have something to do with problems arising elsewhere which I haven't seen here. The STRING type is dedicated primarily to ANSI strings, and that might have something to do with it when the String variable is assigned.


So I think the String parameter looks good to GetFullPathName(). In kernel32, a number of checks are performed to handle ANSI/Unicode character casing and conversion through calls to Basep8BitStringToDynamicUnicodeString, RtlUnicodeToMultiByteSize, RtlUnicodeStringToAnsiString, RtlUnicodeStringToOemString, or RtlUpcaseUnicodeChar.


Stepping through with the PEBrowse debugger, I wasn't able to see why the situations noted in (1) and (2) above arose.


If there are any good guesses at the reasons for those, I'd like to hear about it.


I haven't seen any methods work which deal with the 4th argument using Marshal.PtrToStructure() to copy data from the reference returned to a structure; most won't compile, and the ones that did generated an ExecutionEngineException when the code ran, which is a CLR problem which often arises from corrupted data. Changing the Win32 header to use either char* or byte* runs into the problem that the compiler won't let you use the & address operator to assign the pointer to reference a managed type. I'd like to know how many valid combinations there are of operand types and marshaling behaviors that work in this case across this boundary, but I suspect there are few.


Apparently, enough people tried making this call that the general consensus was that the suggestion of just setting the 4th argument to null and ignoring the returned value looked like the best option. But I can't imagine why this would happen in C++ rather than only in C#.


The magnificent list in the MSDN library of Windows Data Types which gives all of the types which are considered relevant in C and C++ doesn't do me much good when it comes to C#, and any information I might need in a given case to solve a marshaling problem is likely to be somewhere in scattered detail where it'll take hours to find it.


CodeProject always has something that's likely to give you a hint, if not an outright answer; but it still isn't the "Marshaling for Dummies" collection most people would keep on the nightstand.


I wish there was something somebody could do about that!

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
United States United States
Writer, designer, inventor, musician, observer, and critic with many years of experience in each of these areas. I can do without the approval of "experts" because I believe candid statements and penetrating analysis bring greater rewards than a "pat on the back". And if I have something to say when you're not listening, I tell someone else about it.

Comments and Discussions

 
QuestionTried it Pin
Gluups16-Dec-19 4:11
Gluups16-Dec-19 4:11 
QuestionWhat is the need this is trying to fullfil? Pin
tlhIn`toq12-Jul-11 6:36
tlhIn`toq12-Jul-11 6:36 
AnswerRe: What is the need this is trying to fullfil? Pin
GAMerritt12-Jul-11 13:19
GAMerritt12-Jul-11 13:19 
GeneralRe: What is the need this is trying to fullfil? Pin
tlhIn`toq12-Jul-11 13:39
tlhIn`toq12-Jul-11 13:39 
GeneralRe: What is the need this is trying to fullfil? Pin
GAMerritt13-Jul-11 7:10
GAMerritt13-Jul-11 7:10 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.