【问题标题】:Can't enumerate network shares无法枚举网络共享
【发布时间】:2014-08-13 21:00:37
【问题描述】:

我正在我的 WPF 应用程序中处理一个自定义对话框,该对话框将允许用户选择网络中用户可以看到的任何位置的文件夹。我使用的代码改编自 thisCodeProject 关于枚举网络资源的文章。

原文章中的代码在EnumerateServers对象被实例化后立即枚举所有网络资源,每当找到容器时递归调用自身,并将找到的每个节点添加到ArrayList中。这是低效的:

  1. 在完全遍历网络树之前,您无法开始枚举任何内容。
  2. 枚举可能需要大量时间,具体取决于网络流量和当前用户可以看到的节点数量。

我在 WPF TreeView 控件中显示网络资源,因此我只想遍历作为用户展开的节点的直接子节点的节点。

考虑到这一点,我对代码进行了一些更改。具体来说,我对其进行了修改,以便在发生错误时抛出异常并删除递归。现在的代码如下所示:

[StructLayout( LayoutKind.Sequential )]
internal class NetResource {
    public ResourceScope       Scope       = 0;
    public ResourceType        Type        = 0;
    public ResourceDisplayType DisplayType = 0;
    public ResourceUsage       Usage       = 0;
    public string              LocalName   = null;
    public string              RemoteName  = null;
    public string              Comment     = null;
    public string              Provider    = null;
};

public enum ResourceDisplayType {
    Generic,
    Domain,
    Server,
    Share,
    File,
    Group,
    Network,
    Root,
    ShareAdmin,
    Directory,
    Tree,
    NdsContainer
};

public enum ResourceScope {
    Connected = 1,
    GlobalNet,
    Remembered,
    Recent,
    Context
};

public enum ResourceType {
    Any,
    Disk,
    Print,
    Reserved
};

[Flags]
public enum ResourceUsage {
    Connectible   = 0x00000001,
    Container     = 0x00000002,
    NoLocalDevice = 0x00000004,
    Sibling       = 0x00000008,
    Attached      = 0x00000010,
    All           = Connectible | Container | Attached,
};

public class Share {

    public string Comment { get; private set; }
    public ResourceDisplayType DisplayType { get; private set; }
    public string Name { get; private set; }
    public string Provider { get; private set; }
    public ResourceScope Scope { get; private set; }
    public ResourceType ShareType { get; private set; }
    public ResourceUsage Usage { get; private set; }
    public string UNCPath { get; private set; }

    internal Share( NetResource netResource ) {
        DisplayType = netResource.DisplayType;
        Scope       = netResource.Scope;
        ShareType   = netResource.Type;
        Usage       = netResource.Usage;
        if ( !string.IsNullOrWhiteSpace( netResource.Comment ) ) {
            char[] commentChars = new char[ netResource.Comment.Length ];
            netResource.Comment.CopyTo( 0, commentChars, 0, netResource.Comment.Length );
            Comment = new string( commentChars );
        }
        if ( !string.IsNullOrWhiteSpace( netResource.LocalName ) ) {
            char[] localNameChars = new char[ netResource.LocalName.Length ];
            netResource.LocalName.CopyTo( 0, localNameChars, 0, netResource.LocalName.Length );
            Name = new string( localNameChars );
        }
        if ( !string.IsNullOrWhiteSpace( netResource.Provider ) ) {
            char[] providerChars = new char[ netResource.Provider.Length ];
            netResource.Provider.CopyTo( 0, providerChars, 0, netResource.Provider.Length );
            Provider = new string( providerChars );
        }
        if ( !string.IsNullOrWhiteSpace( netResource.RemoteName ) ) {
            char[] remoteNameChars = new char[ netResource.RemoteName.Length ];
            netResource.RemoteName.CopyTo( 0, remoteNameChars, 0, netResource.RemoteName.Length );
            UNCPath = new string( remoteNameChars );
        }
    }
}

public class ShareEnumerator : IEnumerable<Share> {

