2010년 9월 29일 수요일

[InstallShield 2008] 레지스트리 키 기록











 프로그램 설치 시, 레지스트리를 등록해야 하는데, 스크립트에서 호출하는 DLL에서 해줄까 하다가.. 간단한 거는 그냥 InstallShield 스크립트로 기록하고자 했다.





 InstallShield 2008 마법사(?!)를 최대한 활용하고자 했는데.. 내가 넘겨 받은 설치본은 무작정 스크립트로 때려 박은 코드였다. 아... 언제나 느끼는 거지만 대충 만들어서 나한테 떠넘길때 정말 짜증.. 넘길꺼면 좀 제대로 만들기나 하지!!







레지스트리 등록





스크립트로도 할 것이지만 Installation Designer 에서 레지스트리 키를 등록. 여기서 작업할 때 장점은, 레지스트리의 등록을 구조적으로 쉽게 보여주고, Uninstall 시 같이 삭제할 것인지도 설정할 수 있다.













스크립트 작성





레지스트리 키 및 값을 등록하는 함수를 작성했다. 




function CreateRegInstallDir()


STRING szKey; // 레지스트리 키 경로


STRING szAppKey; // App 레지스트리 키 경로


STRING szApp; // App 이름


STRING szInstallDir;        // InstallDir 값 이름


begin                                      


REGDB_OPTIONS = REGDB_OPTIONS | REGDB_OPTION_WOW64_64KEY;


RegDBSetDefaultRoot(HKEY_LOCAL_MACHINE);          




szKey = "Software\\COMPANY\\AA\\BB";                     


szAppKey = "Software\\COMPANY\\AA\\BB\\Demo";


szApp = "Demo";                        


szInstallDir = "InstallDir";  




if (0 < RegDBKeyExist (szAppKey)) then 


RegDBCreateKeyEx(szKey, szApp);


endif;          




RegDBSetKeyValueEx(szAppKey, szInstallDir, REGDB_STRING, TARGETDIR, -1);  


REGDB_OPTIONS = REGDB_OPTIONS & ~REGDB_OPTION_WOW64_64KEY;


end; 










RegDBKeyExist() 함수를 이용하여 레지스트리 키가 있는지 검사한다. 없으면 RegDBCreateKeyEx() 함수를 이용하여 키를 생성한다. 값을 등록할때는 RegDBSetKeyValueEx() 함수를 이용한다. 마지막 파라미터로 기록할 값의 길이를 지정해 줘야 하는데 -1을 넘겨주면 알아서 계산해 준다. -1 파라미터를 사용할 수 있는 레지스트리 값 타입이 따로 있으니 조심.





각 함수는 도움말을 참조 하면 자세히 알 수 있다.















레지스트리 삭제



szKey = "Software\\App\\Data";


RegDBSetDefaultRoot(HKEY_LOCAL_MACHINE);         


if (1 == RegDBKeyExist (szKey)) then


RegDBDeleteKey (szKey); 


endif;

2010년 9월 28일 화요일

클래스 라이브러리에서 응용프로그램 실행 위치 구하기




 WinForm 응용프로그램의 실행 파일 경로는 System.Windows.Forms 네임스페이스에 있는 Application.StartupPath 프로퍼티를 참조하면 쉽게 구할 수 있다.


using System.Windows.Forms;

...

string path = Application.StartupPath;


 클래스 라이브러리가 WinForm 모듈이 아닐 경우에는 System.Reflection 네임스페이스에 있는 Assembly 클래스를 이용하면 된다. Assembly.GetEntryAssembly().Location 프로퍼티를 이용하면 실행 파일 이름을 포함한 전체 경로를 얻게 되므로 Path 클래스를 이용하면 쉽게 구할 수 있다.



using System.Reflection;

...

string path = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);



 Assembly.GetEntryAssembly().Location MSDN 설명을 보면 클래스 라이브러리 즉, 관리되는 코드를 관리되지 않는 코드에서 호출할 경우 null을 리턴한다고 나와있다. 이것만 주의해 주면 되겠다.



2010년 9월 26일 일요일

생성자 내부에서 발생한 예외 처리







생성자 내부에서 발생한 예외 처리








 Head First C# 책을 읽고 있는데 다음과 같은 글이 쓰여 있다.





