The following Marshal code almost works

  • Thread starter Thread starter Just Me
  • Start date Start date
J

Just Me

The following almost works.
The problem is Marshal.PtrToStringAuto seems to terminate at the first null
so I don't get the full string.

Any suggestions on how to fix this?
Or how to improve the code?

Thanks

PS I added the +1 because as I understand the GetLogicalDriveStrings doc
there will be an uncounted null at the end for Unicode. Agree?

Dim lDrives As String
Dim lLenOflDrives As Integer
Dim lPointerToMemory As IntPtr
lLenOflDrives = Kernel.GetLogicalDriveStrings(0, Nothing)
Dim lDrivesSB As New StringBuilder(lLenOflDrives + 1)
lDrivesSB.Insert(0, ChrW(33), lLenOflDrives + 1)
lPointerToMemory = Marshal.StringToHGlobalAuto(lDrivesSB.ToString)
'Get the list of dirves. Returns a:\<null>c:\<null><null>
If Kernel.GetLogicalDriveStrings(lLenOflDrives, lPointerToMemory) Then
'Would need to use PtrToStringAnsi if GetLogicalDriveStrings were declared
CharSet.Ansi
lDrives = Marshal.PtrToStringAuto(lPointerToMemory, lLenOflDrives + 1) 'Text
to first null, e.g, a:\
Marshal.FreeHGlobal(lPointerToMemory)
'See if aDrive is in the list
DriveExists = InStr(1, lDrives, drive, CompareMethod.Text)
End If
 
Or how to improve the code?

Thanks

PS I added the +1 because as I understand the GetLogicalDriveStrings doc
there will be an uncounted null at the end for Unicode. Agree?

Dim lDrives As String
Dim lLenOflDrives As Integer
Dim lPointerToMemory As IntPtr
lLenOflDrives = Kernel.GetLogicalDriveStrings(0, Nothing)
Dim lDrivesSB As New StringBuilder(lLenOflDrives + 1)
lDrivesSB.Insert(0, ChrW(33), lLenOflDrives + 1)
lPointerToMemory = Marshal.StringToHGlobalAuto(lDrivesSB.ToString)
'Get the list of dirves. Returns a:\<null>c:\<null><null>
If Kernel.GetLogicalDriveStrings(lLenOflDrives, lPointerToMemory) Then

If you change your GetLogicalDriveStrings declaration so that the last
parameter is a Char(), you can simply call it like this

Dim buffer(lLenOflDrives - 1) As Char
If Kernel.GetLogicalDriveStrings(lLenOflDrives, buffer) > 0 Then



Mattias
 
Just Me,

I don't know if you are interested in some managed code to get logical
drives.
However I place it here, when you not, maybe somebody else when he/she is
searching.

set a reference and Imports System.Management
\\\
Dim searcher As New ManagementObjectSearcher _
("SELECT * FROM Win32_LogicalDisk")
Dim ManObjOp As ManagementObject
If searcher.Get.Count > 1 Then
For Each ManObjOp In searcher.Get
Dim win32 As String = "Win32_LogicalDisk='" &
ManObjOp("Name").ToString & "'"
Dim ManObjLogD As New ManagementObject(win32)
For Each diskProperty As PropertyData In
ManObjLogD.Properties
If Not diskProperty.Value Is Nothing Then
Console.WriteLine _
("{0} = {1}", diskProperty.Name, diskProperty.Value)
End If
Next
Next
End If
///


Cor
 
Just Me,
First I would "improve it" via using Environment.GetLogicalDrives instead.

Is there a reason you are not using Environment.GetLogicalDrives?


According to Adam's book Marshal.PtrToStringAuto(IntPtr, Integer) should
*NOT* terminate at the first null character, where as the
Marshal.PtrToStringAuto(IntPtr) does. Are you certain its getting truncated
& not simply the Debugger stops displaying after the null char? Remember the
Debugger treats the null char as a string terminator!

If I actually needed to call Kernel32.GetLogicalDriveStrings I would use
Marshal.AllocHGlobal instead of Marshal.StringToHGlobalAuto, as
Marshal.StringToHGlobalAuto is more for when you need to pass a string into
Win32, not return a string from Win32...

Something like:
Dim lDrives As String
Dim lLenOflDrives As Integer = Kernel.GetLogicalDriveStrings(0,
IntPtr.Zero)
Dim lPointerToMemory As IntPtr = Marshal.AllocHGlobal(lLenOflDrives
* 2)
Dim rc As Integer = Kernel.GetLogicalDriveStrings(lLenOflDrives,
lPointerToMemory)
If rc <> 0 Then
lDrives = Marshal.PtrToStringAuto(lPointerToMemory,
lLenOflDrives) 'Text to first null, e.g, a:\
Marshal.FreeHGlobal(lPointerToMemory)
Dim drives() As String = lDrives.Split(ControlChars.NullChar)
End If

For information on P/Invoke I normally reference Adam Nathan's book ".NET
and COM - The Complete Interoperability Guide" from SAMS Press.

Hope this helps
Jay
 
Mattias,
If you change your GetLogicalDriveStrings declaration so that the last
parameter is a Char(), you can simply call it like this
I like that idea!

Thanks for reminding me...

Jay
 
Works great.

Some comments/questions:

From Help

GetLogicalDriveStrings

lpBuffer
[out] Pointer to a buffer that receives a series of null-terminated strings,
one for each valid drive in the system, that end with a second null
character. The following example shows the buffer contents with <null>
representing the terminating null character.
c:\<null>d:\<null><null>


In my run I got for drives()
A:\
C:\
D:\
""
""
I think the above adds up to 14 chars (counting nulls)

