이 글은 오래된 전에 작성된 글입니다. 따라서 최신 버전의 기술에 알맞지 않거나 오류를 유발할 수 있습니다.
저자는 이 글에 대한 질문을 받지 않을 것입니다. 하지만 이 글이 리뉴얼 되면 이 글에 대한 질문을 하거나
토론을 할 수도 있습니다.
간만에 또 네트워크 드라이브 이야기를 다시 해볼까 합니다.
지난 포스트에서
WNetUseConnection API을 사용해서 네트워크 드라이브를 연결하는 방법에 대해 살펴보았습니다. 그런데 필요한 것은 네트워크 드라이브가 연결되어 있다면 굳이 이 함수를 호출할 필요가 없다는 것이죠. 그래서 이미 연결되어 있는 네트워크 드라이브 목록을 열거하는 API를 소개하고자 합니다.
Enumerating Network Drive
거두 절미하고, 현재 세션에 연결되어 있는 네트워크 드라이브 목록을 알려면 어케해야 하는가? 간단하다. WIN32 API는 모든 것을 구비하고 있으므로 WIN32 API를 호출하면 되겠다. 뭐 System.DirectoryServices 네임스페이스를 사용하는 방법도 있지만, ADSI 니 WinNT 프로바이더니 기타 등등 설명할 것이 너무 많으므로 대략 미루기로 한다.
WNet* API
우리가 하고자 하는 일은 다음과 같이 현재 컴퓨터에서 연결한 네트워크 드라이브 목록을 얻어내는 것이다. 이것을 알아내려면 Windows Networking API 인 WNetOpenEnum 함수와 WNetEnumResource 함수, 그리고 WNetCloseEnum 함수를 사용해야 한다.
화면 1. 네트워크 드라이브 연결 끊기
화면 1과 같은 다이얼로그를 나타내는 것이 목표가 아님에 유의하자. 화면 1과 같은 다이얼로그를 표시하려면 WNetDisconnectDialog 함수를 사용하면 된다. 여기서 필자가 말하고자 하는 것은 연결된 네트워크 드라이브 목록을 뽑아 내는 것임에 유의하자.
WNetOpenEnum() Function
WNetOpenEnum 함수는 열거할 네트워크 자원에 대한 명세를 하는 것이다. WNetOpenEnum 함수와 WNetEnumResource 함수는 접속된 네트워크 드라이브 목록을 나열만 해주는 것이 아니다. 현재 컴퓨터가 네트워크 상에서 검색한 다른 컴퓨터의 네트워크 자원들을 열거하는데도 사용될 수 있다. 우리가 원하는 것은 현재 로컬 컴퓨터가 접속한 원격 컴퓨터의 네트워크 드라이브(프린터 등은 제외)를 나열하는 것이다.
이 함수에 대한 P/Invoke 선언은 다음과 같다. 당근 C# 이다. VB.NET으로 바꾸는 건 어렵지도 않을 뿐더러 VB.NET 코드를 C# 으로 바꿔주는 사이트도 있으니 알아서 바꾸어 보기 바란다.
[DllImport("mpr.dll", CharSet=CharSet.Auto)]
public static extern int WNetOpenEnum(uint dwScope, uint dwType, uint dwUsage,
[MarshalAs(UnmanagedType.LPStruct)] NETRESOURCE lpNetResource,
out IntPtr lphEnum);
WNetOpenEnum 함수의 첫번째 매개변수 dwScope는 열거하고자 하는 네트워크 자원들의 범위를 나타내는 상수이다. RESOURCE_CONNECTED 상수 값을 주면 현재 연결된 네트워크 자원만을 열거해 준다. 이외에도 전체 네트워크 자원을 열거해주는 RESOURCE_GLOBALNET 이나, AD 도메인, 컴퓨터 등 현재의 CONTEXT에 대한 하위 자원들을 열거해 주는 RESOURCE_CONTEXT 등이 있지만 상세히 설명하지 않겠다. MSDN을 참고하기 바란다.
두 번째 매개변수 dwType은 네트워크 자원의 종류를 표시한다. 디스크(RESOURCETYPE_DISK), 프린터(RESOURCETYPE_PRINT)를 명세할 수 있다. 당근 우리는 RESOURCETYPE_DISK 값을 사용할 것이다.
세 번째 매개변수 dwUsage는 열거하고자 하는 자원의 용도를 표시하는데 사용된다. 예를 들어 연결할 수 있는 자원들만 표시한다던가(RESOURCEUSAGE_CONNECTABLE), 도메인이나 서버처럼 하위에 네트워크 자원을 갖는 컨테이너만 열거하고자 한다던가 등등... 이 매개변수에 0을 설정하면 모든 자원이 반환되며 우리의 코드도 0 값을 사용한다.
네 번째 매개변수 lpNetResource는 열거하고자 하는 자원의 시작점을 표시한다. 앞서 dwScope 매개변수에 열거할 네트워크 자원의 범위를 명시할 수 있다고 했고, dwScope가 RESOUCE_CONTEXT 라면 lpNetResource 매개변수가 나타내는 자원의 하위 자원을 열거해 준다. 우리는 연결된 네트워크 드라이브만을 찾을 것이므로 이 값에 null을 사용하면 된다. NETRESOUCE 구조체의 C# 선언은 지난 포스트에서 이미 다루었으므로 표시하지 않는다. 스크롤 압박... -_-
이 마지막 매개변수는 열거에 대한 핸들을 반환해 준다. 이 핸들값은 WNetEnumResource 함수 호출과 WNetCloseEnum 함수 호출에 사용된다.
다음은 WNetOpenEnum 에 사용되는 상수들 값을 C# 으로 선언한 것이다. 오오... 이 얼마나 친절한 블로그 인가 ? (즐 쳐드셈~~~ -_-)
public const uint RESOURCE_CONNECTED = 0x00000001;
public const uint RESOURCE_GLOBALNET = 0x00000002;
public const uint RESOURCE_REMEMBERED = 0x00000003;
public const uint RESOURCE_RECENT = 0x00000004;
public const uint RESOURCE_CONTEXT = 0x00000005;
public const uint RESOURCETYPE_ANY = 0x00000000;
public const uint RESOURCETYPE_DISK = 0x00000001;
public const uint RESOURCETYPE_PRINT = 0x00000002;
public const uint RESOURCEUSAGE_CONNECTABLE = 0x00000001;
public const uint RESOURCEUSAGE_CONTAINER = 0x00000002;
public const uint RESOURCEUSAGE_ATTACHED = 0x00000010;
public const uint RESOURCEUSAGE_ALL = (RESOURCEUSAGE_CONNECTABLE |
RESOURCEUSAGE_CONTAINER |
RESOURCEUSAGE_ATTACHED);
WNetEnumResouce() Function
이제 열거를 Open 했으니 반복적으로 데이터를 가져오면 된다. WNetEnumResource 함수가 정확하게 이 일을 수행한다. C# 선언을 살펴보자.
[DllImport("mpr.dll", CharSet=CharSet.Auto)]
public static extern int WNetEnumResource(IntPtr hEnum, ref int lpcCount,
IntPtr lpBuffer, ref int lpBufferSize);
선언이 좀 알딸딸 하다. 첫번째 매개변수 hEnum은 WNetOpenEnum 함수가 반환한 바로 그 열거 핸들값을 사용하면 된다. 두 번째 매개변수, lpcCount 는 함수 호출에서 몇 개의 네트워크 자원에 대한 정보를 반환할 것인가를 지정한다. 1 을 주면 1 개의 네트워크 자원을, 2를 주면 2개의 네트워크 자원을 반환한다. -1을 주면 다음 매개변수인 버퍼가 허용하는 만큼을 반환하며, ref 변수이므로 반환한 자원의 개수를 lpcCount에 설정해 준다.
lpBuffer 매개변수와 lpBufferSize 매개변수는 반환할 네트워크 자원에 대한 버퍼와 그 크기(바이트 단위)를 나타낸다. lpBuffer에 기록되는 네트워크 자원의 정보는 NETRESOUCE 구조체의 배열이다. 씨봉, 간단하게 WNetEnumResouce를 호출할 때마다 한 개씩 네트워크 자원 정보를 읽어오면 간단하고 쉬울 텐데... 이 함수는 버퍼를 사용한다. 대략 무슨말인고 하니... 버퍼와 버퍼의 크기를 알려주면 이 버퍼에 넣을 수 있을 만큼의 데이터를 집어 넣어 준다는 말이다. 그렇담 매 호출마다 NETRESOUCE 구조체 1개 분량만 넘겨주면 되겠네... 라고 생각하고 코딩했다가 30여분을 헤맸다... -_- NETRESOUCE 구조체를 찬찬히 살펴보면 문자열 필드들을 갖고 있고 이 문자열의 크기가 명시되어 있지 않다. 하지만 WNetEnumResouce 는 lpBuffer 매개변수에 NETRESOUCE 구조체 1개와 그 바로 뒤에 문자열들을 반환한다. 뭔 말인지 모르겠다면... WNetEnumResource가 반환하는 네트워크 자원에 대한 정보를 담는 데이터의 크기를 알 수 없다는 얘기이다. 이 때문에 lpcCount, lpBuffer, lpBufferSize 매개변수를 모두 주어야 하는 것이다.
조급해 할 필요 없다. WNetEnumResouce()를 사용하는 구체적인 예제코드를 나중에 제시할 터이니 걱정마라. 우리 프로그래머의 무적 도우미 Copy&Paste 가 있지 않은가? -_-
WNetCloseEnum() Function
이제 열거가 끝났다면 WNetCloseEnum() 메쏘드를 호출하면 된다. 이 함수는 눈물나게 고맙게도 대단히 간단하다.
[DllImport("mpr.dll", CharSet=CharSet.Auto)]
public static extern int WNetCloseEnum(IntPtr hEnum);
달랑 하나 있는 매개변수는 WNetOpenEnum이 반환했던 바로 그 열거 핸들값을 쓰면 된다. 끝~~
Sample Code
지금까지 소개한 WNet* 함수들을 호출하여 연결된 네트워크 드라이브 목록을 받아내는 메쏘드를 작성해보자. 작성할 메쏘드는 매개변수를 취하지 않고 연결된 네트워크 드라이브들에 대한 NETRESOURCE 구조체의 배열을 반환하도록 할 것이다. 코드를 보면서 이야기 하자.
public static NETRESOURCE[] GetNetworkDriveList()
{
int result;
int nrCount = -1;
int bufferSize = 4*1024;
NETRESOURCE nr = null;
int itemSize = Marshal.SizeOf(typeof(NETRESOURCE));
ArrayList items = new ArrayList();
int count, size;
IntPtr buffer;
IntPtr hEnum;
result = WNetOpenEnum(RESOURCE_CONNECTED, RESOURCETYPE_DISK, 0, null, out hEnum);
buffer = Marshal.AllocHGlobal(bufferSize);
try {
while(true) {
count = nrCount;
size = bufferSize;
result = WNetEnumResource(hEnum, ref count, buffer, ref size);
if (result == 259) // ERROR_NO_MORE_ITEMS
break;
IntPtr curPos = buffer;
for(int i=0; i < count; i++) {
nr = (NETRESOURCE)Marshal.PtrToStructure(curPos, typeof(NETRESOURCE));
items.Add(nr);
curPos = new IntPtr(curPos.ToInt32() + itemSize);
}
}
}
finally {
Marshal.FreeHGlobal(buffer);
WNetCloseEnum(hEnum);
}
return (NETRESOURCE[])items.ToArray(typeof(NETRESOURCE));
}
먼저 WNetOpenEnum 함수를 호출한다. 예고한 대로, 연결된 네트워크 드라이브 목록만을 알아보기 위해 RESOURCE_CONNECTED 와 RESOURCE_DISK 가 사용되었다. 이 두 매개변수가 사용되면 dwUsage, lpNetResource 매개변수에는 0과 null 을 주면 된다. 이제 WNetEnumResource를 반복적으로 호출하면 된다. 먼저 while 문은 반복적으로 WNetEnumResource를 호출하는데 사용된다. 1회의 WNetEnumResource의 호출로 모든 정보를 받아오지 못하는 경우도 있을 수 있기 때문에 필요한 것이다. result 값이 259 이면 더 이상 열거할 네트워크 드라이브가 없다는 뜻이다. while 문 안의 for 문은 WNetEnumResource가 1개 이상의 NETRESOURCE 구조체를 반환할 수 있기 때문에 필요하다. for 문 안에서는 Marshal.PrtToStructure 메쏘드를 호출하여 unmanaged 힙에 존재하는 데이터를 managed heap으로 마샬링 한다. NETRESOURCE 구조체가 버퍼 내에서 배열형태로 여러개가 존재하므로 포인터 연산 비스므레한 것을 수행해야 한다. 즉, WNetEnumResource 함수가 2개 이상의 NETRESOURCE 구조체를 반환하면 두 번째 이상의 NETRESOURCE 구조체를 마샬링하기 위해 포인터 연산이 필요하다는 것이다. IntPtr 값에 Marshal.SizeOf(NETRESOURCE) 값을 더함으로써 계산 할 수 있다. 위 메쏘드에서 한가지 주목할 점은 WNetEnumResource 가 unmanaged 버퍼를 요구한다는 점이다. 그래서 Marshal.AllocHGlobal을 호출하여 unmanaged heap 에 메모리를 할당하고 이것을 사용하였다.
어찌 되었건, 위 코드는 잘 작동한다(적어도 필자가 테스트한 결과...). 위 메쏘드를 호출하면 NETRESOURCE 배열을 반환하므로 이 배열에 대해 필요한 연산을 수행하면 된다. 다이얼로그에 출력한다던가, 연결된 네트워크 드라이브를 확인하여 연결되어 있지 않다면 필요한 연결을 한다던가 등등...
언제 써먹지?
장문에 걸쳐서 WIN32 API 3개를 설명했다. 궁극적으로 이 딴걸 대체 어따 써먹는가가 중요할 것이다. 필자의 경우는 웹 어플리케이션에서 파일서버에 파일을 기록할 때 사용했었다. 웹 서버가 2대 이상인 경우, 별도의 파일서버에 업로드 된 파일을 기록해야 하는 경우가 종종(자주) 생긴다. 2대 이상의 웹 서버가 단일한 파일 스토리지에 기록해야만 하므로 공유된 네트워크 폴더를 사용하기 마련이다. 이 때 이 네트워크 폴더를 액세스하기 위해 사용하는 방법이 web.config의 <identity> 설정을 이용하거나, 코드를 사용해서 명시적으로 impersonation을 하던지 (IIS 보안 관련 아티클 참조), 아니면 네트워크 드라이브를 연결해 버리면 된다. 네트워크 드라이브를 연결하는 방법은 이미 다른 포스트를 통해 소개한 적이 있다. 이 때, 네트워크 드라이브가 이미 연결되어 있는지 아닌지를 판별할 필요가 있다는 것이다. 이 때 이 글의 예제 코드를 써먹으면 유용할 것이다. 다른 용도도 많이 있을 테지만 여기서 다 열거하진 않겠다.
마치며...
항상 포스트를 다 쓰고 나면 이런 생각이 든다. "내가 이걸 왜 썼지?"... 나름대로 많이 찾아보고, 힘들게 작성한 글인데... 이 글을 읽는 사람들은 얼마나 도움이 될까? 쯔읍... -_-
Comments (read-only)
#struct에 null 할당이 가능한가요? / 김종호 / 8/29/2005 6:13:00 PM
코드를 따라하다가 보니 NETRESOURCE nr = null; 에서부터 에러가 나는군요. struct에 null 이 안되는데요...?
#re: 네트워크 드라이브 목록 뽑기 / 블로그 쥔장 / 8/29/2005 6:33:00 PM
엇... NETRESOURCE 는 클래스 인데요.. -_-
우워... 지난 포스트에 NETRESOURCE를 struct로 선언 했었네요... -_-
NETRESOUCE 선언 입니다... 이렇게 하셔야할 듯...
[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Auto)]
public class NETRESOURCE
{
public uint dwScope;
public uint dwType;
public uint dwDisplayType;
public uint dwUsage;
public string lpLocalName;
public string lpRemoteName;
public string lpComment;
public string lpProvider;
}