The problem is that C and VB handle strings quite differently.
In C, it is a memory area containing bytes (or 16-bit words if Unicode is used, but I will overlook that now), and ending with a byte with a zero value. The address of this memory area is held in a pointer, ie. a 32-bit value (in Win32).
In VB, a (dynamic) string is a structure (BSTR) holding the memory area (or a masked pointer on it) and a variable containing the size of the string. There is no terminating byte of the string (or at least, it isn't used by VB).
Now, the problem is to call a C function and get back a string.
Fortunately, most of the API functions creating strings require an already allocated string area as output parameter, so you provide this area and its size, and the C function just write there.
You just have to tell VB what size the final string has, and it's done.
Here is an example:
For this, I will use the GetFullPathName function, which gets the current drive and path, and concatenates it to the given filename. This has no use in VB as you can easily do the same without API call, but it is good enough for this example.
Private Declare Function GetFullPathName Lib "kernel32" Alias "GetFullPathNameA" (ByVal lpFileName As String, ByVal nBufferLength As Long, ByVal lpBuffer As String, ByVal lpFilePart As Long) As Long ' Note that I changed the API Viewer's lpFilePart definition from String to Long, as it return a pointer ' on an address in lpBuffer, and I am not sure how VB will handle this. Private Sub Command1_Click() ' Just a button on any form to test the function Dim s As String, zpos As Long s = String(255, 0) ' Can be Space(255), if you prefer i = GetFullPathName("MyFile.any", 255, s, lp) ' i = GetLastError zpos = InStr(s, vbNullChar) s = Left(s, zpos - 1) MsgBox s End Sub
I initialize s to any value (here byte zero), so VB allocates a wide enough area to store the result from the API call.
Then, I pass this string to the function and tell how wide this string is. The size is usually used by the C function to avoid overwriting after the end of the buffer. VB automatically convert its string to a C pointer, because you told to pass the parameter ByVal. Using ByRef leads to unpredictable results (usually doom... :-)
Then I search in s the first null character, marking the end of the returned string.
Note that here, I could use the i return value, giving the length of the string, but not all functions return this kind of info, so for example purpose I used the classic way.
Therefore, I truncated s to the exact length of the string, and used it any way I like.
There is still a tricky problem: some C functions return a pointer on a string, ie. the address of the string
in memory. It may be a string with system wide life, so the only concern would be to retreive the string value,
or it may be a string allocated by the function, leaving the caller the responsability to desallocate this memory.
This desallocation problem is another point I won't explain here. Just say it all depends on the function, as there can be several ways to free this memory. The best case being the API giving a function to do this...
Well, I will focus on the main problem: how to get the string data floating somewhere in memory?
To demonstrate this, I choosed a function returning a pointer on a string. Actually, this function change on-place the string given as parameter, so there is no need to fetch the string data. And furthermore, there is a VB function doing the same task. But again it is good enough to demonstrate my point.
To get the string, I declare the C pointer as a long integer, and pass it to an API copy string function. This function wants a pointer as input parameter, I give it my long integer, and writes in the string given as output parameter, here I let VB convert an existing string to a pointer on an allocated memory area, using the mechanism explained above.
' This is my "magical" function, with proprietary declare... Private Declare Function ConvCStringToVBString Lib "kernel32" Alias "lstrcpyA" (ByVal lpsz As String, ByVal pt As Long) As Long ' Notice the As Long return value replacing the As String given by the API Viewer. Private Declare Function CharUpper Lib "user32" Alias "CharUpperA" (ByVal lpsz As String) As Long Private Sub Command1_Click() ' Just a button on any form to test the function Dim l As Long, zpos As Long Dim s As String, test As String test = "yada yada" ' Retreive pointer to string from the API call ' as a long integer l = CharUpper(test) ' Initialize string with zeroes (or anything else) ' so it will have the right size (ajust value as needed) s = String(255, 0) ConvCStringToVBString s, l ' Look for the null char ending the C string zpos = InStr(s, vbNullChar) s = Left(s, zpos - 1) MsgBox s End Sub
That do the trick!
I can't remember having seen this trick elsewhere, so perhaps it is an exclusive one...
Not bad to introduce my programming section... ;-)
Actually, I got feedback of readers that found this trick useful and not to be found elsewhere. :-)
Go to programming page or to main page
Contact me, comment this
Updated: 1999/08/06 (Minor update (links): 2004/05/09)