lLengthOfDrives is 13
Which make sense A:\<null>C:\<null>D:\<null><null>


I'm confused about where the extra "" came from, unless DOTNET adds a null
to the buffer, in which case the buffer is too small!

Thanks
 
Doesn't get much simpler then that.

Thanks

Mattias Sjögren said:
If you change your GetLogicalDriveStrings declaration so that the last
parameter is a Char(), you can simply call it like this

Dim buffer(lLenOflDrives - 1) As Char
If Kernel.GetLogicalDriveStrings(lLenOflDrives, buffer) > 0 Then



Mattias
 
Jay B. Harlow said:
Just Me,
First I would "improve it" via using Environment.GetLogicalDrives instead.

Is there a reason you are not using Environment.GetLogicalDrives?

Didn't know about it - but I want to play with Marshaling anyway
 
According to Adam's book Marshal.PtrToStringAuto(IntPtr, Integer) should
*NOT* terminate at the first null character, where as the
Marshal.PtrToStringAuto(IntPtr) does. Are you certain its getting
truncated & not simply the Debugger stops displaying after the null char?
Remember the Debugger treats the null char as a string terminator!

Just checked this - your right - routine was working OK but the Debugger
fooled me.
Now I can't trust anything. CheckedDebug.Writeline and Console.writeline -
they also stop at the null.

Thanks
 
Just Me,
Thinking about it the two blank strings are expected. String.Split returns
every thing between the null chars.

Your string is:

Const null As Char = ControlChars.NullChar

Dim ldrives As String = "A:\" & null & "C:\" & null & "D:\" & null &
null

Notice there is an empty string between the last two null's & an empty
string between the last null & the end of the string.

You could simply trim off the null chars on the end before you split it:

Dim drives() As String =
ldrives.TrimEnd(ControlChars.NullChar).Split(ControlChars.NullChar)

Or simply not include them when you create the string:

lDrives = Marshal.PtrToStringAuto(lPointerToMemory,
lLenOflDrives - 2)

Again I would probably simply use Environment.GetLogicalDrives, however this
is a good exercise in how Interop works...

Hope this helps
Jay

Just Me said:
Works great.

Some comments/questions:

From Help

GetLogicalDriveStrings

lpBuffer
[out] Pointer to a buffer that receives a series of null-terminated
strings, one for each valid drive in the system, that end with a second
null character. The following example shows the buffer contents with
<null> representing the terminating null character.
c:\<null>d:\<null><null>


In my run I got for drives()
A:\
C:\
D:\
""
""
I think the above adds up to 14 chars (counting nulls)

lLengthOfDrives is 13
Which make sense A:\<null>C:\<null>D:\<null><null>


I'm confused about where the extra "" came from, unless DOTNET adds a null
to the buffer, in which case the buffer is too small!

Thanks


Jay B. Harlow said:
Just Me,
First I would "improve it" via using Environment.GetLogicalDrives
instead.

Is there a reason you are not using Environment.GetLogicalDrives?


According to Adam's book Marshal.PtrToStringAuto(IntPtr, Integer) should
*NOT* terminate at the first null character, where as the
Marshal.PtrToStringAuto(IntPtr) does. Are you certain its getting
truncated & not simply the Debugger stops displaying after the null char?
Remember the Debugger treats the null char as a string terminator!

If I actually needed to call Kernel32.GetLogicalDriveStrings I would use
Marshal.AllocHGlobal instead of Marshal.StringToHGlobalAuto, as
Marshal.StringToHGlobalAuto is more for when you need to pass a string
into Win32, not return a string from Win32...

Something like:
Dim lDrives As String
Dim lLenOflDrives As Integer = Kernel.GetLogicalDriveStrings(0,
IntPtr.Zero)
Dim lPointerToMemory As IntPtr =
Marshal.AllocHGlobal(lLenOflDrives * 2)
Dim rc As Integer = Kernel.GetLogicalDriveStrings(lLenOflDrives,
lPointerToMemory)
If rc <> 0 Then
lDrives = Marshal.PtrToStringAuto(lPointerToMemory,
lLenOflDrives) 'Text to first null, e.g, a:\
Marshal.FreeHGlobal(lPointerToMemory)
Dim drives() As String = lDrives.Split(ControlChars.NullChar)
End If

For information on P/Invoke I normally reference Adam Nathan's book ".NET
and COM - The Complete Interoperability Guide" from SAMS Press.

Hope this helps
Jay
 
Notice there is an empty string between the last two null's & an empty
string between the last null & the end of the string.


Not sure what you mean by an empty string but QuickWatch give

lDrives.chars(10)="\"c
lDrives.chars(11)=Nothing
lDrives.chars(12))=Nothing
lDrives.chars(13)Index out of range

As you'd expect since lLenOflDrives=13

I don't see anything after the second null

I just hate to move on when I can't figure something out.


Thanks for the help
 
Just Me,
Remember that a null char is nothing special in .NET, it is a perfectly
valid string character.

Do you understand how String.Split works? It returns each of the strings
between the delimiters. If you have two delimiters in a row it returns an
empty string, if your string starts with or ends with a Delimiter it returns
an empty string.

Try the following:

Dim input As String = "A:\|C:\|D:\||"

Dim values() As String = input.Split("|"c)

How many strings were returned?

Do you see why they were all returned?

Null char is only special when you directly or indirectly call a Win32 API.
In purely managed code, such as the String class, null char has no
significance!

Hope this helps
Jay
 
I wasn't confused about the null char but it hadn't occurred to me that if
the string ended in a separator Split would assume an empty string after
it - but it does make sense.

Thanks
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Back
Top