Win32 API paths longer than MAX_PATH

I made this thread to share my experience with the common win32 paths problem, which is the non-support for a longer names exceeding the MAX_PATH limit (defined as 260 characters including the terminating NULL one). Not so long I saw that with the Windows 8 SDK were added some interesting new path functions defining a new max path standard as follows (PathCch.h): PATHCCH_MAX_CCH

This header also includes some path helper functions for transforming paths either to their long form ("\\?\") or in their normal form. So now I think handling long paths won't be so funky anymore. Look here for more info.

But to use this function you need to target Windows 8 as a base OS which is not very good as the Win8 isn't very popular and the most users don't own it. That's way I made my own path correcting functions which have the functionality of the SDK one plus something more. Here is the code and defines needed (sorry for the length but I have implemented everything by my own):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
// max # of characters we support using the "\\?\" syntax
// (0x7FFF + 1 for NULL terminator)
#define PATHCCH_MAX_CCH             0x8000

#define LONG_PATH_ID                L"\\\\?\\"
#define UNC_PREFIX                  "\\\\"
#define UNC_LONG_ID                 L"\\\\?\\UNC\\"
#define CUR_DIR_REL_PATH_ID         ".\\"

#define WILDCAR_CHAR_ASTER          '*'
#define WILDCAR_CHAR_QUEMARK        '?'

#define DIR_DOWN                    ".."
#define DIR_UP                      "."

bool ValidatePath(_In_ const TCHAR *sPath, _Out_ bool & bDir)
{
	//If the path contains a wildcards search test only the parent directory

	const TCHAR * sLastElement = _tcsrchr(sPath, _T('\\')); //The last component in the path

	bool bFoundWildCard = false;

	if(sLastElement)
	{
		++sLastElement;
		if(_tcsrchr(sLastElement, _T(WILDCAR_CHAR_ASTER)) || _tcsrchr(sLastElement, _T(WILDCAR_CHAR_QUEMARK))) //If wilcard characters are contained in the last path component
		{
			bFoundWildCard = true;
			--sLastElement;
			const_cast<TCHAR *>(sLastElement)[0] = _T('\0');
		}
	}

	DWORD dwPathAttr = GetFileAttributes(sPath);

	if(dwPathAttr == INVALID_FILE_ATTRIBUTES)
	{
		_com_error sErrorMsg(GetLastError());
		//CProgramError.Set(sErrorMsg.ErrorMessage());
		return false;
	}

	bDir = dwPathAttr & FILE_ATTRIBUTE_DIRECTORY;

	if(bFoundWildCard)
	{
		const_cast<TCHAR *>(sLastElement)[0] = _T('\\');
	}

	return true;
}

void RespondPathComponent(_In_ const TCHAR *pComponent, _Out_ std::vector<CString> & vPathComponents)
{
	const TCHAR *pComponentLimiterR = _tcschr(pComponent, _T('\\'));

	const TCHAR *pComponentLimiterL = _tcschr(pComponent, _T('/'));

	const TCHAR *pComponentLimiter = NULL;

	if(pComponentLimiterR && pComponentLimiterL)
		pComponentLimiter = (pComponentLimiterR > pComponentLimiterL ? pComponentLimiterL : pComponentLimiterR);
	else
		pComponentLimiter = (pComponentLimiterR ? pComponentLimiterR : pComponentLimiterL);

	if(pComponentLimiter)
	{
		size_t szComponent = pComponentLimiter - pComponent;
		if(szComponent)
		{
			CString sTemp;
			sTemp.SetString(pComponent, szComponent);
			vPathComponents.push_back(sTemp);
		}
		++pComponentLimiter;
		RespondPathComponent(pComponentLimiter, vPathComponents);
	}
	else
	{
		size_t szLastComp = _tcslen(pComponent);
		if(szLastComp)
		{
			CString sTemp;
			sTemp.SetString(pComponent, szLastComp);
			vPathComponents.push_back(sTemp);
		}
	}
}

size_t FixUpPathComponents(_Out_ std::vector<CString> & vPathComponents, _In_ const TCHAR *pPathComponents)
{
	RespondPathComponent(pPathComponents, vPathComponents);

	size_t szNumComponents = vPathComponents.size();

	for(size_t i(0); i < szNumComponents; ++i)
	{
		if(vPathComponents[i] == _T(DIR_DOWN))
		{
			vPathComponents.erase(vPathComponents.begin() + i); //Remove the current component
			--szNumComponents;
			if(i > 0)
			{
				vPathComponents.erase(vPathComponents.begin() + i - 1);
				--szNumComponents;
			}
		}
		else if(vPathComponents[i] == _T(DIR_UP))
		{
			if( (i + 1) < szNumComponents )
			{
				vPathComponents.erase(vPathComponents.begin() + i + 1);
				--szNumComponents;
			}
			vPathComponents.erase(vPathComponents.begin() + i); //Remove the current component
			--szNumComponents;
		}
	}

	return szNumComponents;
}

//Note that sCurrentDir is appended to all relative paths (nomatter the drive letter) - it needs to be a full path, not ending with '\\'
bool ExpandAndFixUpPath(_Inout_ CString & sPath, _In_opt_ const CString sCurrentDir)
{
	const size_t InPathStrSz = sPath.GetLength();

	if(!InPathStrSz) //Invalid path
		return false;

	//sPath.LockBuffer(); //Lock character buffer

	const TCHAR *PathBuffer = sPath.GetString(); //Retrieve the buffer

	if(InPathStrSz > 1) //To suppose the path is full it needs to have at lease 2 characters
	{
		if(_tcsstr(PathBuffer, _T(UNC_PREFIX)) == PathBuffer) //If the path begin with UNC_PREFIX
		{
			std::vector<CString> vPathComponents;

			size_t nComponents;

			if((nComponents = FixUpPathComponents(vPathComponents, &PathBuffer[2])) < 2) //A UNC path needs at leas two elements
				return false;

			sPath = _T('\\');

			for(size_t i(0); i < nComponents; ++i)
			{
				sPath += _T('\\');
				sPath += vPathComponents[i];
			}

			return true;
		}
		else if(PathBuffer[1] == _T(':')) //If the path begin with a disk designator
		{
			std::vector<CString> vPathComponents;

			if(FixUpPathComponents(vPathComponents, &PathBuffer[2]))
			{
				if(PathBuffer[2] == _T('\\') || PathBuffer[2] == _T('/'))
				{
					sPath.SetString(PathBuffer, 2);

					for(size_t i(0); i < vPathComponents.size(); ++i)
					{
						sPath += _T('\\');
						sPath += vPathComponents[i];
					}
				}
				else
				{
					sPath = sCurrentDir;

					for(size_t i(0); i < vPathComponents.size(); ++i)
					{
						sPath += _T('\\');
						sPath += vPathComponents[i];
					}
				}
			}
			else
			{
				sPath.SetString(PathBuffer, 2);
				sPath += _T('\\');
			}
			return true;
		}
	}

	std::vector<CString> vPathComponents;

	const TCHAR *pComponentsBegin = (_tcsstr(PathBuffer, _T(CUR_DIR_REL_PATH_ID)) == PathBuffer ? &PathBuffer[((sizeof(_T(CUR_DIR_REL_PATH_ID)) / sizeof(TCHAR)) - 1)] : PathBuffer);

	FixUpPathComponents(vPathComponents, pComponentsBegin);

	sPath = sCurrentDir;

	for(size_t i(0); i < vPathComponents.size(); ++i)
	{
		sPath += _T('\\');
		sPath += vPathComponents[i];
	}

	return true;
}

bool NormalizePath(_Inout_ CString & sPath, _In_ const CString sCurrentDir, _Out_opt_ bool *bDir = NULL, _In_ bool bValidate = false)
{
	
	if(!ExpandAndFixUpPath(sPath, sCurrentDir)) //Extend the path to it's full form
		return false;

	size_t LongPathLen = sPath.GetLength();

	const TCHAR *pPathBuf = sPath.GetString();

#ifdef _UNICODE

	if(LongPathLen <= (MAX_PATH - 1)) //If the path is longer in the range of MAX_PATH return it directly
	{
		if(bValidate)
			if(!ValidatePath(pPathBuf, *bDir)) //Validate path before returning it
				return false;

		return true;
	}

	bool bIsUNCPath = _tcsstr(pPathBuf, _T(UNC_PREFIX)) == pPathBuf;

	if(!bIsUNCPath)
	{
		if(LongPathLen > (PATHCCH_MAX_CCH - 1 - ((sizeof(LONG_PATH_ID) / sizeof(WCHAR)) - 1))) //If have no space to store the prefix fail
		{
			//CProgramError.Set(_T("Path too long!"));
			return false;
		}
	
	
		CString sPathTmp = LONG_PATH_ID;

		sPathTmp += pPathBuf;

		if(bValidate)
			if(!ValidatePath(sPathTmp.GetString(), *bDir)) //Validate path before returning it
				return false;
	
		sPath = sPathTmp;

		return true;
	}
	else
	{
		if( LongPathLen > ( PATHCCH_MAX_CCH - 1 - ((sizeof(UNC_LONG_ID) / sizeof(WCHAR)) - 1) + ((sizeof(_T(UNC_PREFIX)) / sizeof(WCHAR)) - 1) ) ) //If have no space to store the prefix fail
		{
			//CProgramError.Set(_T("Path too long!"));
			return false;
		}
	
	
		CString sPathTmp = UNC_LONG_ID;

		sPathTmp += &pPathBuf[2];

		if(bValidate)
			if(!ValidatePath(sPathTmp.GetString(), *bDir)) //Validate path before returning it
				return false;
	
		sPath = sPathTmp;

		return true;
	}

#else

	if(bValidate)
		if(!ValidatePath(pPathBuf, *bDir)) //Validate path before returning it
			return false;

	return true;

#endif
}


The main function that you'll want to use is NormalizePath, as sPath input/output parameter, receiving the abnormal path and replacing it with the normal one, sCurrentDir parameter receives the directory supposed as current (used for safe-thread reasons) and if bValidate is set to true, the bDir must receive a pointer to a bool that will be set if the path points a directory.

I'll be happy if you can suggest code changes!

*info - http://msdn.microsoft.com/en-us/library/windows/desktop/hh707084(v=vs.85).aspx
Last edited on
closed account (Dy7SLyTq)
This is a really great post, and I'm book marking it. I just have one not picky auggestion... post this in the lounge because the windows section is meant more for questions. But anyways... this is really informative
I have not tested the code, but I have few observations:
1 - use of CString requires MFC/ATL/WTL, not available for free
2 - use of TCHAR instead of directly Unicode only for new programs ? Let's be serious ...

About the subject itself, there are winapi functions that accepts long paths even prior to windows 8, available only in their Unicode variants, like CreateFileW, FindFirstFileW, CreateDirectoryW, etc.
It would help if your explained what your code was doing, how it does it and how to use it.

BTW, the \\\\? stuff bypasses the parser and passes the string to the device driver. It's necessary when you need to bypass the user lib parser, for example when you want to detele a file that has a reserverd name (like prn or aux).
@DTSCode

Thanks, if some moderator can do that I give him my permission.

@modoran

Sorry, but this code was written especially for my program needs and they are really supporting Unicode and Ansi character sets. Yes those functions ( CreateFileW, FindFirstFileW, CreateDirectoryW) accepts long paths but a long path can only be Fully Qualified - It can't be a relative or using a navigation elements. Also the path must begin with the special characters ("\\\\?\\") and it must be no longer than PATHCCH_MAX_CCH (a standard defined in the new SDK).

@kbw

I will definetly write an explanation of my code but I don't understand the rest of your post.
closed account (jwkNwA7f)
Thanks, if some moderator can do that I give him my permission.

You can do that yourself. Click edit on your OP and select 'Lounge' instead of Windows programming.

EDIT: click edit on your OP and then edit topic at the top left, then the rest.
Last edited on
closed account (Dy7SLyTq)
actually you can move it yourself. just edit the thread and when it says location (or something like that i cant really remember) windows programming just change it to lounge
I done it. Thanks!
Topic archived. No new replies allowed.