    public string Comment { get; set; }
    public ResourceDisplayType DisplayType { get; set; }
    public string Provider { get; set; }
    public string ResourceName { get; set; }
    public string ResourcePath { get; set; }
    public ResourceScope Scope { get; set; }
    public ResourceType ShareType { get; set; }
    public ResourceUsage Usage { get; set; }

    public ShareEnumerator() { }

    public ShareEnumerator( Share aShare ) {
        Comment      = aShare.Comment;
        DisplayType  = aShare.DisplayType;
        Provider     = aShare.Provider;
        ResourceName = aShare.Name;
        ResourcePath = aShare.UNCPath;
        Scope        = aShare.Scope;
        ShareType    = aShare.ShareType;
        Usage        = aShare.Usage;
    }

    public IEnumerator<Share> GetEnumerator() {
        NetResource netResource = new NetResource {
            Comment     = this.Comment,
            DisplayType = this.DisplayType,
            LocalName   = this.ResourceName,
            Provider    = this.Provider,
            RemoteName  = this.ResourcePath,
            Scope       = this.Scope,
            Type        = this.ShareType,
            Usage       = this.Usage
        };
        uint        bufferSize = 16384;
        IntPtr        buffer     = IntPtr.Zero;
        uint        cEntries   = 1;
        IntPtr        handle     = IntPtr.Zero;
        ErrorCodes  result;

        try {
            buffer = Marshal.AllocHGlobal( (int) bufferSize );
            result = WNetOpenEnum( Scope, ShareType, Usage, netResource, out handle );
            if ( result != ErrorCodes.NO_ERROR ) {
                throw new InvalidOperationException( string.Format( "The call to WNetOpenEnum failed: the result code was {0:x}", (int) result ) );
            }

            try {
                do {
                    result = WNetEnumResource( handle, ref cEntries, buffer, ref bufferSize );
                    if ( result == ErrorCodes.NO_ERROR ) {
                        // It was.  Marshal the buffer into the NetResource object.
                        Marshal.PtrToStructure( buffer, netResource );

                        if ( netResource.DisplayType == DisplayType || netResource.DisplayType == ResourceDisplayType.Domain ) {
                            // We do. Convert it into a Share & enumerate it.
                            yield return new Share( netResource );
                        }
                    } else if ( result == ErrorCodes.ERROR_NO_MORE_ITEMS ) {
                        break;
                    } else {
                        throw new InvalidOperationException( string.Format( "The call to WNetEnumResource failed: the result code was {0:x}", (int) result ) );
                    }
                } while ( result == ErrorCodes.NO_ERROR );
            } finally {
                WNetCloseEnum( (IntPtr) buffer );
            }
        } finally {
            if ( buffer != IntPtr.Zero ) {
                // VERY IMPORTANT! Deallocate the buffer to prevent memory leaks!!
                Marshal.FreeHGlobal( buffer );
            }
        }
    }

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() {
        return GetEnumerator();
    }

    private enum ErrorCodes {
        NO_ERROR = 0,
        ERROR_NO_MORE_ITEMS = 259
    };

    [DllImport( "Mpr.dll", EntryPoint = "WNetOpenEnumA", CallingConvention = CallingConvention.Winapi )]
    private static extern ErrorCodes WNetOpenEnum( ResourceScope dwScope, ResourceType dwType, ResourceUsage dwUsage, NetResource p, out IntPtr lphEnum );

    [DllImport( "Mpr.dll", EntryPoint = "WNetCloseEnum", CallingConvention = CallingConvention.Winapi )]
    private static extern ErrorCodes WNetCloseEnum( IntPtr hEnum );

    [DllImport( "Mpr.dll", EntryPoint = "WNetEnumResourceA", CallingConvention = CallingConvention.Winapi )]
    private static extern ErrorCodes WNetEnumResource( IntPtr hEnum, ref uint lpcCount, IntPtr buffer, ref uint lpBufferSize );
}

}

当用户单击TreeView 中的“整个网络”节点以获取网络节点时,此代码运行:

