无法枚举网络共享

时间:2014-08-13 21:00:37

标签: c# wpf networking treeview

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

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

  1. 在网络树完全遍历之前,您无法开始枚举任何内容。
  2. 根据网络流量和数据,枚举可能需要很长时间。当前用户可以看到的节点数。
  3. 我在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 );
    }
    

    }

    当用户点击&#34;整个网络&#34;对于网络节点,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;
    }
    

    在计算机节点下面会有&#34;分享&#34;节点。当他们点击&#34;分享&#34; 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时,用户界面无响应。我的用户界面必须保持响应,所以我使用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;
    }
    

    代码会生成一个包含“整个网络”的树。节点。当您展开它时,我会获得&#34; Microsoft终端服务&#34;,&#34; Microsoft Windows网络&#34;以及&#34; Web客户端网络&#34;的条目。当我展开其中任何一个时,WNetOpenEnum失败,返回57作为结果。

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

1 个答案:

答案 0 :(得分:0)

我终于让我的对话框按照我的意愿工作,但没有使用原始问题中发布的任何代码。相反,我用CodeProject上另外两篇文章中的代码替换了所有代码。以下是我的应用程序中代码的注释以及代码来自何处的信息:

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方法检索特定服务器上可用的共享。第一篇文章中的代码正常工作。不过,第二篇文章中的代码存在一些问题。

最大的问题出现在DllImport NetApiBufferFree函数中。当我在调试器中调用它时我的代码调用它时,该函数必须有错误或者某些东西,因为调试器永远不会重新获得控制,并且恢复鼠标光标的方法中的代码永远不会运行。当我用第一篇文章中的那个替换API的定义时,一切正常。

不太严重的问题是,第二篇文章创建了一个名为ShareType的枚举类型,该类型使用[Flags]属性进行了修饰。这个问题是枚举的值取自LMShares.h文件,是序列值,而不是2的幂。这样的枚举不能作为一组标志。例如,应该没有值为3的枚举标识符,因为该值应该通过逐位OR运算1和1的枚举值来形成。 2在一起。但是IPC类型的枚举标识符肯定是3。

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

令人惊讶的是,这些函数返回值的速度有多快。我确定他们已在某个地方缓存,在某些情况下他们可能需要更长时间才能返回值。但无论如何我都有代码工作,因为我希望它可以工作。