태터데스크 관리자

도움말
닫기
적용하기   첫페이지 만들기

태터데스크 메시지

저장하였습니다.

Url Resolver클래스는 아래와 같은 기능들을 가지고 있습니다.

상대경로를 절대경로로 변환
상위 경로로 이동
Base URL 구하기

상대경로를 절대경로로 변환하는 기능에 북마크, 쿼리는 고려하지 않았으며, 일반적인 경우에 모두 정상적으로 동작합니다.

참고자료:
URL Relative Syntax and Base URLs - http://www.tcpipguide.com/free/t_URLRelativeSyntaxandBaseURLs-3.htm

URL 상대경로의 처리 - http://ljh131.tistory.com/6

	public class UrlResolver
	{
		public static string GetBaseUrl(string url)
		{
			string baseUrl = url.Substring(0, url.LastIndexOf('/') + 1);

			return baseUrl;
		}

		// return false if base url is root reached
		public static bool GoUpOnePath(string baseUrl, out string newBaseUrl)
		{
			// 루트인지 체크 ('/' 개수가 3개이하면 중단)
			int searchIndex = 0;
			int count = 0;
			int currentIndex = 0;

			while (true)
			{
				currentIndex = baseUrl.IndexOf('/', searchIndex);

				if (currentIndex != -1)
					count++;
				else
					break;

				searchIndex = currentIndex + 1;
			}

			if (count <= 3)
			{
				newBaseUrl = baseUrl;

				return false;
			}

			// go up one
			int secondOfLastSlashIndex = baseUrl.LastIndexOf('/', baseUrl.Length - 2);
			newBaseUrl = baseUrl.Substring(0, secondOfLastSlashIndex + 1);

			return true;
		}

		/*
		 * based on http://www.tcpipguide.com/free/t_URLRelativeSyntaxandBaseURLs-3.htm
		 * this function NOT include bookmark, query and new scheme url
		 */
		public static string GetAbsoluteUrl(string baseUrl, string relativeUrl)
		{
			// baseurl의 마지막은 반드시 /임
			Debug.Assert(baseUrl.LastIndexOf('/') == baseUrl.Length - 1);

			string[] relativePaths = { "//", "/", "./", "../", ".." };
			bool resolveRelativePath = false;

			do
			{
				resolveRelativePath = false;

				foreach (string relativePath in relativePaths)
				{
					if (relativeUrl.IndexOf(relativePath) == 0)
					{
						resolveRelativePath = true;

						switch (relativePath)
						{
							case "//":	// 호스트 네임 지정
								baseUrl = "http://";
								break;

							case "/":	// 루트 지정
								while (GoUpOnePath(baseUrl, out baseUrl))
									;
								break;

							case "./":	// 현재 디렉토리. 무시
								break;

							case "../":	// 한단계 아래 디렉토리로 감. (더 이상 갈 수 없을때는 진행하지 않음.)
								GoUpOnePath(baseUrl, out baseUrl);
								break;

							case "..":	// 한단계 아래 디렉토리로 감. (../와 같음)
								GoUpOnePath(baseUrl, out baseUrl);
								break;
						}

						// remove relative path from url
						relativeUrl = relativeUrl.Substring(relativePath.Length);

						// reset loop
						break;
					}
				}
			} while (resolveRelativePath);

			return baseUrl + relativeUrl;
		}
	}


사실 익스플로러 툴바, BHO(Browser Helper Object)를 만드는게 여간 힘든일이 아닙니다. COM을 써야할 뿐더러 일반적인 프로그램이 아니기 때문에 정보 조차 잘 없죠.

하지만 C#으로 만들면 정말 쉽습니다.

우선 전 코드 프로젝트를 참고했습니다.

Band Objects - .NET 2.0 Redux
http://www.codeproject.com/csharp/BandObjects20.asp?select=1819328&df=100&forumid=306921&exp=0

 
위의 소스 파일은 코드 프로젝트의 소스를 사용하기 쉽게 약간 수정한 파일입니다. 기본 구조는 코드 프로젝트 소스와 마찬가지로 BandObject클래스를 상속받는 것입니다.

