-
3 Attachment(s)
[RESOLVED] FindFirstFile(W) Unicode problem with the windows non-unicode settings
Dev-PC: Windows 7, VB6SP6
With my app i use the API FindFirstFileWide to enum files with unicode characters in the name from a folder.
I get the correct unicode file names if the regional settings for non-unicode programs are set to "English" or another non-unicode-language:
Attachment 182007
If i change the regional settings for non-unicode programs to "Chinese" the file names from the API FindFirstFileWide are all wrong:
Attachment 182006
Does anyone know how to fix this problem and get the real file names?
Attached you will find a test project with the chinese file names.
The project will need VBCCR17.OCX to display the unicode characters in the textbox.
-
Re: FindFirstFile(W) Unicode problem with the windows non-unicode settings
I couldn't test your program but I did look over the code and saw this:-
Code:
sFileName$ = TrimNull(StrConv(WFDW.cFileName$, vbFromUnicode))
This looks a bit suspicious because it seems to be assuming that a VB6 String is an ANSI String which it is not. A VB6 String is actually a COM type called a BSTR which is Unicode String. There should be no need for that conversion. Assuming that FindFirstFileW is filling that member with a Unicode String, what your code would actually be doing is converting that String to an ANSI String that conforms to the current codepage of the system and then it casts it to a BSTR. This process could screw up the String irreversibly.
This is just my guess though. I can't run your program because of some component missing or something and I'm not gonna risk screwing up my system settings by fiddling around with the locale settings.
Try changing the above code to this:-
Code:
sFileName$ = TrimNull(WFDW.cFileName$)
See if that works.
-
Re: FindFirstFile(W) Unicode problem with the windows non-unicode settings
Oh I forgot to mention. There is one other possible point failure, the API call itself. I believe that VB6 marshals all Strings as ANSI Strings whenever it calls into the Win32 API. It's also possible that it's performing an ANSI conversion when it returns that WIN32_FIND_DATA structure. It won't matter if you're using the wide version of the API call. The way you get around that is by passing an array instead:-
Code:
Private Type WIN32_FIND_DATA_WIDE
dwFileAttributes As Long
ftCreationTime As FILETIME
ftLastAccessTime As FILETIME
ftLastWriteTime As FILETIME
nFileSizeHigh As Long
nFileSizeLow As Long
dwReserved0 As Long
dwReserved1 As Long
cFileName(MAX_PATH_WIDE) As Byte
cAlternate As String * 28
End Type
Then in your code you do this:-
Replace:-
Code:
sFileName$ = TrimNull(StrConv(WFDW.cFileName$, vbFromUnicode))
with this:-
Code:
sFileName$ = WFDW.cFileName
sFileName$ = TrimNull(sFileName$)
-
1 Attachment(s)
Re: FindFirstFile(W) Unicode problem with the windows non-unicode settings
Quote:
Originally Posted by
Niya
This looks a bit suspicious because it seems to be assuming that a VB6 String is an ANSI String which it is not. A VB6 String is actually a COM type called a BSTR which is Unicode String. There should be no need for that conversion.
I guess you never worked with this kind of Wide-APIs.
This conversion is needed because each character at the mem block of the filename is stored with double bytes and need to be converted to a VB unicode string:
Attachment 182011
-
Re: FindFirstFile(W) Unicode problem with the windows non-unicode settings
Code:
cFileName(MAX_PATH_WIDE) As Byte
I tried your code change suggestion above and now i get the correct chinese file names on a computer with a chinese non-unicode program setting!
Thanks a lot for the hint!
-
1 Attachment(s)
Re: FindFirstFile(W) Unicode problem with the windows non-unicode settings
Quote:
Originally Posted by
Mith
I guess you never worked with this kind of Wide-APIs.
I've can write string functions that work properly with Unicode in pure assembly. I know what I'm talking about.
Also, here is a more correct implementation of what you are doing:-
Code:
Option Explicit
Private Const MAX_PATH_WIDE = 520
Private Const INVALID_HANDLE_VALUE = -1
Private Type FILETIME
dwLowDateTime As Long
dwHighDateTime As Long
End Type
Private Type WIN32_FIND_DATA_WIDE
dwFileAttributes As Long
ftCreationTime As FILETIME
ftLastAccessTime As FILETIME
ftLastWriteTime As FILETIME
nFileSizeHigh As Long
nFileSizeLow As Long
dwReserved0 As Long
dwReserved1 As Long
cFileName(MAX_PATH_WIDE) As Byte
cAlternate(28) As Byte
dwFileType As Long
dwCreatorType As Long
wFinderFlags As Integer
End Type
Private Declare Function FindFirstFileWide Lib "kernel32" Alias "FindFirstFileW" (ByVal lpFileName As Long, lpFindFileData As WIN32_FIND_DATA_WIDE) As Long
Private Declare Function FindNextFileWide Lib "kernel32" Alias "FindNextFileW" (ByVal hFindFile As Long, lpFindFileData As WIN32_FIND_DATA_WIDE) As Long
Private Declare Function FindClose Lib "kernel32" (ByVal hFindFile As Long) As Long
Private Sub Form_Load()
GetFileNames
End Sub
Private Sub GetFileNames()
Dim hFile As Long
Dim WFDW As WIN32_FIND_DATA_WIDE
Dim sFileName As String
hFile = FindFirstFileWide(StrPtr("D:\UnicodeFileNames\*.*"), WFDW)
If hFile <> INVALID_HANDLE_VALUE Then
Do
sFileName = TrimNull(WFDW.cFileName)
If (WFDW.dwFileAttributes And vbDirectory) <> vbDirectory Then
Text1.Text = Text1.Text & sFileName & vbCrLf
End If
Loop While FindNextFileWide(hFile, WFDW)
Call FindClose(hFile)
End If
End Sub
Private Function TrimNull(ByVal s As String) As String
Dim fn As Long
fn = InStr(1, s, ChrW(0), vbTextCompare)
TrimNull = Mid$(s, 1, fn - 1)
End Function
I've tested this and it works with Unicode file names correctly:-
https://www.vbforums.com/images/ieimages/2021/08/1.png
I've also corrected a serious problem with your original code. The WIN32_FIND_DATA_WIDE data structure in your original code was defined as:-
Code:
Private Type WIN32_FIND_DATA_WIDE
dwFileAttributes As Long
ftCreationTime As FILETIME
ftLastAccessTime As FILETIME
ftLastWriteTime As FILETIME
nFileSizeHigh As Long
nFileSizeLow As Long
dwReserved0 As Long
dwReserved1 As Long
cFileName As String * MAX_PATH_WIDE
cAlternate As String * 28
End Type
It's missing the last 3 fields which could be disastrous. Those missing fields come up to 10 bytes worth of data which means every time you call the API with that structure, 10 bytes of data is being written to a memory addresses it's not supposed to be writing to. This could result in an access violation error which would cause Windows to terminate your application. It may not happen 95% of the time you run that program but it could happen. You really want to fix this before you put it out into the wild.
Here is the full source of my version.
Additional credit to Elroy for his Unicode TextBox control.
-
Re: FindFirstFile(W) Unicode problem with the windows non-unicode settings
the last part of your WIN32_FIND_DATA_WIDE type must be new added by MS:
Code:
dwFileType As Long
dwCreatorType As Long
wFinderFlags As Integer
i never saw this 3 properties before and i cant find any information about them at the internet.
The internet is full with the old declaration of the WIN32_FIND_DATA type.
I guess MS changed the internal handling of the API call to support the old and the new style of this type.
I also checked the official docs from MS for this type: the 3 new properties are listed but without any description.
Im sure older windows versions will not support them!
Take care if you use the new properties and run your app with older windows versions!
-
Re: FindFirstFile(W) Unicode problem with the windows non-unicode settings
https://docs.microsoft.com/en-us/win...n32_find_dataw
FindFirstFileW expects this version of the structure to be passed. It says this in the documentation for the API:-
Code:
HANDLE FindFirstFileW(
LPCWSTR lpFileName,
LPWIN32_FIND_DATAW lpFindFileData
);
You have to play close attention to the type:-
LPWIN32_FIND_DATAW
-
Re: FindFirstFile(W) Unicode problem with the windows non-unicode settings
Quote:
Originally Posted by
Mith
the 3 new properties are listed but without any description.
If it came down to it and I really needed to know what those fields are for, I'd ask on a site like StackOverflow. They usually have people with deep knowledge that can answer these kinds of questions but most of the time, you only really need to know that they are there so you can account for them in your code.
-
Re: FindFirstFile(W) Unicode problem with the windows non-unicode settings
I checked my installed MSDN Library for the WIN32_FIND_DATA type and i found this:
Code:
WIN32_FIND_DATA
The WIN32_FIND_DATA structure describes a file found by the FindFirstFile, FindFirstFileEx, or FindNextFile function.
typedef struct _WIN32_FIND_DATA { // wfd
DWORD dwFileAttributes;
FILETIME ftCreationTime;
FILETIME ftLastAccessTime;
FILETIME ftLastWriteTime;
DWORD nFileSizeHigh;
DWORD nFileSizeLow;
DWORD dwReserved0;
DWORD dwReserved1;
TCHAR cFileName[ MAX_PATH ];
TCHAR cAlternateFileName[ 14 ];
} WIN32_FIND_DATA;
MS changed the type without to tell the devs that they can use both types, the old and the new style...
I will stay with the old style...
-
Re: FindFirstFile(W) Unicode problem with the windows non-unicode settings
Quote:
Originally Posted by
Mith
I checked my installed MSDN Library for the WIN32_FIND_DATA type and i found this:
Code:
WIN32_FIND_DATA
The WIN32_FIND_DATA structure describes a file found by the FindFirstFile, FindFirstFileEx, or FindNextFile function.
typedef struct _WIN32_FIND_DATA { // wfd
DWORD dwFileAttributes;
FILETIME ftCreationTime;
FILETIME ftLastAccessTime;
FILETIME ftLastWriteTime;
DWORD nFileSizeHigh;
DWORD nFileSizeLow;
DWORD dwReserved0;
DWORD dwReserved1;
TCHAR cFileName[ MAX_PATH ];
TCHAR cAlternateFileName[ 14 ];
} WIN32_FIND_DATA;
OMG....TCHAR! This thing actually uses TCHAR.
Quote:
Originally Posted by
Mith
I will stay with the old style...
Don't do this. I promise you, those missing 10 bytes will byte you in the ass eventually. The fact that this structure uses TCHARs in it's definition means this wasn't made to target any OS past Windows 98. If your program is not going to be on Windows 95/98, ME or any of those dinosaurs, use the current structure definition. It would be valid all the way back to XP and perhaps even Windows NT where Windows first became a Unicode OS. Windows 95/98 etc were ANSI which is why you see that horribly outdated TCHAR typedef.
-
Re: FindFirstFile(W) Unicode problem with the windows non-unicode settings
I did a test with the "new" WIN32_FIND_DATA type and scanned all files recursively inside the folder "C:\Windows" on WinXPSP3 and Win10.
The result: the values for the properties dwFileType, dwCreatorType and wFinderFlags are always 0...
btw, i searched the web for WIN32_FIND_DATA and checked several websites: noone is using the new declaration with the 3 new properties at the end!
I don't know why MS added this 3 new properties without any statement at the docs but i will stick with the old style of the WIN32_FIND_DATA declaration.
-
Re: FindFirstFile(W) Unicode problem with the windows non-unicode settings
-
Re: FindFirstFile(W) Unicode problem with the windows non-unicode settings
i got an answer about these 3 properties:
You should use these properties only if you develop and compile the app for a MAC computer!
See stackoverflow - WIN32_FIND_DATA
See stackoverflow - MAC
As you can see these 3 properties should not be used if you develop and compile for windows machines!
-
Re: FindFirstFile(W) Unicode problem with the windows non-unicode settings
Ah ok. Didn't see that one coming. Good detective work.
-
Re: FindFirstFile(W) Unicode problem with the windows non-unicode settings
cFileName As String * MAX_PATH_WIDE vs cFileName(MAX_PATH_WIDE) As Byte + StrConv(cFileName, vbFromUnicode) completely obfuscates the root cause of the problem.
There is nothing wrong with cFileName As String * MAX_PATH_WIDE per se but the machine it fails on has most probably a weirdly configured locales for the user (numbers and dates format etc.) vs for the system one (the locale an admin sets for non-Unicode applications).
StrConv(cFileName, vbFromUnicode) has a 3-rd parameter for locale ID which if not specified defaults to LOCALE_USER_DEFAULT = &H400 while automagic ANSI<->Unicode conversion uses LOCALE_SYSTEM_DEFAULT = &H800
Mismatching user vs system locale is IMO a very plausible reason for the original failure.
I might be completely wrong with the above hypothesis though. I'm personally using cFileName As String * MAX_PATH_WIDE declaration with FindFirstFileW API and I'm trying to figure out if I should be worried about it being incompatible or could this failure be attributed unsupported locale config.
cheers,
</wqw>
-
Re: FindFirstFile(W) Unicode problem with the windows non-unicode settings
hi wqweto,
cFileName As String * MAX_PATH_WIDE can be a problem if the user set the windows non-unicode program settings to a unicode language like chinese etc.
Before i changed my code everywhere to cFileName(MAX_PATH_WIDE) As Byte and i tried to fix this problem by using a non-unicode localeID with the StrConv function but it doesnt helped to fix the problem.
Now i use everywhere cFileName(MAX_PATH_WIDE) As Byte with FindFirstFileW and my chinese customer have no problem anymore with my app!
-
Re: FindFirstFile(W) Unicode problem with the windows non-unicode settings
Quote:
Originally Posted by
Mith
cFileName As String * MAX_PATH_WIDE can be a problem if the user set the windows non-unicode program settings to a unicode language like chinese etc.
First time I hear about "unicode language". Probably you mean multi-byte but still this cannot be the reason for the original failure because both StrConv and API declares string conversion use the same WideCharToMultiByte family of API functions. The difference in output must come from both using different arguments to these API functions hence my hypothesis above.
cheers,
</wqw>
-
Re: FindFirstFile(W) Unicode problem with the windows non-unicode settings
I'm not gonna pretend I know more about VB6's internals than you wqweto but the reason I even suggested using Byte arrays in the first place was because I seem to remember VB6 always marshalling Strings from a COM BSTR to a null terminated ANSI String whenever you make API calls. Now ANSI is sensitive to code pages, meaning a single ANSI String can mean different thing depending on the code page. If you use the wrong code page when converting to an ANSI String, problems can arise. Unicode doesn't suffer from this limitation. A Unicode code point is universal, it always means the same thing. For example, U+0409 will always be Љ, whether you're on a Greek computer, Chinese computer or an English computer. All Windows versions descended from Windows NT like XP use Unicode internally. I used Byte arrays to prevent VB6 from interfering with the Unicode Strings between the API calls.
Now you also mentioned WideCharToMultiByte family of APIs. Even this is a problem. The MultiByte part of that is referring to MBCS which encompasses String formats similar to UTF-8 but unlike UTF-8, which is a Unicode String format, MBCS formats are sensitive to code pages. WideCharToMultiByte converts a String from Windows' native UTF-16 to an MBCS format.
In any case, when writing apps for the modern world, you want to avoid all these older String formats and stick with Unicode wherever possible. This is the principle I was guided by when I was attempting to help him solve this problem. If you have a Unicode String and you're passing it to a Unicode API, you NEVER EVER want to involve older String formats like ANSI or MBCS, hence the use of Byte arrays.
-
Re: FindFirstFile(W) Unicode problem with the windows non-unicode settings
@Niya: As I was writing a reply to you it just dawned me what the real problem with OP actually is with the API declare.
In fact cFileName As String * MAX_PATH_WIDE is completely fine, no need to use a byte-array and StrConv.
It is the FindFirstFileWide declare that has to use ByVal lpFindFileData As Long and subsequently VarPtr(WFDW) at the callsite to *prevent* any ANSI<->Unicode conversion from kicking in with the fixed-length cFileName string.
That's what I actual do with no problems in any locale ("unicode" or not) so for me the case is closed with a satisfactory explanation :-))
cheers,
</wqw>
-
Re: FindFirstFile(W) Unicode problem with the windows non-unicode settings
Quote:
Originally Posted by
wqweto
@Niya: As I was writing a reply to you it just dawned me what the real problem with OP actually is with the API declare.
In fact cFileName As String * MAX_PATH_WIDE is completely fine, no need to use a byte-array and StrConv.
It is the FindFirstFileWide declare that has to use ByVal lpFindFileData As Long and subsequently VarPtr(WFDW) at the callsite to *prevent* any ANSI<->Unicode conversion from kicking in with the fixed-length cFileName string.
That's what I actual do with no problems in any locale ("unicode" or not) so for me the case is closed with a satisfactory explanation :-))
cheers,
</wqw>
This is actually a great suggestion. That solution didn't occur to me.
One thing bugs me about this though. A BSTR and a WCHAR are two different things. They are both UTF-16 encoded Strings, yes but a BSTR is prepended with a 32 bit value that represents the length of the String. A WCHAR has no header, it's just null terminated. What scares me here is that if you pass a BSTR like that, the API can overwrite that 32 length value because it expects a WCHAR. This can effectively corrupt the String. Now, you say it works for you but I'm curious as to why. You have an explanation?
EDIT:
Would it be safe to assume that VB6 doesn't prepend a length header when you declare a fixed length String field in a UDT? That's the only explanation I can think of as a reason that works.
-
Re: FindFirstFile(W) Unicode problem with the windows non-unicode settings
BTW: Always check the Windows SDK headers for something like struct definitions. It often contains details you don't find elsewhere:
Code:
typedef struct _WIN32_FIND_DATAW {
DWORD dwFileAttributes;
FILETIME ftCreationTime;
FILETIME ftLastAccessTime;
FILETIME ftLastWriteTime;
DWORD nFileSizeHigh;
DWORD nFileSizeLow;
DWORD dwReserved0;
DWORD dwReserved1;
_Field_z_ WCHAR cFileName[ MAX_PATH ];
_Field_z_ WCHAR cAlternateFileName[ 14 ];
#ifdef _MAC
DWORD dwFileType;
DWORD dwCreatorType;
WORD wFinderFlags;
#endif
} WIN32_FIND_DATAW, *PWIN32_FIND_DATAW, *LPWIN32_FIND_DATAW;
Fixed-length String types are not BSTRs.
-
Re: FindFirstFile(W) Unicode problem with the windows non-unicode settings
Code:
Option Explicit
Private Type T
A As Long
B As String * 16
C As Long
End Type
Private Sub Form_Load()
Dim T As T
Debug.Print Len(T), LenB(T) '<--- Always use LenB() for measuring UDT byte count.
End Sub
-
Re: FindFirstFile(W) Unicode problem with the windows non-unicode settings
Quote:
Originally Posted by
dilettante
Fixed-length String types are not BSTRs.
Ah, I thought as much. I just didn't want to make that assumption blindly.
@Mith
If you use wqweto's suggestion remember to halve the String length values. MAX_PATH should be 260 and cAlternateFileName should be of length 14. Since his method allows you to use Strings instead of Byte arrays, VB6 will take care of allocating the correct amount of memory.
-
Re: FindFirstFile(W) Unicode problem with the windows non-unicode settings
Quote:
Originally Posted by
dilettante
Code:
Option Explicit
Private Type T
A As Long
B As String * 16
C As Long
End Type
Private Sub Form_Load()
Dim T As T
Debug.Print Len(T), LenB(T) '<--- Always use LenB() for measuring UDT byte count.
End Sub
Ah yes. This answers the question perfectly.
-
Re: FindFirstFile(W) Unicode problem with the windows non-unicode settings
Btw, FindFirstFile does not accept struct size neither as parameter nor as WIN32_FIND_DATA (first) member. LenB(WFD) should be correct when VarPtr(WFD) is used at callside, otherwise Len(WFD) calculates size *after* Unicode<->ANSI conversion on fixed-length strings.
cFileName As String * MAX_PATH is exactly TCHAR cFileName[MAX_PATH] i.e. a fixed-size array so being zero-terminated is by convention (not required by the data-type) to be usable w/ C/C++ string functions.
Fixed-size strings in VB6 are not length-prefixed the way fixed-size arrays are not prefixed in C/C++ so these are 100% compatible with fixed-size arrays of chars or wchar_t's (depending on VarPtr being used at callsize or not).
cheers,
</wqw>
-
Re: FindFirstFile(W) Unicode problem with the windows non-unicode settings
Here is an alternate version of the code I posted in post #6 using wqweto's method of preventing interference with the Strings:-
Code:
Option Explicit
Private Const MAX_PATH = 260
Private Const INVALID_HANDLE_VALUE = -1
Private Type FILETIME
dwLowDateTime As Long
dwHighDateTime As Long
End Type
Private Type WIN32_FIND_DATA_WIDE
dwFileAttributes As Long
ftCreationTime As FILETIME
ftLastAccessTime As FILETIME
ftLastWriteTime As FILETIME
nFileSizeHigh As Long
nFileSizeLow As Long
dwReserved0 As Long
dwReserved1 As Long
cFileName As String * MAX_PATH
cAlternate As String * 14
dwFileType As Long
dwCreatorType As Long
wFinderFlags As Integer
End Type
Private Declare Function FindFirstFileWide Lib "kernel32" Alias "FindFirstFileW" (ByVal lpFileName As Long, ByVal lpFindFileData As Long) As Long
Private Declare Function FindNextFileWide Lib "kernel32" Alias "FindNextFileW" (ByVal hFindFile As Long, ByVal lpFindFileData As Long) As Long
Private Declare Function FindClose Lib "kernel32" (ByVal hFindFile As Long) As Long
Private Sub Form_Load()
GetFileNames
End Sub
Private Sub GetFileNames()
Dim hFile As Long
Dim WFDW As WIN32_FIND_DATA_WIDE
Dim sFileName As String
hFile = FindFirstFileWide(StrPtr(App.Path & "\UnicodeFileNames\*.*"), VarPtr(WFDW))
If hFile <> INVALID_HANDLE_VALUE Then
Do
sFileName = TrimNull(WFDW.cFileName)
If (WFDW.dwFileAttributes And vbDirectory) <> vbDirectory Then
Text1.TextUnicode = Text1.TextUnicode & sFileName & vbCrLf
End If
Loop While FindNextFileWide(hFile, VarPtr(WFDW))
Call FindClose(hFile)
End If
End Sub
Private Function TrimNull(ByVal s As String) As String
Dim fn As Long
fn = InStr(1, s, ChrW(0), vbTextCompare)
TrimNull = Mid$(s, 1, fn - 1)
End Function
Proof it still works and handles the Unicode correctly:-
https://www.vbforums.com/images/ieimages/2021/08/3.png
-
Re: FindFirstFile(W) Unicode problem with the windows non-unicode settings
Quote:
Originally Posted by
wqweto
Fixed-size strings in VB6 are not length-prefixed the way fixed-size arrays are not prefixed in C/C++ so these are 100% compatible with fixed-size arrays of chars or wchar_t's (depending on VarPtr being used at callsize or not).
This makes fixed lengths Strings in VB6 functionally identical to WCHAR/wchar_t. This actually makes perfect sense. They probably designed it this way deliberately to work with the WCHAR type that is prevalent in the Win32 API.
-
Re: FindFirstFile(W) Unicode problem with the windows non-unicode settings
I just randomly stumbled onto this when it showed up in my YouTube feed. Was from one of the channels I watch on YouTube. A segment of it speaks directly about some of the historical stuff I mentioned earlier concerning Windows and it's relationship to Unicode.
The relevant portion is at 2:33. For some reason the Forum is not working correctly with the time code. It just plays from the start.
https://youtu.be/p-sprvJX07E?t=153
It's not really relevant to the OP's problem but I felt it is relevant to the Unicode discussion itself. It's really nice to hear an actual Microsoft Windows engineer talking about Windows and what actually goes on.
-
Re: FindFirstFile(W) Unicode problem with the windows non-unicode settings
Quote:
Originally Posted by
wqweto
In fact cFileName As String * MAX_PATH_WIDE is completely fine, no need to use a byte-array and StrConv.
It is the FindFirstFileWide declare that has to use ByVal lpFindFileData As Long and subsequently VarPtr(WFDW) at the callsite to *prevent* any ANSI<->Unicode conversion from kicking in with the fixed-length cFileName string.
Great tip for further optimization!
I changed my code back to "cFileName As String * MAX_PATH_WIDE" and now i use "ByVal lpFindFileData As Long" in combination with "VarPtr(WFDW)".
Works great!
-
Re: FindFirstFile(W) Unicode problem with the windows non-unicode settings
Quote:
Originally Posted by
Mith
Great tip for further optimization!
I changed my code back to "cFileName As String * MAX_PATH_WIDE" and now i use "ByVal lpFindFileData As Long" in combination with "VarPtr(WFDW)".
Works great!
Did you remember to half the MAX_PATH constant?
-
Re: FindFirstFile(W) Unicode problem with the windows non-unicode settings
Quote:
Originally Posted by
Niya
Did you remember to half the MAX_PATH constant?
No, i did not :o
Thanks for the reminder!
-
Re: FindFirstFile(W) Unicode problem with the windows non-unicode settings
i checked my code for other APIs were i use the StrConv-function to get a unicode string:
Code:
Private Declare Function GetComputerNameWIDE Lib "kernel32" Alias "GetComputerNameW" (ByVal lpBuffer As String, nSize As Long) As Long
Public Declare Function GetUserNameWIDE Lib "advapi32.dll" Alias "GetUserNameW" (ByVal lpBuffer As String, ByRef nSize As Long) As Long
Public Declare Function GetEnvironmentStringsWide Lib "kernel32" Alias "GetEnvironmentStringsW" () As Long
Code:
Public Function GetCurrentUserNameWIDE() As String
GetCurrentUserNameWIDE = String$(512, vbNullChar)
GetUserNameWIDE GetCurrentUserNameWIDE, 512
GetCurrentUserNameWIDE = TrimNull(StrConv(GetCurrentUserNameWIDE, vbFromUnicode))
End Function
Code:
Public Function GetLocalComputerNameWIDE() As String
Dim sName As String
Dim lBufferSize As Long
Const MAX_COMPUTERNAME As Long = 16
lBufferSize = MAX_COMPUTERNAME * 2
sName = String$(lBufferSize, vbNullChar)
If GetComputerNameWIDE(sName, lBufferSize) <> 0 Then
GetLocalComputerNameWIDE = TrimNull(StrConv(sName, vbFromUnicode))
End If
End Function
Code:
Public Function GetAllEnvironmentVariables() As String
Dim lngRet As Long
Dim strDest As String
Dim lLen As Long
Dim sReturn As String
lngRet = GetEnvironmentStringsWide
Do
lLen = lstrlen(lngRet)
If lLen = 0 Then Exit Do
strDest = Space$(lLen * 2)
CopyMemory ByVal strDest, ByVal lngRet, lLen * 2
strDest = StrConv(strDest, vbFromUnicode)
sReturn = sReturn & strDest & vbCrLf
lngRet = lngRet + (lLen * 2) + 2
Loop
Call FreeEnvironmentStrings(lngRet)
GetAllEnvironmentVariables = sReturn
End Function
I guess on a windows pc with a chinese GUI language these APIs can return wrong unicode strings too?
Im not sure if its possible to use VarPtr with these APIs to avoid StrConv.
Any suggestions from you guys?
-
Re: FindFirstFile(W) Unicode problem with the windows non-unicode settings
Yea, these are definitely problematic. Passing the String by value to these APIs would trigger ANSI conversions. This is how I'd do the GetComputerName API:-
Code:
Private Declare Function GetComputerNameWIDE Lib "kernel32" Alias "GetComputerNameW" (ByVal lpBuffer As Long, nSize As Long) As Long
Private Function GetComputerName() As String
Dim nm As String
Dim numChars As Long
numChars = 50
nm = Space(numChars)
If GetComputerNameWIDE(StrPtr(nm), numChars) Then
GetComputerName = Left(nm, numChars)
End If
End Function
Private Sub Form_Load()
Dim cname As String
cname = GetComputerName
End Sub
Instead of passing the String by value, I pass a pointer which bypasses the ANSI conversions. This is the same idea proposed by wqweto. Although StrPtr is working on a BSTR it actually passes the address of a String after the BSTR byte length header so it works as a LPWSTR which is what the API GetComputerNameW expects. Do you think you can work out the rest on your own? Or you want me to help with those too?
-
Re: FindFirstFile(W) Unicode problem with the windows non-unicode settings
Quote:
Originally Posted by
Niya
Do you think you can work out the rest on your own? Or you want me to help with those too?
I only have problems with the API "GetEnvironmentStringsWide" because there are no parameters.
The string variable will be filled with "CopyMemory"...
-
Re: FindFirstFile(W) Unicode problem with the windows non-unicode settings
Quote:
Originally Posted by
Mith
I only have problems with the API "GetEnvironmentStringsWide" because there are no parameters.
The string variable will be filled with "CopyMemory"...
This one was a little tricky because of the weird format of the environment block. This is what I came up with for this:-
Code:
Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (ByVal lpDest As Long, ByVal lpSrc As Long, ByVal count As Long)
Private Declare Function GetEnvironmentStringsWide Lib "kernel32" Alias "GetEnvironmentStringsW" () As Long
Private Declare Function FreeEnvironmentStringsWide Lib "kernel32" Alias "FreeEnvironmentStringsW" (ByVal ptr As Long) As Long
Private Function ReadByte(ByVal pointer As Long) As Byte
Dim b As Byte
CopyMemory VarPtr(b), pointer, 1
ReadByte = b
End Function
Public Function GetAllEnvironmentVariables() As String
Dim ptr As Long
ptr = GetEnvironmentStringsWide()
If ptr <> 0 Then
Dim readPos As Long
Dim charCount As Long
Dim strn As String
Dim b As Long
readPos = ptr
'Look for the double null terminator
Do Until ReadByte(readPos) = 0 And ReadByte(readPos + 1) = 0 And ReadByte(readPos + 2) = 0 And ReadByte(readPos + 3) = 0
charCount = charCount + 1 'Counts the number of characters in the entire string including embedded nulls
readPos = readPos + 2 'Next character
Loop
strn = Space(charCount) 'Allocate a String to hold the entire thing
CopyMemory StrPtr(strn), ptr, charCount * 2 'Copy the memory to the VB String
GetAllEnvironmentVariables = Replace(strn, ChrW(0), vbCrLf) 'Convert embedded nulls to newline characters and return it
If Not CBool(FreeEnvironmentStringsWide(ptr)) Then
Err.Raise 9000, "", "FreeEnvironmentString failed"
End If
End If
End Function
Private Sub Form_Load()
Debug.Print GetAllEnvironmentVariables
End Sub
This one is safe from ANSI conversions since I perform the copy on pointers instead of passing Strings around.
Note that I didn't put a lot of thought into efficiency while writing that. That code essentially sweeps the entire block a total of 3 times, once to measure the length of the block, a second time to copy the block and a 3rd time to replace all the embedded nulls with newline characters. This entire process could be reduced to a single sweep but I wanted the code to be easy to understand so I didn't bother to optimize it.
-
Re: FindFirstFile(W) Unicode problem with the windows non-unicode settings
Here is a more optimized version that does the sweep only once:-
Code:
Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (ByVal lpDest As Long, ByVal lpSrc As Long, ByVal count As Long)
Private Declare Function GetEnvironmentStringsWide Lib "kernel32" Alias "GetEnvironmentStringsW" () As Long
Private Declare Function FreeEnvironmentStringsWide Lib "kernel32" Alias "FreeEnvironmentStringsW" (ByVal ptr As Long) As Long
Private Function ReadUTF16Char(ByVal pointer As Long) As String
ReadUTF16Char = Space(1)
CopyMemory StrPtr(ReadUTF16Char), pointer, 2
End Function
Public Function GetAllEnvironmentVariables2() As String
Dim ptr As Long
ptr = GetEnvironmentStringsWide()
If ptr <> 0 Then
Dim readPos As Long
Dim char As String
readPos = ptr
Do
char = ReadUTF16Char(readPos)
If char <> ChrW(0) Then
GetAllEnvironmentVariables2 = GetAllEnvironmentVariables2 & char
Else
If ReadUTF16Char(readPos + 2) = ChrW(0) Then
Exit Do
Else
GetAllEnvironmentVariables2 = GetAllEnvironmentVariables2 & vbCrLf
End If
End If
readPos = readPos + 2
Loop
If Not CBool(FreeEnvironmentStringsWide(ptr)) Then
Err.Raise 9000, "", "FreeEnvironmentString failed"
End If
End If
End Function
Private Sub Form_Load()
Debug.Print GetAllEnvironmentVariables2
End Sub
There is more room for optimization here but I don't expect this function would be called in a tight loop or anything like that so personally for me it's optimized enough.
-
Re: FindFirstFile(W) Unicode problem with the windows non-unicode settings
Quote:
Originally Posted by
Niya
Here is a more optimized version that does the sweep only once:
Great, thank you!
I tested your function on a chinese windows with chinese non-unicode settings:
All chinese characters from the environment variables are correctly displayed!
-
Re: FindFirstFile(W) Unicode problem with the windows non-unicode settings