private void NetworkExpanded( object sender, RoutedEventArgs e ) {
    if ( !ShareScanner.IsBusy ) {
        OriginalCursor = LayoutRoot.Cursor;
        LayoutRoot.Cursor = Mouse.OverrideCursor = Cursors.Wait;

        TreeViewItem networkItem = sender as TreeViewItem;
        if ( networkItem.Items.Count == 1 && networkItem.Items[ 0 ] == dummyNode ) {
            networkItem.Items.Clear();
            ShareScanner.RunWorkerAsync( new ShareScannerArgs( networkItem, networkItem.Tag as Share, ShareScannerTypes.Computers ) );
        }
    }
    e.Handled = true;
}

整个网络节点下方是用户可以看到的特定计算机的节点。当他们展开其中一个节点时,将运行以下代码:

private void NetworkComputerExpanded( object sender, RoutedEventArgs e ) {
    if ( !ShareScanner.IsBusy ) {
        OriginalCursor = LayoutRoot.Cursor;
        LayoutRoot.Cursor = Mouse.OverrideCursor = Cursors.Wait;

        TreeViewItem computerItem = sender as TreeViewItem;

        if ( computerItem.Items.Count == 1 && computerItem.Items[ 0 ] == dummyNode ) {
            computerItem.Items.Clear();
                ShareScanner.RunWorkerAsync( new ShareScannerArgs( computerItem, computerItem.Tag as Share, ShareScannerTypes.Shares ) );
        }
    }
    e.Handled = true;
}

在计算机节点下方会有“共享”节点。当他们单击TreeView 中的“共享”节点时,此代码运行:

private void NetworkShareExpanded( object sender, RoutedEventArgs e ) {
    if ( !ShareScanner.IsBusy ) {
        OriginalCursor = LayoutRoot.Cursor;
        LayoutRoot.Cursor = Mouse.OverrideCursor = Cursors.Wait;

        TreeViewItem shareItem = sender as TreeViewItem;

            if ( shareItem.Items.Count == 1 && shareItem.Items[ 0 ] == dummyNode ) {
                shareItem.Items.Clear();
                ShareScanner.RunWorkerAsync( new ShareScannerArgs( shareItem, shareItem.Tag as Share, ShareScannerTypes.Folders ) );
        }
    }
    e.Handled = true;
}

共享下方会有各个文件夹的节点,但我还没有为该级别编写任何代码,因为我正在尝试让其他级别工作。

从我发布的代码中可以看出,我使用BackgroundWorker 来实际完成这项工作。在执行对 WNetOpenEnumWNetEnumResource 的调用时,UI 变得无响应。我的 UI 必须保持响应,因此我使用 BackgroundWorker 来等待并保持 UI 响应。

这是BackgroundWorker's DoWork 事件处理程序:

private void ShareScanner_DoWork( object sender, DoWorkEventArgs e ) {
    BackgroundWorker worker = sender as BackgroundWorker;
    ShareScannerArgs info = e.Argument as ShareScannerArgs;

    ShareEnumerator enumerator = null;

    switch ( info.WhatToScanFor ) {
        case ShareScannerTypes.Computers:
            enumerator = new ShareEnumerator {
                DisplayType = ResourceDisplayType.Network,        // Retrieve Servers only
                Scope       = ResourceScope.GlobalNet,            // Retrieve only objects the user can see
                ShareType   = ResourceType.Disk,                  // Retrieve only Disk shares
                Usage       = ResourceUsage.All                   // Retrieve all Connectible, Container & Attached nodes.
            };
            break;

        case ShareScannerTypes.Shares:
        case ShareScannerTypes.Folders:
            enumerator = new ShareEnumerator( info.ParentShare );
            break;

        default:
            // Should never get here!
            throw new InvalidOperationException( string.Format( "Unknown ShareScannerType: {0}", info.WhatToScanFor ) );
    }

    try {
        foreach ( Share share in enumerator ) {
            if ( worker.CancellationPending ) {
                e.Cancel = true;
                return;
            }

            worker.ReportProgress( 0, new NodeArgs( info.Parent, share, info.WhatToScanFor ) );
        }
    } catch ( Exception ) { }
}

这是ProgressChanged 事件处理程序的代码:

