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.
C++ programmers will dismiss you as a cretinous simpleton for your inability to keep track of pointers chained 6 levels deep and Java programmers will pillory you for buying into the evils of Microsoft. Meanwhile C# programmers will get paid just a little bit more than you for writing exactly the same code and VB6 programmers will continue to whitter on about "footprints". - FunkyDexter
There's just no reason to use garbage like InputBox. - jmcilhinney
The threads I start are Niya and Olaf free zones. No arguing about the benefits of VB6 over .NET here please. Happiness must reign. - yereverluvinuncleber
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
C++ programmers will dismiss you as a cretinous simpleton for your inability to keep track of pointers chained 6 levels deep and Java programmers will pillory you for buying into the evils of Microsoft. Meanwhile C# programmers will get paid just a little bit more than you for writing exactly the same code and VB6 programmers will continue to whitter on about "footprints". - FunkyDexter
There's just no reason to use garbage like InputBox. - jmcilhinney
The threads I start are Niya and Olaf free zones. No arguing about the benefits of VB6 over .NET here please. Happiness must reign. - yereverluvinuncleber
Re: FindFirstFile(W) Unicode problem with the windows non-unicode settings
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:
Re: FindFirstFile(W) Unicode problem with the windows non-unicode settings
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:-
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.
C++ programmers will dismiss you as a cretinous simpleton for your inability to keep track of pointers chained 6 levels deep and Java programmers will pillory you for buying into the evils of Microsoft. Meanwhile C# programmers will get paid just a little bit more than you for writing exactly the same code and VB6 programmers will continue to whitter on about "footprints". - FunkyDexter
There's just no reason to use garbage like InputBox. - jmcilhinney
The threads I start are Niya and Olaf free zones. No arguing about the benefits of VB6 over .NET here please. Happiness must reign. - yereverluvinuncleber
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!
C++ programmers will dismiss you as a cretinous simpleton for your inability to keep track of pointers chained 6 levels deep and Java programmers will pillory you for buying into the evils of Microsoft. Meanwhile C# programmers will get paid just a little bit more than you for writing exactly the same code and VB6 programmers will continue to whitter on about "footprints". - FunkyDexter
There's just no reason to use garbage like InputBox. - jmcilhinney
The threads I start are Niya and Olaf free zones. No arguing about the benefits of VB6 over .NET here please. Happiness must reign. - yereverluvinuncleber
Re: FindFirstFile(W) Unicode problem with the windows non-unicode settings
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.
C++ programmers will dismiss you as a cretinous simpleton for your inability to keep track of pointers chained 6 levels deep and Java programmers will pillory you for buying into the evils of Microsoft. Meanwhile C# programmers will get paid just a little bit more than you for writing exactly the same code and VB6 programmers will continue to whitter on about "footprints". - FunkyDexter
There's just no reason to use garbage like InputBox. - jmcilhinney
The threads I start are Niya and Olaf free zones. No arguing about the benefits of VB6 over .NET here please. Happiness must reign. - yereverluvinuncleber
Re: FindFirstFile(W) Unicode problem with the windows non-unicode settings
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.
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.
C++ programmers will dismiss you as a cretinous simpleton for your inability to keep track of pointers chained 6 levels deep and Java programmers will pillory you for buying into the evils of Microsoft. Meanwhile C# programmers will get paid just a little bit more than you for writing exactly the same code and VB6 programmers will continue to whitter on about "footprints". - FunkyDexter
There's just no reason to use garbage like InputBox. - jmcilhinney
The threads I start are Niya and Olaf free zones. No arguing about the benefits of VB6 over .NET here please. Happiness must reign. - yereverluvinuncleber
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.
C++ programmers will dismiss you as a cretinous simpleton for your inability to keep track of pointers chained 6 levels deep and Java programmers will pillory you for buying into the evils of Microsoft. Meanwhile C# programmers will get paid just a little bit more than you for writing exactly the same code and VB6 programmers will continue to whitter on about "footprints". - FunkyDexter
There's just no reason to use garbage like InputBox. - jmcilhinney
The threads I start are Niya and Olaf free zones. No arguing about the benefits of VB6 over .NET here please. Happiness must reign. - yereverluvinuncleber
C++ programmers will dismiss you as a cretinous simpleton for your inability to keep track of pointers chained 6 levels deep and Java programmers will pillory you for buying into the evils of Microsoft. Meanwhile C# programmers will get paid just a little bit more than you for writing exactly the same code and VB6 programmers will continue to whitter on about "footprints". - FunkyDexter
There's just no reason to use garbage like InputBox. - jmcilhinney
The threads I start are Niya and Olaf free zones. No arguing about the benefits of VB6 over .NET here please. Happiness must reign. - yereverluvinuncleber
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.
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
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.
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.
C++ programmers will dismiss you as a cretinous simpleton for your inability to keep track of pointers chained 6 levels deep and Java programmers will pillory you for buying into the evils of Microsoft. Meanwhile C# programmers will get paid just a little bit more than you for writing exactly the same code and VB6 programmers will continue to whitter on about "footprints". - FunkyDexter
There's just no reason to use garbage like InputBox. - jmcilhinney
The threads I start are Niya and Olaf free zones. No arguing about the benefits of VB6 over .NET here please. Happiness must reign. - yereverluvinuncleber
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 :-))
Re: FindFirstFile(W) Unicode problem with the windows non-unicode settings
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.
C++ programmers will dismiss you as a cretinous simpleton for your inability to keep track of pointers chained 6 levels deep and Java programmers will pillory you for buying into the evils of Microsoft. Meanwhile C# programmers will get paid just a little bit more than you for writing exactly the same code and VB6 programmers will continue to whitter on about "footprints". - FunkyDexter
There's just no reason to use garbage like InputBox. - jmcilhinney
The threads I start are Niya and Olaf free zones. No arguing about the benefits of VB6 over .NET here please. Happiness must reign. - yereverluvinuncleber
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
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.
C++ programmers will dismiss you as a cretinous simpleton for your inability to keep track of pointers chained 6 levels deep and Java programmers will pillory you for buying into the evils of Microsoft. Meanwhile C# programmers will get paid just a little bit more than you for writing exactly the same code and VB6 programmers will continue to whitter on about "footprints". - FunkyDexter
There's just no reason to use garbage like InputBox. - jmcilhinney
The threads I start are Niya and Olaf free zones. No arguing about the benefits of VB6 over .NET here please. Happiness must reign. - yereverluvinuncleber
Re: FindFirstFile(W) Unicode problem with the windows non-unicode settings
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
C++ programmers will dismiss you as a cretinous simpleton for your inability to keep track of pointers chained 6 levels deep and Java programmers will pillory you for buying into the evils of Microsoft. Meanwhile C# programmers will get paid just a little bit more than you for writing exactly the same code and VB6 programmers will continue to whitter on about "footprints". - FunkyDexter
There's just no reason to use garbage like InputBox. - jmcilhinney
The threads I start are Niya and Olaf free zones. No arguing about the benefits of VB6 over .NET here please. Happiness must reign. - yereverluvinuncleber
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).
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:-
C++ programmers will dismiss you as a cretinous simpleton for your inability to keep track of pointers chained 6 levels deep and Java programmers will pillory you for buying into the evils of Microsoft. Meanwhile C# programmers will get paid just a little bit more than you for writing exactly the same code and VB6 programmers will continue to whitter on about "footprints". - FunkyDexter
There's just no reason to use garbage like InputBox. - jmcilhinney
The threads I start are Niya and Olaf free zones. No arguing about the benefits of VB6 over .NET here please. Happiness must reign. - yereverluvinuncleber
Re: FindFirstFile(W) Unicode problem with the windows non-unicode settings
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.
C++ programmers will dismiss you as a cretinous simpleton for your inability to keep track of pointers chained 6 levels deep and Java programmers will pillory you for buying into the evils of Microsoft. Meanwhile C# programmers will get paid just a little bit more than you for writing exactly the same code and VB6 programmers will continue to whitter on about "footprints". - FunkyDexter
There's just no reason to use garbage like InputBox. - jmcilhinney
The threads I start are Niya and Olaf free zones. No arguing about the benefits of VB6 over .NET here please. Happiness must reign. - yereverluvinuncleber
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.
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.
C++ programmers will dismiss you as a cretinous simpleton for your inability to keep track of pointers chained 6 levels deep and Java programmers will pillory you for buying into the evils of Microsoft. Meanwhile C# programmers will get paid just a little bit more than you for writing exactly the same code and VB6 programmers will continue to whitter on about "footprints". - FunkyDexter
There's just no reason to use garbage like InputBox. - jmcilhinney
The threads I start are Niya and Olaf free zones. No arguing about the benefits of VB6 over .NET here please. Happiness must reign. - yereverluvinuncleber
Re: FindFirstFile(W) Unicode problem with the windows non-unicode settings
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)".
C++ programmers will dismiss you as a cretinous simpleton for your inability to keep track of pointers chained 6 levels deep and Java programmers will pillory you for buying into the evils of Microsoft. Meanwhile C# programmers will get paid just a little bit more than you for writing exactly the same code and VB6 programmers will continue to whitter on about "footprints". - FunkyDexter
There's just no reason to use garbage like InputBox. - jmcilhinney
The threads I start are Niya and Olaf free zones. No arguing about the benefits of VB6 over .NET here please. Happiness must reign. - yereverluvinuncleber
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.
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?
C++ programmers will dismiss you as a cretinous simpleton for your inability to keep track of pointers chained 6 levels deep and Java programmers will pillory you for buying into the evils of Microsoft. Meanwhile C# programmers will get paid just a little bit more than you for writing exactly the same code and VB6 programmers will continue to whitter on about "footprints". - FunkyDexter
There's just no reason to use garbage like InputBox. - jmcilhinney
The threads I start are Niya and Olaf free zones. No arguing about the benefits of VB6 over .NET here please. Happiness must reign. - yereverluvinuncleber
Re: FindFirstFile(W) Unicode problem with the windows non-unicode settings
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.
C++ programmers will dismiss you as a cretinous simpleton for your inability to keep track of pointers chained 6 levels deep and Java programmers will pillory you for buying into the evils of Microsoft. Meanwhile C# programmers will get paid just a little bit more than you for writing exactly the same code and VB6 programmers will continue to whitter on about "footprints". - FunkyDexter
There's just no reason to use garbage like InputBox. - jmcilhinney
The threads I start are Niya and Olaf free zones. No arguing about the benefits of VB6 over .NET here please. Happiness must reign. - yereverluvinuncleber
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.
C++ programmers will dismiss you as a cretinous simpleton for your inability to keep track of pointers chained 6 levels deep and Java programmers will pillory you for buying into the evils of Microsoft. Meanwhile C# programmers will get paid just a little bit more than you for writing exactly the same code and VB6 programmers will continue to whitter on about "footprints". - FunkyDexter
There's just no reason to use garbage like InputBox. - jmcilhinney
The threads I start are Niya and Olaf free zones. No arguing about the benefits of VB6 over .NET here please. Happiness must reign. - yereverluvinuncleber
C++ programmers will dismiss you as a cretinous simpleton for your inability to keep track of pointers chained 6 levels deep and Java programmers will pillory you for buying into the evils of Microsoft. Meanwhile C# programmers will get paid just a little bit more than you for writing exactly the same code and VB6 programmers will continue to whitter on about "footprints". - FunkyDexter
There's just no reason to use garbage like InputBox. - jmcilhinney
The threads I start are Niya and Olaf free zones. No arguing about the benefits of VB6 over .NET here please. Happiness must reign. - yereverluvinuncleber