"여기 유용한 팁이


있군요. C# 프로그래머


면접 시 생성자 내부에서


발생한 예외를


어떻게 처리할 것인지 묻는 


경우가 많죠."





음... 갑자기 정신이 바짝!! 든다.  MFC 프로그래머 면접때는 Main() 함수의 위치에 대해 물어보곤 했었는데. 후후.





생성자는 객체를 초기화 하기 위한 목적이며, 생성자가 성공적으로 호출되면 객체의 인스턴스가 생성된다. 생성자에서 예외가 발생하였다면 객체의 인스턴스를 완료하지 못한것이다. 즉, 생성자 내부에서 예외가 발생한다면 그 코드는 정상적인 객체를 생성하지 못하였으므로 코드를 다음부분으로 진행해서는 안된다. 예외가 발생할 수 있는 생성자를 가진 객체를 생성할 때, try-catch 블록으로 감싸주어 예외에 맞는 처리를 해주어야 한다.





다음 링크를 참조하면 더 쉽게 이해할 수 있다.




2010년 9월 12일 일요일

Attribute [Category], [Description]

Attribute [Category], [Description]







 아직까지 어트리뷰트를 많이 사용하지 않았다. 그 필요성을 느끼는 단계까지 나의 내공이 부족한 걸까? 아니면 환경이??

모... 불평하거나 위측하지 않으련다.



 CodeProject 에서 자료를 찾다가 남의 소스를 봤는데 희한한게 있었다. Category, Description 어트리뷰트가 바로 그것이다. 남의 코드와, 오픈소스를 많이 봐두면 좋다는 말.. 많이 들었는데 비로서 피부로 느낀다.



 Category, Description 어트리뷰트는 Property에 사용된다. 일반적으로 WinForm 컨트롤을 사용자 환경에 맞게 서브클래싱 할 때 VisualStudio 의 도움을 받을 수 있도록 사용한다.


 다음과 같이 Button 컨트롤 클래스를 상속받은 MyButton 클래스를 정의한다.




 프로퍼티를 구현한다.




 MyButton 컨트롤을 폼에 올려 놓았을 때, 컨트롤의 속성창을 보면 기타 라는 항목에 정의한 프로퍼티 목록이 보안다.

솔직히 속성 창에 보이는 것 만으로도 신기했다. 후후..




 속성 창 맨 아래 보면 설명이 나오는데, 프로퍼티에 정의한 XML 주석의 내용이 나올줄 알았다. 너무 쉽게 생각했나?!

아무튼 아무것도 나오지 않는다.

 



 MyButton 에 정의한 프로퍼티에 다음과 같은 어트리뷰트를 추가한다.




Category 어트리뷰트는 속성창에서 해당 프로퍼티가 위치할 항목을 지정한다. 




Description 어트리뷰트는 해당 속성의 출력할 설명을 지정한다.






 이 밖에도 Browsable, DesignerSerializationVisibility 프로퍼티가 사용되어지는 것 같은데 자료 찾기가 좀 어렵다.. 아직 MSDN이 어색하다..


2010년 9월 11일 토요일

List Constructor : List(Of T) Constructor (IEnumerable(Of T))

C# List<T> Constructor :

List(Of T) Constructor (IEnumerable(Of T))





 Generic Collection 의 등장으로 참 편하게 코딩하고 있다. MFC 할때는... 허흑;;

List<T> 를 아마 가장 많이 사용하지 않을까 싶다. List<T> 생성자는 3가지가 존재한다.




 매개변수가 없는 생성자와, int 인수를 갖는 생성자는 다들 잘 알고 있을테고.. 인수로 IEnumerable<T> 를 갖는 생성자에 대해 테스트해 보았다.

 IEnumerable<T> 인터페이스를 구현한 컬렉션을 인자로 받는다. 인자로 받은 컬렉션의 요소들을 복사하여 새로운 컬렉션 객체를 만든다. 복사라는 말이나왔다. 과연 Deep Copy 일까 Shallow Copy 일까?