private void ShareScanner_ProgressChanged( object sender, ProgressChangedEventArgs e ) {
    NodeArgs nodeArgs = e.UserState as NodeArgs;
    Share parentShare = nodeArgs.Tag as Share;

    TreeViewItem item = new TreeViewItem {
        Header = parentShare.UNCPath,
        Tag    = nodeArgs.Tag
    };

    switch ( nodeArgs.NodeToBuild ) {
        case ShareScannerTypes.Computers:
            item.Items.Add( dummyNode );
            item.Expanded += new RoutedEventHandler( NetworkComputerExpanded );
            break;

        case ShareScannerTypes.Shares:
            item.Items.Add( dummyNode );
            item.Expanded += new RoutedEventHandler( NetworkShareExpanded );
            break;

        case ShareScannerTypes.Folders:
            break;

        default:
            // Should never get here!
            throw new InvalidOperationException( string.Format( "Unknown ShareScannerType: : {0}", nodeArgs.NodeToBuild ) );
    }

    nodeArgs.Parent.Items.Add( item );
}

最后,RunWorkerCompleted 事件的代码:

private void ShareScanner_RunWorkerCompleted( object sender, RunWorkerCompletedEventArgs e ) {
    Mouse.OverrideCursor = null;
    LayoutRoot.Cursor = OriginalCursor;
}

代码生成一棵带有“整个网络”节点的树。当您展开它时,我会看到“Microsoft 终端服务”、“Microsoft Windows 网络”和“Web 客户端网络”的条目。当我展开其中任何一个时,WNetOpenEnum 失败,结果返回 57。

我做错了什么?代码看起来很简单,但显然我错过了一些东西。

【问题讨论】:

  • 您是否考虑过使用不同的方法,例如使用WMI
  • 我从未使用过 WMI。也不知道是什么。你能指点我一些文章吗?
  • 我不相信 WMI 会让您枚举 UNC 共享。看看这个问题和答案——所以似乎有很多 C#-enumerate network share-UNC 问题正在进行:stackoverflow.com/questions/8918477/…

标签: c# wpf networking treeview


【解决方案1】:

我终于让我的对话按我的意愿工作了,但没有使用原始问题中发布的任何代码。相反,我用来自 CodeProject 上的另外两篇文章的代码替换了所有这些代码。以下是我的应用程序代码中的 cmets,其中包含有关代码来源的信息:

The `ComputerEnumerator` class originated as the `NetworkBrowser.cs` class from the 
"Retrieving a List of Network Computer Names Using C#" article, which can be found at:
http://www.codeproject.com/Articles/16113/Retreiving-a-list-of-network-computer-names-using

The ShareEnumerator class originated as the ServerEnum.cs class from the
"Network Shares and UNC paths" article, which can be found at:
http://www.codeproject.com/Articles/2939/Network-Shares-and-UNC-paths

第一个类使用NetServerEnum 函数检索网络上所有可用的计算机,而第二个类使用NetShareEnum 方法检索特定服务器上可用的共享。第一篇文章中的代码按原样正常工作。不过,第二篇文章中的代码存在一些问题。

最大的问题在于DllImportNetApiBufferFree 函数。当我跨过调用时,当我的代码在调试器中调用它时,该函数一定有错误或其他原因,因为调试器从未重新获得控制权,并且恢复鼠标光标的方法中的代码从未运行过。当我将 API 的定义替换为第一篇文章中的定义时,一切正常。

不太严重的问题是第二篇文章创建了一个名为ShareType 的枚举类型,它使用[Flags] 属性进行了修饰。这样做的问题是枚举的值取自 LMShares.h 文件并且是顺序值,而不是 2 的幂。这样的枚举不能作为一组标志工作。例如,不应有值为 3 的枚举标识符,因为该值应通过将 1 和 2 的枚举值按位或运算形成。但是IPC类型的枚举标识符,果然是3。

如果您尝试使用此枚举中的值过滤返回的共享,此问题会(并且确实)导致问题。为了解决后一个问题,我从该类型中删除了 [Flags] 属性,并且不要将 is 用于任何位操作。

这些函数返回值的速度之快令人惊叹。我确信它们被缓存在某个地方,并且在某些情况下它们可能需要更长的时间才能返回值。但无论如何,我的代码都可以按照我的意愿工作。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2010-09-17
    • 2014-01-20
    • 2010-09-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多