Home > other >  IEnumString.Next() always returns 0 items fetched
IEnumString.Next() always returns 0 items fetched

Time:09-01

I'm p/invoking InternetSecurityManager.GetZoneMappings() in order to get a list of safe sites from my Intranet Zone, and the Next() method on the returned IEnumString object always reports that zero sites were fetched, even though there were.

Here's my code, based on this answer:

Imports System
Imports System.Collections.Generic
Imports System.Linq
Imports System.Runtime.InteropServices
Imports System.Runtime.InteropServices.ComTypes

Public Class Test
  Private Shared ReadOnly CLSID_InternetSecurityManager As New Guid("7b8a2d94-0ac9-11d1-896c-00c04fb6bfc4")
  Private ReadOnly InternetSecurityManager As IInternetSecurityManager
  Private ReadOnly SecurityManager As Object

  Private Const URLZONE_LOCAL_MACHINE As UInteger = &H0
  Private Const URLZONE_INTRANET As UInteger = URLZONE_LOCAL_MACHINE   1
  Private Const URLZONE_TRUSTED As UInteger = URLZONE_INTRANET   1
  Private Const URLZONE_INTERNET As UInteger = URLZONE_TRUSTED   1
  Private Const URLZONE_UNTRUSTED As UInteger = URLZONE_INTERNET   1

  Public Sub New
    Dim oType As Type

    oType = Type.GetTypeFromCLSID(CLSID_InternetSecurityManager)

    Me.SecurityManager = Activator.CreateInstance(oType)
    Me.InternetSecurityManager = Me.SecurityManager

    Me.GetSites()
  End Sub

  Private Function GetSites() As List(Of String)
    Dim nItemsFetched As ULong
    Dim oEnumString As IEnumString
    Dim oSites As List(Of String)
    Dim aItems As String()

    oEnumString = Nothing

    Me.InternetSecurityManager.GetZoneMappings(URLZONE_INTRANET, oEnumString, 0)

    oSites = New List(Of String)
    aItems = New String(0) {}

    Do
      oEnumString.Next(1, aItems, nItemsFetched)

      If aItems.First Is Nothing Then
        Exit Do
      Else
        oSites.Add(aItems.First)
      End If
    Loop

    Return oSites
  End Function
End Class

It runs and it does enumerate the list correctly, but I'd like to clean it up a bit by responding appropriately to the value of nItemsFetched (rather than the present rather cumbersome check for Nothing).

It seems that managed code samples of GetZoneMappings() are pretty scarce out there. Plenty of signatures, but no usage. I couldn't find any (in fact, for all my searching this might be the only one). After some serious digging, though, I did manage to put my thumb on the EnumString sample linked above.

Here's my IInternetSecurityManager interface, gleaned from p/invoke.net:

Imports System
Imports System.Runtime.InteropServices
Imports System.Runtime.InteropServices.ComTypes

<ComImport, Guid("79EAC9EE-BAF9-11CE-8C82-00AA004BA90B"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)>
Public Interface IInternetSecurityManager
  <PreserveSig>
  Function SetSecuritySite(
  <[In]> pSite As IntPtr) As Integer

  <PreserveSig>
  Function GetSecuritySite(
  <Out> pSite As IntPtr) As Integer

  <PreserveSig>
  Function MapUrlToZone(
  <[In], MarshalAs(UnmanagedType.LPWStr)> pwszUrl As String, <Out> ByRef pdwZone As UInteger, dwFlags As UInteger) As Integer

  <PreserveSig>
  Function GetSecurityId(
  <MarshalAs(UnmanagedType.LPWStr)> pwszUrl As String,
  <MarshalAs(UnmanagedType.LPArray)> pbSecurityId As Byte(), ByRef pcbSecurityId As UInteger, dwReserved As UInteger) As Integer

  <PreserveSig>
  Function ProcessUrlAction(
  <[In], MarshalAs(UnmanagedType.LPWStr)> pwszUrl As String, dwAction As UInteger, <Out> ByRef pPolicy As Byte, cbPolicy As UInteger, pContext As Byte, cbContext As UInteger, dwFlags As UInteger, dwReserved As UInteger) As Integer

  <PreserveSig>
  Function QueryCustomPolicy(
  <[In], MarshalAs(UnmanagedType.LPWStr)> pwszUrl As String, ByRef guidKey As Guid, ByRef ppPolicy As Byte, ByRef pcbPolicy As UInteger, ByRef pContext As Byte, cbContext As UInteger, dwReserved As UInteger) As Integer

  <PreserveSig>
  Function SetZoneMapping(dwZone As UInteger, <[In], MarshalAs(UnmanagedType.LPWStr)> lpszPattern As String, dwFlags As UInteger) As Integer

  <PreserveSig>
  Function GetZoneMappings(dwZone As UInteger, <Out> ByRef ppenumString As IEnumString, dwFlags As UInteger) As Integer
End Interface

I tried building an IEnumString interface based on this article, but I only ran into problems with that. At least the native call completes and returns data. I just want to be able to know when the data stops coming.

How can I get EnumString.Next() to correctly return the number of items it fetches?

CodePudding user response:

Next() returns an HRESULT value, so 0 is S_OK (success) which means it returns something in rgelt.

So,

  1. you must allocate aItems to an array of (at least) 1 element before call;
  2. optionally, you can pass a non null pointer to the pceltFetched. if you pass null, you cannot determine how many items where read;
  3. the call should return S_OK (0) if something was read, S_FALSE (1) if not all was requested was read (and possibly something else in error cases) which in the celt = 1 case is the same a "this was the last item in the enumeration".

So, your code can be written like this:

Do

    Dim ret = oEnumString.Next(1, aItems, IntPtr.Zero)
    If ret <> 0 Then Exit Do

    oSites.Add(aItems.First)
Loop

Or like this if you want to use the pceltFetched argument. This is rarely needed, only when celt > 1 (when the return value just say that not everything was read) which is not always supported by enum sources either, so everyone always uses celt = 1...

Dim fetchedPtr As IntPtr
fetchedPtr = Marshal.AllocHGlobal(Marshal.SizeOf(Of Integer)()) ' or AllocCoTaskMem
Dim fetched As Integer

Do

    oEnumString.Next(1, aItems, fetchedPtr)
    fetched = Marshal.ReadInt32(fetchedPtr)
    If fetched <> 1 Then Exit Do

    oSites.Add(aItems.First)
Loop

Marshal.FreeHGlobal(fetchedPtr)
  • Related