테스트 코드는 다음과 같다.




     internal class Product


    {


        public string Name { get; set; }


        public int Cost { get; set; }


    }








    class Program


    {


        static void Main(string[] args)


        {


            var product1 = new List<Product>() { new Product() { Name = "P1a", Cost = 1 }, 


                                                           new Product() { Name = "P2a", Cost = 2 }};





            foreach (Product item in product1)            


                Console.WriteLine("{0}, {1}", item.Name, item.Cost);





            Console.WriteLine(Environment.NewLine);


            var product2 = new List<Product>(product1);


            foreach (Product item in product2)


                Console.WriteLine("{0}, {1}", item.Name, item.Cost);





            Console.WriteLine(Environment.NewLine);


            foreach (Product item in product1)


            {


                item.Name = item.Name + "Edit";


                Console.WriteLine("{0}, {1}", item.Name, item.Cost);


            }





            Console.WriteLine(Environment.NewLine);


            foreach (Product item in product2)


                Console.WriteLine("{0}, {1}", item.Name, item.Cost);   


        }


    }




출력 결과







 예제에서 보면은 product1 List 객체를 이용하여 product2를 생성하였다. 완전히 다른 두 객체일 거라 생각을 했지만 product1 의 내부 요소를 수정하니까 product2 의 내부 요소도 변경이 되었다. 즉, product1 과 product2 라는 두개의 객체가 생성 되었지만 내부적인 요소는 같은 참조 객체를 가리키고 있다는 것이다.

 List<T>(Enumerable(T)) 를 사용할 때, 완전히 다른 사본이 생성된게 아님을 주의하며 코딩해야 겠다.

2010년 9월 8일 수요일

Windows 에서 폴더 및 파일의 전체 경로 길이






 Windows 프로그래밍을 하면서 폴더 및 파일의 전체 경로를 설정할 때 덩그러니 TextBox 하나 두고 테스트 하였다. 뭐.. 지금까지는 급한 개발기간이였으니 그렇다 치더라도 이제는 입력값의 유효성을 체크하는 코드를 추가해야 겠다. 





  폴더 및 파일 의 전체 경로 이름의 제한 길이가 있다. 전체 경로 길이는 260자로 제한된다.





WinDef.h 파일에 정의된 MAX_PATH







stdlib.h 파일에 정의된 _MAX_PATH










 조금만 관심을 갖고 찾아 봤다면 알 수 있겠지만 260 길이 안에는 "드라이브 이름 + 폴더 이름 + 파일 이름 + NULL문자"  를 모두 합한 길이이다. 마지막 NULL 문자는 문자열을 구분하기 위해 사용되는 값이니 실질적으로는 259 길이의 경로를 사용하게 된다. 








폴더 이름의 최대 길이는 248?!





위에서 MAX_PATH 의 값이 260 이라 하여 폴더 이름의 길이가 260인 폴더를 만들 수 는 없다. 실제로 폴더를 만들어 보면, "0123456789 " 라는 문자열로 26번 반복해서 폴더 이름을 만들었다. 만드는 순간 어떠한 경고도 없기에 잘 만들어졌나 싶지만 실제로 길이를 확인해 보면 244 길이를 갖는 폴더가 만들어 진다.







폴더 생성을 C:\ 하위에서 했기 때문에 폴더의 전체 경로 앞부분에는 C:\ 가 붙는다. C:\ 문자열은 길이가 4 이므로 ("\"를 출력하기 위해선 "\\" 를 사용하므로) 폴더 이름의 최대 경로는 248이 된다.








최대 폴더 이름을 갖는 폴더의 하위 폴더 생성





위에서 생성한 폴더 하위에서 폴더를 생성하려 하면 Windows 에서 다음과 같은 오류 메시지를 출력하며 폴더 생성을 막는다. 







따라서, 최대 폴더 이름을 갖는 폴더에서는 하위 폴더를 생성할 수 없다.








최대 폴더 이름을 갖는 폴더의 하위 파일 생성





위에서 생성한 폴더 하위에서 파일을 추가하면.. 생각에는 하위 폴더 생성처럼 파일 생성을 막을 줄 알았지만 파일은 생성이 된다. 하지만 파일의 확장자를 포함한 파일 이름의 길이는 11자까지만 만들 수 있다.







최대 이름의 폴더를 생성할 때, Windows 차원에서 파일은 생성할 수 있도록 배려(?!)를 해준듯 하다. 폴더안에 아무것도 담을 수 없다면 그건 폴더 존재의 가치가 없으니까.








어째든 [드라이브명] [폴더 이름] [파일 이름] 을 합친 전체 경로는 260자를 넘길 수 없다.







※ C# 에서 위 규칙을 어기고 코딩하였을 경우 다음과 같은 예외 메시지가 발생한다.





2010년 9월 7일 화요일

[InstallShield 2008] 사용자 정의 설정파일 읽기








사용자 정의 설정파일 읽기














 cf). InstallShield 2008 사용





 InstallShield 에서 생성한 설치본으로 설치 시, 외부 파일로 부터 설치 정보를 읽어와 설치해야 되는 경우가 있다. 즉, 스크립트에서 해당 설정 파일의 내용을 읽어 설치하는 경우이다.


 InstallShield Proect 에서 지원하는 방법은 없는것 같다. 설정 파일을 ini 파일 대신 현재 추세(?)에 맞게 XML 파일을 사용하기로 한다. 