우선 다음 두 개의 레퍼런스를 추가하도록 합니다. COM의 MSHTML(Microsoft HTML Object Library)과 SHDocVw(Microsoft Internet Controls) 입니다. 두 레퍼런스를 추가하면 Object Browser에서 이들 어셈블리를 볼 수 있습니다.

다음 단계는 BandObject클래스를 상속받아 실제 툴바를 구현하는 단계입니다. 아래 코드는 사용자 참여 웹 툴바에서 사용된 예이며, 툴바와 BHO 구현에 필요한 핵심적인 코드만 적어 놓았습니다. Guid는 자신의 Guid를 적으세요. 

[Guid("719FAB8A-8E67-4ba6-915D-4EE1AD3A2CEE")]
[BandObject("사용자 참여 웹 툴바", BandObjectStyle.Horizontal | BandObjectStyle.ExplorerToolbar, HelpText = "사용자 참여 웹 툴바")]
public class UpwBandObject : BandObject
{
	public UpwBandObject()
	{
		InitializeComponent();

		minSize = new Size(100, 27); // 최소 크기를 반드시 적어줘야 합니다.

		SetSiteEvent += new SetSiteEventHandler(UpwBandObject_SetSiteEvent);
		ShowDWEvent += new ShowDWEventHandler(UpwBandObject_ShowDWEvent);
	}
	private void UpwBandObject_ShowDWEvent(bool fShow)
	{
		toolbarVisible = fShow;

		RefreshCurrentSite();
	}

	private void UpwBandObject_SetSiteEvent(object pUnkSite)
	{
		if (pUnkSite != null)
		{
			// add events
			webBrowserClass.BeforeNavigate2 += 
new DWebBrowserEvents2_BeforeNavigate2EventHandler(webBrowser_BeforeNavigate2);
			webBrowserClass.NavigateComplete2 += 
new DWebBrowserEvents2_NavigateComplete2EventHandler(webBrowser_NavigateComplete2);
			webBrowserClass.DocumentComplete += 
new DWebBrowserEvents2_DocumentCompleteEventHandler(webBrowser_DocumentComplete);
		}
		else
		{
			// remove events
			webBrowserClass.BeforeNavigate2 -= new DWebBrowserEvents2_BeforeNavigate2EventHandler(webBrowser_BeforeNavigate2);
			webBrowserClass.NavigateComplete2 -= new DWebBrowserEvents2_NavigateComplete2EventHandler(webBrowser_NavigateComplete2);
			webBrowserClass.DocumentComplete -= new DWebBrowserEvents2_DocumentCompleteEventHandler(webBrowser_DocumentComplete);
		}
	}

	private void webBrowser_BeforeNavigate2(object pDisp, ref object URL, ref object Flags, ref object TargetFrameName,
			  ref object PostData, ref object Headers, ref bool Cancel)
	{
	}

	private void webBrowser_NavigateComplete2(object pDisp, ref object URL)
	{
	}

	private void webBrowser_DocumentComplete(object pDisp, ref object URL)
	{
	}

	private void Navigate(string url)
	{
		object flags = null;
		object targetFrameName = null;
		object postData = null;
		object headers = null;

		webBrowserClass.Navigate(url, ref flags, ref targetFrameName, ref postData, ref headers);
	}
}


웹 브라우저 클래스의 이벤트들이 바로 BHO부분입니다. 이벤트에 관련된 부분은 Visual Studio의 Object Browser를 보면서 추가할 수 있습니다. 물론 MSDN에도 짧게나마 설명이 있습니다.

UserControl을 상속받으면 Visual Studio에서는 Designer로 폼을 편집할 수 있습니다. View Designer를 클릭해서 나오는 화면이 툴바에 나오게 될 화면입니다. 그 부분은 개발하고자 하는 툴바에 맞춰서 디자인 하시면 됩니다.