■ Script





 스크립트에서 파일을 읽을 수 있지만, InstallShield 에서 제공해주는 함수는 기능이 미약하고, 또 한 XML 파일 읽는 것은 더더욱 불편하다. 따라서 스크립트에서는 해당 설정 파일을 읽는 DLL 을 호출하기로 한다. 호출된 DLL 에서 해당 파일을 열고, XML 엘리먼트들을 파싱하여 설치 제어를 한다.









nDLLResult = XCopyFile("Config.xml",SUPPORTDIR,COMP_NORMAL);


if (nDLLResult != 0) then        


MessageBox(FormatMessage(nDLLResult), SEVERE);


abort;


endif;               





VarSave (SRCTARGETDIR);    


SRCDIR = SUPPORTDIR;





LaunchAppEx(szInstaller, "/Type " + SRCDIR, WAIT, SW_HIDE, nDLLResult); 


if (nDLLResult = 0) then        


abort;    


endif;            





VarRestore (SRCTARGETDIR);






 XCopyFile() 함수를 이용하여 설정 파일(위에선 Config.xml) 을 InstallShield 로 만든 설치 모듈이 사용하는 폴더에 복사 시킨다. XCopyFile() 함수에 대한 설명은 InstallShield 도움말을 참조하면 된다. 여기서 한가지 알 수 있는 것은 설치본 모듈이 실행되면 설치에 필요한 모든 파일들을 로컬 시스템의 임시 폴더에 압축을 풀어 복사한다. 그 후, setup.exe 모듈을 실행 시켜 설치 스크립트대로 설치를 진행한다. 임시 폴더는 시스템 마다 다르다. SUPPORTDIR 을 참조하면 임시 폴더 전체 경로를 얻을 수 있다. XCopyFile() 함수 동작이 실패 하면 리턴값으로 원인을 알려주는데 FormatMessage() 함수를 이용하면 문자열로 실패 이유를 알 수 있다. 





 LaunchAppEx() 함수로 설정 파일을 로드할 모듈을 실행 시킨다. 위 경우는 설정 파일의 경로를 실행 시킬 모듈의 매개변수로 전달해 주었다. 모듈에서는 매개변수를 읽어 설정 파일의 경로를 확인하고, 파일을 연다. 파일에서 설정들을 읽어와 설정 결과를 리턴값으로 리턴해 준다. 





 VarSave(SRCTARGETDIR) 는 변수들의 저장된 값을 임시로 저장하게 되어 VarRestore(SRCTARGETDIR) 함수를 호출하게 되면 원래 값으로 돌아가게 된다. (위 코드선 크게 의미가 없다.)








■ Remark





 InstallShield 2008 로 설치 모듈을 만들었을 경우 "Disk Image", "Log Files", "Package", "Report" 폴더가 생긴다. Disk Image 에서 여러개의 설치 모듈이 있는게 보이는데 그 위치에 설정 파일을 위치 시킨다. "Package" 폴더에는 하나의 설치 모듈이 생기는데 이 모듈과 같은 위치에 설치 파일을 위치 시켜도 설치 파일의 위치를 인식할 수 없다.