마지막 단계는 실제 툴바를 익스플로러에 등록하는 과정입니다. 우선 프로젝트 속성의 Assembly Information에서 Make assembly COM-Visible을 체크하고 컴파일을 합니다. 또 Strong name을 위해 Signing assembly (.snk)가 필요한데 이것 역시 프로젝트 속성에서 만들 수 있습니다.

그리고 빌드 후에 다음 명령을 실행 시킵니다.

cd $(ProjectDir)bin\Debug

"C:\Program Files\Microsoft Visual Studio 8\SDK\v2.0\Bin\gacutil" /if MyToolbar.dll
"C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\regasm" MyToolbar.dll

익스플로러를 열면 툴바 영역에서 등록된 툴바가 메뉴에 표시됩니다.

디버깅은 프로퍼티의 Start Action에 externel program으로 익스플로러를 지정하면 됩니다.

혹시 궁금증 있으신 분은 코멘트를 남기시면 답변 드리겠습니다.



C#에서 DLL의 Unmanaged function을 호출하려면 다음과 같은 방법으로 함수를 만들어야 합니다. 아래 글의 출처는 msdn입니다.

To consume exported DLL functions

1. Identify functions in DLLs.
Minimally, you must specify the name of the function and name of the DLL that contains it.

2. Create a class to hold DLL functions.
You can use an existing class, create an individual class for each unmanaged function, or create one class that contains a set of related unmanaged functions.

3. Create prototypes in managed code.
[C#] Use the DllImportAttribute to identify the DLL and function. Mark the method with the static and extern modifiers.

4. Call a DLL function.
Call the method on your managed class as you would any other managed method. Passing structures and implementing callback functions are special cases.

가장 일반적인 방법이죠. 좀 복잡할테니 예를 들어서 설명하겠습니다.

우리가 필요한 함수는 Win32API함수인 EnumDisplayDevice()라는 함수입니다. 함수의 프로토타입은 다음과 같습니다.

BOOL EnumDisplayDevices(
  LPCTSTR lpDevice,                // device name
  DWORD iDevNum,                   // display device
  PDISPLAY_DEVICE lpDisplayDevice, // device information
  DWORD dwFlags                    // reserved
);
이제 이 함수 프로토타입을 C#용으로 고쳐야 합니다.

가장 먼저 할 일은 함수가 있는 DLL파일입니다. 이건 MSDN을 통해 쉽게 찾을 수 있죠. User32.dll에 있다는 것이 바로 확인됩니다.

두번째는 본격적으로 함수의 프로토타입을 C#으로 만드는 과정입니다.

우선 첫번째 인자를 볼까요? 문자열을 나타내는 LPCTSTR입니다. long pointer to const string이죠. 이 타입을 어떻게 바꿔야 할까요?

여기서 반드시 참고해야 할 것이 MSDN의 Platform Invoke Data Types 표입니다.

http://msdn2.microsoft.com/en-us/library/ac7ay120(vs.80).aspx

이 표를 보면 Win32API나 C스타일 함수에서 사용되는 데이터 타입들에 대응되는 Managed class type들이 나와있습니다. 표를 보면서 C#에서의 파라매터 타입을 정해주면 됩니다. 표에 따르면 - LPCTSTR은 직접 나와있지 않지만 - 얼추 string에 대응됩니다.

마찬가지로 DWORD는 UInt32입니다. 문제는 세번째 인자인 PDISPLAY_DEVICE인데요, 구조체의 포인터죠.

아쉽게도 이 경우에 대해서는 MSDN에서 명확한 해답을 찾을 수 없었습니다. 하지만 제가 알아본 바로는 대략 구조체를 클래스로 변환한 뒤, In, Out Attribute을 결정해주면 됩니다.

구조체인 DISPLAY_DEVICE에 대응되는 클래스를 만들기 전에 프로토타입 부터 설정해봅시다. MSDN을 보면 이 파라매터는 out 타입입니다. 따라서 In과 Out Attribute를 넣어줍니다. 완성된 프로토타입은 다음과 같습니다.

  [DllImport("user32.dll")]
  public static extern bool EnumDisplayDevices(string lpDevice, UInt32 iDevNum,
             [In, Out] DisplayDevice lpDisplayDevice, UInt32 dwFlags);
이제 할일은 구조체 DISPLAY_DEVICE를 클래스로 변환하는 작업입니다. 구조체의 프로토타입을 봅시다.

typedef struct _DISPLAY_DEVICE {
  DWORD cb;
  TCHAR DeviceName[32];
  TCHAR DeviceString[128];
  DWORD StateFlags;
  TCHAR DeviceID[128];
  TCHAR DeviceKey[128];
} DISPLAY_DEVICE, *PDISPLAY_DEVICE;
여기서 문제는 배열이죠. C#의 배열은 C의 배열과 메모리 할당 방식이 다릅니다. 이 문제는 MarshalAs Attribute를 이용해서 해결할 수 있습니다.

완성된 클래스는 아래와 같습니다.

 [StructLayout(LayoutKind.Sequential)]
 public class DisplayDevice
 {
  public UInt32 cb;  
  [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
  public string DeviceName;  
  [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
  public string DeviceString;  
  public UInt32 StateFlags;  
  [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
  public string DeviceID;  
  [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
  public string DeviceKey;
 }
구조체가 선언된 순서대로 할당되는 것처럼 클래스 Attribute도

 [StructLayout(LayoutKind.Sequential)]

로 만들어서 구조체와 마찬가지로 순차적으로 변수를 할당한다고 선언을 해주면 됩니다.

드디어 프로토타입이 완성되었습니다!

하지만 실제로 함수를 호출하려면 아직 남은 것이 한가지 있습니다.

'Before calling EnumDisplayDevices, you must initialize the cb member of DISPLAY_DEVICE to the size, in bytes, of DISPLAY_DEVICE.'

함수를 호출하기 전에 구조체(여기서는 클래스)의 cb멤버를 구조체의 크기로 설정해야 한다는 것입니다. C에선 sizeof(DISPLAY_DEVICE)로 가능합니다. 하지만 C#에서는 어떻게 해야할까요?

물론 C#에도 sizeof키워드가 있습니다만 여기서는 동작하지 않습니다. 이 경우에도 마샬을 이용해야 하는데, 코드는 아래와 같습니다.

   DisplayDevice dd = new DisplayDevice();
   dd.cb = (UInt32)Marshal.SizeOf(dd);
이제 EnumDisplayDevices함수를 C#에서 호출할 수 있게 되었습니다.

기타 함수들에서 사용되는 상수들은 직접 헤더 파일에서 찾아서 넣어주면 됩니다.

자세한 설명이 필요하면 아래 링크를 참고하세요

http://www.canaware.com/default.aspx?page=ed51cde3-d979-4daf-afae-fa6192562ea9&article=cfd2e407-d20f-4dfc-8c34-e7d29c771b05

말그대로 컨픽 파일을 손수 만들 필요없이 자동으로 해줍니다.

과연 얼만큼 자동으로 해줄까요?

일단 컨픽 파일은 아래와 같은 XML파일입니다.


윈9X에서는 INI파일을 사용했지만 시대가 시대인만큼 컨픽 파일에서도 XML을 쓰는군요.

이 부분이 바로 우리 컨픽 내용입니다.

소스를 통해서 볼까요?

참고로 프로젝트 레퍼런스에서 System.configuration을 추가해야 된답니다.

using System;
using System.Collections.Generic;
using System.Text;
using System.Configuration;
using System.Reflection;
namespace ConfigTest
{
	public class MySection : ConfigurationSection
	{
		[ConfigurationProperty("fileName", DefaultValue = "default.txt")]
		[StringValidator(InvalidCharacters = " ~!@#$%^&*()[]{}/;'\"|\\", MinLength = 1, MaxLength = 60)]
		public string FileName
		{
			get
			{
				return (string)this["fileName"];
			}
			set
			{
				this["fileName"] = value;
			}
		}

		[ConfigurationProperty("maxUsers", DefaultValue = (long)10000)]
		[LongValidator(MinValue = 1, MaxValue = 1000000, ExcludeRange = false)]
		public long MaxUsers
		{
			get
			{
				return (long)this["maxUsers"];
			}
			set
			{
				this["maxUsers"] = value;
			}
		}
	}

	class Program
	{
		static void CreateSection()
		{
			try
			{
				const string MySectionName = "MySection";
				MySection customSection;

				// open config file
				Configuration config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);

				if (config.Sections[MySectionName] == null)
				{
					customSection = new MySection();

					config.Sections.Add(MySectionName, customSection);

					customSection.SectionInformation.ForceSave = true;
					config.Save(ConfigurationSaveMode.Full);
				}

				// read
				MySection section = config.Sections[MySectionName] as MySection;

				Console.WriteLine(section.FileName);

				// modify
				section.FileName = "new_file.txt";

				config.Save(ConfigurationSaveMode.Full);

				Console.WriteLine(section.FileName);
			}
			catch (ConfigurationErrorsException err)
			{
				Console.WriteLine(err.ToString());
			}
		}

		static void Main(string[] args)
		{
			CreateSection();
		}
	}
}
C#의 재미있는 기능 중 하나인 리플렉션을 사용했군요. ConfigurationProperty를 이용해서 어떤 이름으로 저장할 것인지, 게다가 디폴트 값까지 지정할 수 있습니다.

아래 Validator는 ASP.NET에서나 보던 것이군요. ASP.NET과 마찬가지로 문자열에서 안되는 문자라던지 정수형에서 변수 값 범위 등을 설정할 수 있습니다.

별다른 변수 생성을 통해서는 안되고 저런식으로 '리플렉션과 프로퍼티'를 이용해야 한답니다. 개인적으로 프로퍼티를 잘 사용하는 편은 아니지만 이만큼 편하다면 어쩔 수 없지요.

아무튼 저런식으로 지정하고 나면, 클래스를 통채로 저장하는 듯한 기분입니다. 클래스 내에서 지정만 해주면 저장하고 읽는 부분은 매우 간단합니다.

CreateSection() 메소드에서는 컨픽 파일을 열고 - 없으면 생성한 후 - 컨픽을 로드한 후, 다시 수정하고 저장하고 값을 확인하는 테스트를 합니다.

참 편리하죠?

참고:
http://msdn2.microsoft.com/en-us/library/system.configuration.configurationpropertyattribute(vs.80).aspx



c#에선 단순히 스레드만 여러개 만든다고 멀티 프로세싱이 되진 않는다. 스레드가 어느 프로세서에서 실행될 수 있는지를 지정해줘야 한다.
public static void ThreadProc(object threadId)
{
	int id = (int)threadId;

	while(true)
	{
		int sum = 0;

		for (int i = 0; i < 1000000; i++)
		{
			sum += i;
		}

		Console.Out.Write(string.Format("{0} ", id));
	}
}

static void Main(string[] args)
{
	const int NumOfThread = 4;

	Thread[] threads = new Thread[NumOfThread];

	for (int i = 0; i < NumOfThread; i++)
	{
		threads[i] = new Thread(ThreadProc);
		threads[i].Start(i);
	}

	Process currentProcess = Process.GetCurrentProcess();

	foreach (ProcessThread processThread in currentProcess.Threads)
	{
		processThread.ProcessorAffinity = currentProcess.ProcessorAffinity;
	}

	for (int i = 0; i < NumOfThread; i++)
	{
		threads[i].Join();
	}
}

currentProcess.ProcessorAffinity는 현재 프로세스가 실행될 수 있는 프로세서에 대한 비트마스크이다. 예를들어 프로세서가 4개라면 프로그램은 프로세서1부터 4까지 프로세서 중 하나에서 실행될 수 있다. 이 비트마스크를 10진수로 표현하면 15이다.

반면 processThread.ProcessorAffinity는 기본적으로 해당 프로세스가 돌아가는 프로세서 내에서만 동작할 수 있게 되어있다. 이 값을 모든 프로세서안에서 실행될 수 있게 바꿔주는 것이다.