속성(property)------------------------------------------------------------------------------------------------------------------------


객체지향프로그래밍에서 일반적으로 맴버에 접근하기 위한 방법으로 Get함수와 Set함수를 만드는 것이 일반적이다.

하지만 C#에서는 이를 좀더 간편하게 하기 위해서 프로퍼티라고 부르는 문법을 지원한다.


namespace ConsoleApplication1

{

    class home

    {

        int age; -> C#에서도 C++과 같이 다른 지정이 없으면 private로 선언된다.


        public int Age -> age와는 다른 Age를 property로 선언했다.

        {

            get

            {

                return age; -> 값을 가져오는 속성을 저장한다.

            }

            set

            {

                age = value; -> 값을 대입하는 속성을 저장한다.

            }

        }

    }


    class Program

    {

        static void Main()

        {

            home home1 = new home();

            home1.Age = 5; -> 자동으로 Age중 set을 호출한다.

            Console.WriteLine(home1.Age); -> 자동으로 Age중 get을 호출한다.

            int a;

            a = home1.Age; -> 자동으로 Age중 get을 호출한다.

            Console.WriteLine(a);

        }

    }

}


참 편하다. 일일이 함수를 호출할 필요가 없이 자연적으로 연산자 오버로딩을 한것처럼 사용할 수가 있다.


하지만 속성은 더욱더 간략화된 방식으로 이용이 가능하다 다음을 보자.


namespace ConsoleApplication1

{

    class Contact

    {

        // Read-only properties.

        public string Name { get; private set; } -> get은 선언을 하나로 묶는다. set은 private성향으로 아무나 접근하지 못하도록 막는다.

        public string Address { get; private set; } -> get은 선언을 하나로 묶는다. set은 private성향으로 아무나 접근하지 못하도록 막는다.


        // Public constructor.

        public Contact(string contactName, string contactAddress) -> 생성자 그런데 public

        {

            Name = contactName;

            Address = contactAddress;

        }

    }


    // This class is immutable. After an object is created,

    // it cannot be modified from outside the class. It uses a

    // static method and private constructor to initialize its properties.   

    public class Contact2

    {

        // Read-only properties.

        public string Name { get; private set; } -> get은 선언을 하나로 묶는다. set은 private성향으로 아무나 접근하지 못하도록 막는다.

        public string Address { get; private set; } -> get은 선언을 하나로 묶는다. set은 private성향으로 아무나 접근하지 못하도록 막는다.


        // Private constructor.

        private Contact2(string contactName, string contactAddress) -> 생성자 그런데 private

        {

            Name = contactName;

            Address = contactAddress;

        }


        // Public factory method.

        public static Contact2 CreateContact(string name, string address)

        {

            return new Contact2(name, address); -> 내부적으로 생성자를 막고 마치 싱글톤처럼 새로운 녀석을 리턴해 주고 있다.

        }

    }


    public class Program

    {

        static void Main()

        {

            // Some simple data sources.

            string[] names = {"Terry Adams","Fadi Fakhouri", "Hanying Feng", 

                              "Cesar Garcia", "Debra Garcia"};

            string[] addresses = {"123 Main St.", "345 Cypress Ave.", "678 1st Ave",

                                  "12 108th St.", "89 E. 42nd St."};


            // Simple query to demonstrate object creation in select clause.

            // Create Contact objects by using a constructor.

            var query1 = from i in Enumerable.Range(0, 5)

                         select new Contact(names[i], addresses[i]); -> 새로운 클래스를 집어 넣는다.


            // List elements cannot be modified by client code.

            var list = query1.ToList();

            foreach (var contact in list)

            {

                Console.WriteLine("{0}, {1}", contact.Name, contact.Address);

            }


            // Create Contact2 objects by using a static factory method.

            var query2 = from i in Enumerable.Range(0, 5)

                         select Contact2.CreateContact(names[4 - i], addresses[4 - i]);


            Console.WriteLine();


            // Console output is identical to query1.

            var list2 = query2.ToList();


            foreach (var contact in list2)

            {

                Console.WriteLine("{0}, {1}", contact.Name, contact.Address); 

-> 값을 가져오는 것은 아무런 이상이 없다. 즉 get은 이상없이 작동한다. 애초에 public였으니 하지만

                contact.Name = 0; 이런 구문을 쓰면 당근 막힌다.


            }


            // List elements cannot be modified by client code.

            // CS0272:

            // list2[0].Name = "Eugene Zabokritski"; 


            // Keep the console open in debug mode.

            Console.WriteLine();

            Console.WriteLine("Press any key to exit.");

            Console.ReadKey();

        }

    }


}

Posted by JJOREG

컴포지트패턴 --------------------------------------------------------------------------------------------------

  클래스를 사용할때 우리는 대부분 다형성을 기반으로 클래스를 디자인한다.

  객체를 생성하고 관리하려면 먼저 그 객체의 자료형이 클래스가 정의되어야 하는데.

  어떤때는 여러개의 틀을 하나로 모아서 사용해야할 경우가 존재한다.

  즉 하나의 클래스에 상속을 받는 부분부분을 모아서 새로운 틀을 만들어야하는 경우가 존재한다.

  이 경우 이 객체를 복합객체(composite object) 혹은 복합 클래스(composite class)라고 한다.


패턴적용 스토리 -----------------------------------------------------------------------------------------------

  당신은 메카닉 액션 게임을 만들게 되었다. 메카닉 액션게임에서 공격에 사용되는 탄환을 디자인하게 되었다.

  그런데 전탄발사라고 하는 새로운 기능을 만들게 되었다.

  기존의 만들어진 탄환들을 모아서 발사한다.

  그래서 부분이면서 전체가 될 포함할 수 있는 컴포지트 패턴을 사용하기로 했다.


uml -----------------------------------------------------------------------------------------------

보면 알겠지만 전탄발사는 Arms를 포함하면서도 Arms에 속해있다.

또한 내부에서 stl List를 통해서 Arms를 다수 포함할수 있는 구조이다.

(굳이 이런 구조가 아니어도 된다. Arms를 2개 이상 가지고 있어도 충분하다.)

또한 프로토타입 패턴이 섞여있는데 각 객체는 프로토타입에 의해서 생성된다.


코드 -----------------------------------------------------------------------------------------------


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
#pragma once
#include "include.h"
 
typedef struct _tagBulletInfo
{
    int iBulletType;
    int    iBulletSpeed;
    int    iBulletDamage;
 
    _tagBulletInfo(void)
    :iBulletType(0), iBulletSpeed(0), iBulletDamage(0) { }
 
    _tagBulletInfo(const int _Type, const int _Speed, const int _Damage)
    :iBulletType(_Type), iBulletSpeed(_Speed), iBulletDamage(_Damage) { }
 
}BULLETINFO, *PBULLETINFO;
 
class CArms
{
protected:
    BULLETINFO    m_BulletInfo;
 
public:
    virtual void AddArms(CArms* const _Arms) { }
 
public// setting
    virtual void BulletSetting(const BULLETINFO& _BulletInfo) = 0;
 
public// progress
    virtual void ArmsFire(void)
    {
        cout << "타입 :" << m_BulletInfo.iBulletType 
             << "스피드 :" << m_BulletInfo.iBulletSpeed 
             << "위력 :" << m_BulletInfo.iBulletDamage 
             << "무기준비 완료" << endl; 
    }
 
public// progress
    virtual CArms* clone(void) = 0;
 
public:
    CArms(void) {}
    CArms(const BULLETINFO& _BulletInfo) { m_BulletInfo = _BulletInfo; }
    CArms(const CArms& _Bullet) { *this = _Bullet; }
    virtual ~CArms(void) {}
};
 
 
class CVulcan
    : public CArms
{
public:
    virtual void BulletSetting(const BULLETINFO& _BulletInfo)
    { m_BulletInfo = _BulletInfo; }
 
    virtual void ArmsFire(void)
    { 
        CArms::ArmsFire();
        cout << "발칸을 발사했다!!!" << endl; 
    }
 
    virtual CArms* clone(void)
    { return new CVulcan(*this); }
 
public:
    CVulcan(void) {}
    CVulcan(const BULLETINFO& _BulletInfo) { m_BulletInfo = _BulletInfo; }
    CVulcan(const CVulcan& _Vulcan) { *this = _Vulcan; }
    virtual ~CVulcan(void) {}
};
 
class CPistol
    : public CArms
{
public:
    virtual void BulletSetting(const BULLETINFO& _BulletInfo)
    { m_BulletInfo = _BulletInfo; }
 
    virtual void ArmsFire(void)
    { 
        CArms::ArmsFire();
        cout << "권총을 발사했다!!!" << endl; 
    }
 
    virtual CArms* clone(void)
    { return new CPistol(*this); }
 
public:
    CPistol(void) {}
    CPistol(const BULLETINFO& _BulletInfo) { m_BulletInfo = _BulletInfo; }
    CPistol(const CPistol& _Pistol) { *this = _Pistol; }
    virtual ~CPistol(void) {}
};
 
 
class CRocket
    : public CArms
{
public:
    virtual void BulletSetting(const BULLETINFO& _BulletInfo)
    { m_BulletInfo = _BulletInfo; }
 
    virtual void ArmsFire(void)
    { 
        CArms::ArmsFire();
        cout << "로켓을 발사했다!!!" << endl; 
    }
 
    virtual CArms* clone(void)
    { return new CRocket(*this); }
 
public:
    CRocket(void) {}
    CRocket(const BULLETINFO& _BulletInfo) { m_BulletInfo = _BulletInfo; }
    CRocket(const CRocket& _Rocket) { *this = _Rocket; }
    virtual ~CRocket(void) {}
};
 
class CAllArmsFire
    : public CArms
{
private:
    list<CArms*>    m_ArmsList;
 
public:
    virtual void AddArms(CArms* const _Arms) { m_ArmsList.push_back(_Arms); }
 
public:
    virtual void BulletSetting(const BULLETINFO& _BulletInfo) {}
 
public
    virtual void ArmsFire(void
    {
        list<CArms*>::iterator ArmsIter = m_ArmsList.begin();
 
        cout << endl;
        cout << "전탄 발사!!!!" << endl;
 
        while(ArmsIter != m_ArmsList.end())
        { 
            (*ArmsIter)->ArmsFire(); 
            ++ArmsIter;
        }
    }
 
public
    virtual CArms* clone(void
    {
        CArms* pArms = new CAllArmsFire();
    
        list<CArms*>::iterator ArmsIter = m_ArmsList.begin();
 
        while(ArmsIter != m_ArmsList.end())
        {
            pArms->AddArms((*ArmsIter)->clone());
            ++ArmsIter;
        }
 
        return pArms;
    }
 
public:
    CAllArmsFire(void) {}
    CAllArmsFire(const CAllArmsFire& _AllArmsShot) { *this = _AllArmsShot; }
    virtual ~CAllArmsFire(void
    {
        list<CArms*>::iterator ArmsIter = m_ArmsList.begin();
 
        while(ArmsIter != m_ArmsList.end())
        {
            SAFE_DELETE((*ArmsIter));
            ++ArmsIter;
        }
        m_ArmsList.clear();
    }
};
 
class CArmsPoroto
{
private:
    map<string, CArms*>    m_ArmsProtoMap;
 
public:
    void InitArmsProto(void)
    {
        CArms* pArms = NULL;
        CArms* pAllFire = new CAllArmsFire;
 
        pArms = new CVulcan();
        pArms->BulletSetting(BULLETINFO(0, 10, 10));
        m_ArmsProtoMap.insert(map<string, CArms*>::value_type("CVulcan", pArms));
        pAllFire->AddArms(pArms->clone());
 
        pArms = new CPistol();
        pArms->BulletSetting(BULLETINFO(1, 15, 10));
        m_ArmsProtoMap.insert(map<string, CArms*>::value_type("CPistol", pArms));
        pAllFire->AddArms(pArms->clone());
 
        pArms = new CRocket();
        pArms->BulletSetting(BULLETINFO(2, 5, 20));
        m_ArmsProtoMap.insert(map<string, CArms*>::value_type("CRocket", pArms));
        pAllFire->AddArms(pArms->clone());
 
        m_ArmsProtoMap.insert(map<string, CArms*>::value_type("CAllArmsFire", pAllFire));
    }
 
public:
    CArms* GetCloneArms(const string& _ProtoKey)
    {
        map<string, CArms*>::iterator ProtoIter = m_ArmsProtoMap.find(_ProtoKey);
 
        if(ProtoIter == m_ArmsProtoMap.end())
            return NULL;
 
        return ProtoIter->second->clone();
    }
 
public:
    CArmsPoroto(void) {}
    virtual ~CArmsPoroto(void)
    {
        map<string, CArms*>::iterator ProtoIter = m_ArmsProtoMap.begin();
 
        while(ProtoIter != m_ArmsProtoMap.end())
        {
            SAFE_DELETE(ProtoIter->second);
            ++ProtoIter;
        }
        m_ArmsProtoMap.clear();
    }
};
 
// Composite.cpp : 콘솔 응용 프로그램에 대한 진입점을 정의합니다.
//
 
#include "stdafx.h"
#include "composite.h"
 
 
int _tmain(int argc, _TCHAR* argv[])
{
#if defined(DEBUG) | defined(_DEBUG)
    _CrtSetDbgFlag( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF );
    //_CrtSetBreakAlloc(219);
#endif
 
    CArmsPoroto ProtoCreator;
 
    ProtoCreator.InitArmsProto();
 
    CArms* pVulcan = ProtoCreator.GetCloneArms("CVulcan");
    CArms* pPistol = ProtoCreator.GetCloneArms("CPistol");
    CArms* pRocket = ProtoCreator.GetCloneArms("CRocket");
    CArms* pAllFire = ProtoCreator.GetCloneArms("CAllArmsFire");
 
    pVulcan->ArmsFire();
    pPistol->ArmsFire();
    pRocket->ArmsFire();
    pAllFire->ArmsFire();
        
    SAFE_DELETE(pVulcan);
    SAFE_DELETE(pPistol);
    SAFE_DELETE(pRocket);
    SAFE_DELETE(pAllFire);
 
    return 0;
}
 
 





Posted by JJOREG

브릿지패턴 --------------------------------------------------------------------------------------------------

객체란 선언과 구현으로 나뉜다.

선언이란 외부에 공개되는 인터페이스(함수나 맴버들)을 의미하며.

구현이란 인터페이스의 실제적인 동작을 정의해 놓고 있다.

하지만 하나의 구현파일안에 조건에 따른 다른 내용으로 분류되거나 하는 일이 있을수 있다.

하나의 클래스가 어떠한 일을 하는데 조건문에 따른 코드가 너무 길어져서 가독성이 떨어지고 처리해야할 코드가 길어진다면 어떻게 해야할까?

혹은 몇개의 함수만 다른 동작을 하고 나머지는 완전히 똑같은 동작을 하는 클래스가 여러개 있을때.

이들을 어떻게 하면 효율적으로 모으고 관리할 수 있을까?

그에 대한 새로운 방식을 지원해주는 것이 브릿지 패턴이라고 할 수 있다.


패턴적용 스토리 -----------------------------------------------------------------------------------------------

당신은 몬스터의 인공지능을 맡게 되었다. 

그런데 완전히 같은 몬스터라도 인공지능이 다르게 구현될 수 있다고 한다.

즉 같은 몬스터 A라도

소심한 몬스터A

대담한 몬스터A

비열한 몬스터A

같은 식으로 구현이 될 수 있다는 이야기 이다.

그럼 그것을 효율적으로 해결하기 위한 방법을 보자.


uml -----------------------------------------------------------------------------------------------


몬스터 클래스는 내부에 브릿지를 가지고 있다. 이 브릿지는 몬스터의 AI를 담당한다.

몬스터 AI를 담당하는 브릿지가 있기 때문에 몬스터 클래스 내부에서는 실제적인 AI의 동작을

몬스터끼리 달라져야할 부분만 구성하고 다른 부분은 몬스터 내부의 메소드에서 처리하면 된다.

즉 인터페이스의 구현을 연결된 브릿지에 맡김으로 해서 한 클래스에 코드과중이나 역할을 분산시킬 수 있다.



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
#pragma once
 
#include "stdafx.h"
#include "include.h"
 
enum MONSTER_AI
{
    MONSTER_AI_WILD,
    MONSTER_AI_CHICKEN,
    MONSTER_AI_BASTARD
};
 
typedef struct tagMonsterInfo
{
    MONSTER_AI    iAiPatton;
    int            iAtt;
 
    tagMonsterInfo(void) {}
    tagMonsterInfo(const MONSTER_AI& _iAiPatton, const int& _iAtt)
    {
        iAiPatton = _iAiPatton;
        iAtt = _iAtt;
    }
 
}MONSTERINFO, *PMONSTERINFO;
 
class CMonsterAiBridge
{
public:
    virtual void AiAction(const MONSTERINFO& _MonsterInfo) const = 0;
 
public:
    CMonsterAiBridge(void) {}
    virtual ~CMonsterAiBridge(void) {}
};
 
class CWildMonsterAi
    : public CMonsterAiBridge
{
public:
    virtual void AiAction(const MONSTERINFO& _MonsterInfo) const
    {
        cout << "************* 사나운 몬스터AI *************" << endl;
        cout << "사나우니까 공격!" << _MonsterInfo.iAtt << "피해를 입혔다" << endl;
        cout << "사나우니까 공격!" << _MonsterInfo.iAtt << "피해를 입혔다" << endl;
        cout << "사나우니까 공격!" << _MonsterInfo.iAtt << "피해를 입혔다" << endl;
        cout << endl;
    }
 
public:
    CWildMonsterAi(void) {}
    virtual ~CWildMonsterAi(void) {}
};
 
class CChickenMonsterAi
    : public CMonsterAiBridge
{
public:
    virtual void AiAction(const MONSTERINFO& _MonsterInfo) const
    {
        cout << "************* 겁쟁이 몬스터AI *************" << endl;
        cout << "플레이어와 거리가 가깝다! 도망!" << endl;
        cout << "플레이어와 거리가 가깝다! 도망!" << endl;
        cout << "거리가 벌어졌으니 활들고 공격!" << _MonsterInfo.iAtt << "피해를 입혔다" << endl;
        cout << endl;
    }
 
public:
    CChickenMonsterAi(void) {}
    virtual ~CChickenMonsterAi(void) {}
};
 
class CBastardMonsterAi
    : public CMonsterAiBridge
{
public:
    virtual void AiAction(const MONSTERINFO& _MonsterInfo) const
    {
        cout << "************* 비열한 몬스터AI *************" << endl;
        cout << "항복! 뿌잉뿌잉" << endl;
        cout << "플레이어가 전투태세를 풀었다!" << endl;
        cout << "방심하고 있으니 공격!" << _MonsterInfo.iAtt << "피해를 입혔다" << endl;
        cout << "플레이어가 공격하려고 한다!!" << endl;
        cout << "항복! 뿌잉뿌잉" << endl;
        cout << "플레이어가 전투태세를 풀었다!" << endl;
        cout << "방심하고 있으니 공격!" << _MonsterInfo.iAtt << "피해를 입혔다" << endl;
        cout << endl;
    }
 
public:
    CBastardMonsterAi(void) {}
    virtual ~CBastardMonsterAi(void) {}
};
 
class CMonster
{
private:
    MONSTERINFO          m_MonsterInfo;
    CMonsterAiBridge* m_MonsterAiBridge;
 
public:
    void InitMonster(void)
    { 
        switch(m_MonsterInfo.iAiPatton)
        {
        case MONSTER_AI_WILD:
            m_MonsterAiBridge = new CWildMonsterAi();
            break;
        case MONSTER_AI_CHICKEN:
            m_MonsterAiBridge = new CChickenMonsterAi();
            break;
        case MONSTER_AI_BASTARD:
            m_MonsterAiBridge = new CBastardMonsterAi();
            break;
        }
    }
 
    void AiAction(void)
    {
        m_MonsterAiBridge->AiAction(m_MonsterInfo);
    }
 
public:
    CMonster(void) {}
    CMonster(const MONSTERINFO& _MonsterInfo) 
    {
        m_MonsterInfo = _MonsterInfo;
    }
    ~CMonster(void
    {
        SAFE_DELETE(m_MonsterAiBridge);
    }
};
 
// Bridge.cpp : 콘솔 응용 프로그램에 대한 진입점을 정의합니다.
//
 
#include "stdafx.h"
#include "Bridge.h"
 
int _tmain(int argc, _TCHAR* argv[])
{
#ifdef _DEBUG
    _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
#endif
 
    CMonster* MonsterWild = new CMonster(MONSTERINFO(MONSTER_AI_WILD, 15));
    CMonster* MonsterChicken = new CMonster(MONSTERINFO(MONSTER_AI_CHICKEN, 15));
    CMonster* MonsterBastard = new CMonster(MONSTERINFO(MONSTER_AI_BASTARD, 15));
 
    MonsterWild->InitMonster();
    MonsterChicken->InitMonster();
    MonsterBastard->InitMonster();
 
    MonsterWild->AiAction();
    MonsterChicken->AiAction();
    MonsterBastard->AiAction();
 
    SAFE_DELETE(MonsterWild);
    SAFE_DELETE(MonsterChicken);
    SAFE_DELETE(MonsterBastard);
 
    return 0;
}






Posted by JJOREG

uml의 관계 표시화살표 --------------------------------------------------------------------------------------------------

앞서 UML의 표시 관계도를 단순히 화살표로 하다 이번에 아답터패턴에서 각 생성이나 포함하고 있는 객체들에 대한 관계에 대해서 너무 명확하지 못하게 작성하고 있었던 것 같다.
이제부터는 좀더신경을 써보려고 인터넷을 뒤져서 짱오리님의 블로그에서 UML의 기본에 대해서 알아봤다.

클래스----------------------------------------------------------------------------------------------------------------------------

맨위부터 

1. 클래스명

2. 맴버변수

3. 맴버함수를 의미한다.


앞에 붙은 - +들의 의미는

+ public

- private

# protected


코드로 봐보자.


객체----------------------------------------------------------------------------------------------------------------------------


관계표현(Relationships)-------------------------------------------------------------------------------------------------------------------------


서로 의미있는 클래스들의 관계에는 크게 4가지 종류가 있다.


일반적인 의미의 연결 관계인 연관( association ) 관계.

전체와 부분을 나타내는  집합( aggregation )  관계.

다른 클래스의 재산을 물려받는 상속( inheritance ) 관계.

한 클래스가 다른 클래스에 영향을 미치는 의존( dependency ) 관계.


관계를 표현해 보면 다음과 같다.


의존( dependency )--------------------------------------------------------------------------------------------------------

association 과 dependency 를 구분짓는 가장 큰 기준은 ' 참조하는 클래스 인스턴스의 레퍼런스를 계속 유지하고 있느냐, 아니냐 '


표시방법


설명

A클래스는 C 클래스를 내부에 맴버를 가지고 있거나 참조하고 있다.

하지만 함수 내부의 지역적으로 사용하고 있으며 A클래스가 사라진다고 해서 C클래스가 사라지지는 않는다.


주로 다음과 같은 세 가지 경우에 의존 관계로 표현한다.


1. 한 클래스의 메소드가 다른 클래스의 객체를 인자로 받아 그 메소드를 사용한다.

  ( 가장 일반적 ) 

2. 한 클래스의 메소드가 또 다른 클래스의 객체를 반환한다.

3. 다른 클래스의 메소드가 또 다른 클래스의 객체를 반환한다. 이때 이 메소드를 호출하여

   반환되는 객체의 메소드를 사용한다.


연관( association )--------------------------------------------------------------------------------------------------------


표시방법

   


설명

한 객체가 다른 객체와 연결되어 있음을 나타낼 때 그들을 연관관계로 지칭한다. 이러한 

연관관계에서 중요하게 볼 점은 ' 연관 관계의 방향( navigability ) 과 멀티플리시티

( multiplicity ) 이다.



양방향 연관 관계 : 연결된 클래스들이 서로의 존재를 알고 있다는 의미이다.

위의 UML 을 해석하자면 House 와 Person 클래스는 서로의 존재를 알고 있으며, 반드시 

한 사람 이상이 House에 속해야 한다는 것을 뜻한다. 



단방향 연관 관계 : House 클래스는 Person 클래스의 존재를 알고 있지만, Person 은 

House 클래스의 존재를 모르고 있다고 이해하면 된다. 이와 같은 경우는 House 클래스만 

Person 클래스에 대한 참조값을 가지고 있고, Person 은 House 에 대한 어떠한 참조값도 

가지고 있지 않는다.  



관계 표현을 나타낸 그림에서 보면 일반 연관과 특수 연관이라고 나뉘어 지는데, 

특수 연관이라는 것은 임의로 만든 단어이다.  일반 연관이란 앞에서 살펴본 association 을 

나타내며,  association 중에서도 ' 부분과 전체 ' 로 나눌 수 있는 관계를 aggregation 과 

composition 으로 묶어서 분류한 것이다.


aggregation 과 composition 은 모두 association 의 한 특별한 형태로 각각을 구분하는 

기준은  ' life cycle 이 같느냐, 같지 않느냐 ' 이다. life cycle 이란 클래스 인스턴스의 

생명 주기를 말하는 것으로 생성에서 소멸까지의 과정을 말한다. 즉, life cycle 이 같다는 것은

관계된 클래스 혹은 그 인스턴스의 생성과 소멸이 동시에 이루어진다는 것을 뜻한다.


쉽게 예를 들어 표현하자면 모자와 안경을 쓴 사람을 놓고 보자. 현재 이 사람을 구성하고 있는 

요소에는 눈, 팔, 다리와 같이 사람이 죽으면 같이 없어지는 요소들이 있고, 안경 모자와 같이 

바꿔 사용할 수 있는 요소들이 있다. 즉, 눈, 팔, 다리는 사람과 life cycle 이 같은

composition 관계 이고, 안경이나 모자는 aggregation 관계인 것이다.


즉 A클래스는 B클래스를 가지고 내부적으로 B클래스의 레퍼런스를 유지하지만 딱히 라이프사이클이 같거나 내부적으로 생성하고 있지는 않다.


상속( inheritance )--------------------------------------------------------------------------------------------------------


표기


설명

B클래스는 A클래스를 상속한다.


집합( aggregation )--------------------------------------------------------------------------------------------------------

설명

내부적으로 A클레스는 B클래스를 내부적으로 참조 하고 있지만

전체객체의 생성시기가 같을 필요도 없고 한쪽이 소멸된다고해서 나머지가 소멸되는 것은 아니다.

또한 컴퓨터의 모니터나 키보드는 언제든 다른 컴퓨터에 떼어주거나 혹은 파기될 수 있다.


구성( composition )--------------------------------------------------------------------------------------------------------


설명

A클래스는 B클래스를 가지고 있고 두 클래스의 라이프 사이클은 완전히 같다. A클래스가 사라지면 B클래스도 사라진다.


인터페이스( Interface )--------------------------------------------------------------------------------------------------------


설명
인터페이스는 자바와 C#에서 사용하는 개념으로 추후 설명한다.


Posted by JJOREG

아답터패턴 --------------------------------------------------------------------------------------------------

선생님은 말하셨다. 악마같은 패턴이하나 있는데 그중 하나가 아답터 패턴이라고.

아답터 패턴은 전혀 연관이 없는 녀석을 하나의 클래스로 만들어 새로운 클래스를 창조하거나.

두개의 연관없는 기능을 하나로 묶을 수 있다는 것이다.

즉 남의 코드를 마구 가져다 쓸때 매우 도움이 될 수도 있는 패턴이다.


아답터 클래스를 만드는 방식은 2가지가 있다.

일단 클래스 Target와 클래스 Adaptee라는 전혀 다른 기능을 가진 클래스가 있다고 치자.

(물론 하나의 애플리케이션 안에 있을 것 이므로 기본적인 목표는 동일할 수 있다.)

그런데 엮일줄 몰랐던 이 클래스들의 기능이 혼합된 새로운 기능을 만들라고 한다.

새로운 클래스를 만드는 것은 의외로 간단하다. 그냥 하나의 클래스를 만들고 두 클래스의 코드를 복사해서 붙여넣고 조정좀 해주면 되니까.

하지만 이런짓은 코드의 중복을 가져온다. 코드를 중복하지 않고 기능을 추가하거나 새로운 클래스를 만들어 낼 수는 없을까?

그걸 위한 아답터패턴이고 2가지 정도의 방법이 있다.


방법1. Adapter클래스 두개의 클래스를 상속받는다. 다중 상속이다. 하지만 상속을 통해서 adapter클래스는 두개의 클래스의 기능을 모두 사용할 수 있게 되었다. 

그러면서 다형성을 사용하여 어느 패턴의 형식으로도 관리될 수 있다.


방법2. Adapter클래스가 하나의 클래스를 상속받고 다른 클래스는 포함하게 된다. 내부에 포함된 adaptee의 함수를 사용할수 있지만 

오버라이드를 통해서 상위 클래스의 함수에 새로운 기능을 추가하는 일은 힘들다.


패턴적용 스토리 -----------------------------------------------------------------------------------------------

당신은 스킬을 만들라는 요청을 받았다.

그런데 여기서 웃기는 문제가 생겼다. 기존에는 공격스킬과 자신을 회복하는스킬을 나누어 놓자고 하고는 이제는 두개가 섞여있는 체력흡수라는 스킬을 만들어 내라고 한다.

즉 CATTSkill CSelfHEALSkill클래스의 스킬이 혼합된 스킬을 만들라고 하고 있다.

CMIXSkill클래스를 새롭게 만들기로 결정했다. 하지만.

코드의 중복은 피하면서 양쪽 모두의 효과를 동시에 사용할 수 있는 방법으로 아답터 패턴을 사용해보자.


uml -----------------------------------------------------------------------------------------------



양쪽을 모두 상속받는 CMixSkillOne과 한쪽은 상속받고 한쪽을 포함하는 CMixSkillTwo는 같은 동작을 하지만 포함과 상속이라는 약간의 차이가 있습니다.

하지만 양쪽의 인터페이스를 상속받으면서도 다른 모습을 보여줍니다.

한쪽은 인터페이스를 가상함수로 오버로딩 하고 있고.

한쪽은 새롭게 재정의 해서 사용하고 있습니다.

즉 응용하기에 따라 다양한 방법으로 혼합 합성이 가능함을 보여줍니다.



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
#pragma once
 
#include "stdafx.h"
#include "include.h"
 
typedef struct tagSkill
{
    int    iTarget;
    int    iOption;
 
    void Setting(const tagSkill& _Skill)
    {  
        *this = _Skill; 
    }
 
    void Setting(const int& _Target, const int& _Option)
    { 
        iTarget = _Target;
        iOption = _Option;
    }
 
    void tagMonster(void)
    { 
        iTarget = 0;
        iOption = 0; 
    };
 
}SKILLINFO, *PSKILLINFO;
 
class CAttSkill
{
protected:
    SKILLINFO m_SkillInfo;
 
public:
    virtual void Setting(int _Target, int _Option)
    { m_SkillInfo.Setting(_Target, _Option); }
    virtual void Setting(tagSkill& _Skill)
    { m_SkillInfo.Setting(_Skill); }
 
public:
    virtual void Effect(void)
    {
        cout << m_SkillInfo.iTarget << "번 몬스터에게 공격" << endl;
        cout << m_SkillInfo.iOption << "데미지를 입혔다." << endl;
    }
 
public:
    CAttSkill(void) {}
    ~CAttSkill(void) {}
};
 
class CSelfHealSkill
{
protected:
    int iOption;
 
public:
    virtual void Setting(const int& _Option)
    { iOption = _Option; }
 
public:
    void Effect(void)
    {
        cout << "자신에게 회복효과 적용" << endl;
        cout << iOption << "의 체력을 회복했다." << endl;
    }
 
public:
    CSelfHealSkill(void) {}
    ~CSelfHealSkill(void) {}
};
 
class CMixSkillOne
    : public CAttSkill, public CSelfHealSkill
{
public:
    virtual void Setting(const int& _iTarget, const int& _Option1, const int& _Option2)
    {
        m_SkillInfo.iTarget = _iTarget;
        m_SkillInfo.iOption = _Option1;
        iOption = _Option2;
        //Setting(iTarget, _Option1);
        //SelfSetting(_Option2);
    }
 
public:
    void Effect(void)
    {
        CAttSkill::Effect();
        CSelfHealSkill::Effect();
    }
 
 
public:
    CMixSkillOne(void) {}
    ~CMixSkillOne(void) {}
};
 
class CMixSkillTwo
    : public CAttSkill
{
private:
    CSelfHealSkill m_SelfHeal;
 
public:
    virtual void MixSetting(const int& _iTarget, const int& _Option1, const int& _Option2)
    {
        m_SkillInfo.iTarget = _iTarget;
        m_SkillInfo.iOption = _Option1;
        m_SelfHeal.Setting(_Option2);
    }
 
public:
    void MixEffect(void)
    {
        Effect();
        m_SelfHeal.Effect();
    }
 
public:
    CMixSkillTwo(void) {}
    ~CMixSkillTwo(void) {}
};
 
// Adapter.cpp : 콘솔 응용 프로그램에 대한 진입점을 정의합니다.
//
 
#include "stdafx.h"
#include "Adapter.h"
 
 
int _tmain(int argc, _TCHAR* argv[])
{
    int Target = 0;
    int Option = 10;
 
    cout << "************ AttSkill ************" << endl;
 
    CAttSkill AttSkill;
    AttSkill.Setting(Target, Option);
    AttSkill.Effect();
 
    cout << "************ SelfHealSkill ************" << endl;
 
    CSelfHealSkill SelfHeal;
    SelfHeal.Setting(Option);
    SelfHeal.Effect();
 
    cout << "************ MixSkillOne ************" << endl;
 
    CMixSkillOne MixSkillOne;
 
    MixSkillOne.Setting(Target, Option, Option);
    MixSkillOne.Effect();
 
    cout << "************ MixSkillTwo ************" << endl;
 
    CMixSkillTwo MixSkillTwo;
 
    MixSkillTwo.MixSetting(Target, Option, Option);
    MixSkillTwo.MixEffect();
 
    return 0;
}


결과 -----------------------------------------------------------------------------------------------




'게임개발공부 > 디자인패턴' 카테고리의 다른 글

구조패턴 <브릿지 패턴> (가교)  (0) 2013.12.23
uml 관계도 좀더 명확히  (1) 2013.12.22
메인프레임 작업 01  (0) 2013.12.21
생성패턴 <싱글톤 패턴>  (0) 2013.12.18
생성패턴 <프로토 타입>  (0) 2013.12.17
Posted by JJOREG
BOOL WINAPI ShowWindow(
  _In_  HWND hWnd,
  _In_  int nCmdShow
);

HWND [에]

유형 : HWND

창에 대한 핸들.

nCmdShow [에]

유형 : INT

윈도우가 표시되는 방법을 제어합니다. 이 매개 변수는 응용 프로그램이 호출 처음 무시 이 ShowWindow를 응용 프로그램을 시작 프로그램이 제공하는 경우, STARTUPINFO의 구조. 그렇지 않으면, 처음 이 ShowWindow이 호출은, 값에 의해 얻어진 값이어야 WinMain에의 그 함수 의 nCmdShow 파라미터. 이후의 호출에서이 매개 변수는 다음 값 중 하나 일 수 있습니다.

의미
SW_FORCEMINIMIZE
11

윈도우를 소유하는 스레드가 응답하지 않는 경우에도, 창을 최소화한다. 다른 스레드에서 창을 최소화 할 때이 플래그는 사용되어야한다.

SW_HIDE
0

창을 숨기고 다른 창을 활성화합니다.

SW_MAXIMIZE
3

지정된 창을 최대화합니다.

SW_MINIMIZE
6

지정한 창을 최소화하고 Z 순서의 다음 최상위 창을 활성화합니다.

SW_RESTORE
9

활성화하고 창을 표시합니다. 윈도우가 최소화 또는 최대화되는 경우, 시스템은 원래 크기 및 위치로 복원한다. 최소화 된 창을 복원 할 때 응용 프로그램이 플래그를 지정해야합니다.

SW_SHOW
5

창을 활성화하고 현재 크기와 위치에 표시합니다.

SW_SHOWDEFAULT
10

에 지정된 SW_ 값을 기준으로 표시 상태를 설정STARTUPINFO의 전달 구조 의 CreateProcess 시작 프로그램에 의해 작동합니다.

SW_SHOWMAXIMIZED
3

창을 활성화하고 최대화 된 창으로 표시됩니다.

SW_SHOWMINIMIZED
2

창을 활성화하고 최소화 된 상태로 표시합니다.

SW_SHOWMINNOACTIVE
7

최소화 된 창으로 창을 표시합니다. 이 값은 비슷합니다SW_SHOWMINIMIZED 창을 제외하고는 활성화되지 않습니다.

SW_SHOWNA
8

현재 크기와 위치에 창을 표시합니다. 이 값은 유사하다SW_SHOW 창이 활성화되지 않는다는 것을 제외.

SW_SHOWNOACTIVATE
4

가장 최근의 크기와 위치에 창을 표시합니다. 이 값은 유사하다SW_SHOWNORMAL 창이 활성화되지 않는다는 것을 제외.

SW_SHOWNORMAL
1

활성화하고 창을 표시합니다. 윈도우가 최소화 또는 최대화되는 경우, 시스템은 원래 크기 및 위치로 복원한다. 처음으로 창을 표시 할 때 응용 프로그램이 플래그를 지정해야합니다.


Posted by JJOREG
본 글은, Best Practices for Creating DLLs를 번역하였으며, 원문인 DLL_bestprac.doc를 통해 직접 내려받으실 수 있습니다. 본 글에 워낙 이론적인 내용이 많다 보니 이해가 어려울 수 있는데, 실제 코드와 디버깅을 통한 설명을 다음 기회에 공유하도록 하겠습니다.


dynamic-link library(DLL)은 응용프로그램이 실행중에 로드하고 호출할 수 있는 공유된 코드와 데이터로 정의할 수 있습니다. 전형적인 DLL은 응용프로그램을 위해 루틴들을 노출(=Export)시키며, 그 내부(즉, DLL)에서 사용할(internal use) 루틴도 역시 포함되어 있습니다. 이러한 기술은 여러 응용프로그램에서 공통 기능으로 공유할 수 있게 라이브러리 형태로 재사용 가능하게 하여 필요시 로드를 할 수 있도록 해줍니다. DLL 사용의 장점은 코드가 차지하는 공간(code footprint)을 줄이고, 단일 복사본을 공유함으로서 메모리 사용량을 낮추며, 개발과 테스트가 용이하게 하고 모듈화를 가능하게 합니다.

DLL을 만드는 일은 개발자에게 많은 도전을 가져다 줍니다. DLL은 시스템을 통한 강제적인 버전관리가 이뤄지지 않습니다. 즉, 여러개의 DLL이 한 시스템에 있을때, 이러한 버전관리 체크의 부족으로 인한 overwrite는 의존성과 API 충돌을 야기합니다. 개발 환경, 로더 구현 그리고 의존성의 복잡성은 로드 순서와 응용프로그램 행위에 취약성을 만듭니다. 그래도 많은 응용프로그램들은 복잡한 의존성을 가지는 DLL에 의지하고 있습니다. 이 문서는 DLL 개발자들을 위해 가이드라인을 제공하여 견고하고 이식성 있으며 확장성 있는 DLL로 만드는데 도움을 줄 것입니다.

■ 3개의 주요한 DLL 컴포넌트 개발 모델은 다음과 같습니다.

  1. Library Loader
    DLL은 가끔 복잡한 내부의존성(interdependency)을 자니는데, 이는 그들이 로드되어야 하는 순서를 정의합니다. Library Loader는 효과적으로 이러한 의존성을 분석하고, 정확한 로드 순서를 계산한뒤 그 순서대로 로드를 합니다.
  2. DLLMain entry-point function
    이 함수는 Loader에 의해 호출되며, 그 시점은 DLL의 Load 혹은 Unload일때 입니다. Loader는 한 시점에 단 하나의 DLLMain만 호출하도록 연속으로 호출합니다. 더 많은 정보
  3. Loader Lock
    Loader가 순서대로 로드할때 사용되는 프로세스 단위의 동기화 객체입니다. 프로세스 단위의 Library Loader Data를 반드시 읽거나 써야 하는 함수는 반드시 이 Lock을 획득해야 합니다. 물론 이러한 operation을 수행하기 전에 이뤄져야 합니다. Loader Lock은 recursive이며, 이는 같은 쓰레드에서 다시 Lock의 획득이 가능함을 의미합니다.

그림 1

그림 1. DLL 로드시 어떤일이 이뤄지는가?

DLLMain에서의 부적합한 동기화 시도는 응용프로그램에게 deadlock 혹은 초기화 되지 않은 DLL의 data와 code의 접근을 야기하게 됩니다. DLLMain에서의 특정 함수 호출은 이러한 문제를 잃으킵니다.

일반적인 최고의 습관


DLLMain은 Loader Lock이 획득되었을때 호출됩니다. 따라서, DLLMain 내부에서의 호출은 중요한 제약이 강요됩니다. DLLMain은 최소의 초기화 작업을 수행하도록 디자인 되었는데, 이는 Windows API의 몇몇 함수군 호출에 의해서 입니다. DLLMain에서 직접적이든 간접적이든 Loader Lock 획득을 시도하는 어떤 함수도 호출할 수 없습니다. 다시 말해, 이 경우가 발생하면 당신은 deadlock 혹은 crash를 경험하게 됩니다. DLLMain 구현에서의 에러는 해당 프로세스와 그 내부의 쓰레드를 위험에 빠트리게 됩니다.

이상적인 DLLMain은 "그냥 비우는것" 입니다. 그러나, 많은 응용 프로그램의 복잡도를 고려할때 이는 너무한 제약이 됩니다. DLLMain을 다루는 좋은 방법은 많은 초기화 과정을 가능한 뒤로 미뤄라라는 것입니다. 이러한 미뤄진 초기화는 응용프로그램을 더욱더 견고히 해주는데, 그 이유는 Loader Lock가 획득된 동안의 초기화가 이뤄지지 않았기 때문입니다. 역시 이러한 방법은 Windows API의 사용에도 훨씬 많은 안정성을 제공합니다.

몇몇 초기화 작업은 뒤로 미룰순 없을 것입니다. 예를 들어, 설정 파일에 의존성이 있는 DLL이 있는데, 해당 파일이 좋지 않거나 쓰레기 내용이 포함되었을때 그 DLL의 Load가 실패되야 하는 경우가 있을 것입니다. 이런 종류의 초기화 방식은, 다른 작업의 자원 낭비를 하느니 DLL이 그 행위를 시도해보고 빨리 실패하는 것이 좋다고 개념이라 보여집니다.

■ 다음과 같은 작업을 절대로 DLLMain에서 수행해서는 안됩니다.

  • LoadLibrary 혹은 LibraryEx의 직접적 혹은 간접적 호출. 이는 deadlock 혹은 crash를 유발한다.
  • 다른 쓰레드와의 동기화 시도. 이는 deadlock을 유발한다.
  • Loader Lock을 획득하기 위해 기다리는 코드가 획득한 다른 사설 동기화 객제를 획득하려고 하는 시도. 이는 deadlock을 유발한다.
  • CoInitializeEx 사용에 의한 COM 쓰레드 초기화. 특정 상황이 되면 이 함수는 LoadLibrary를 호출한다.
  • 레지스트리 함수군의 호출. 이 함수는 Advapi32.dll에 구현되어 있는데, 만약 AdvApi32.dll이 아직 당신 DLL에서 초기화되지 않았다면, 그 DLL은 메모리를 초기화해제 하며, crash를 유발한다.
  • CreateProces 호출. 이는 다른 DLL을 Load할 수 있다.
  • ExitThread 호출. DLL Detach 과정에서 Exit가 진행중인 Thread는 Loader Lock을 다시 획득하려고 하는 시도가 발생하여 deadlock 혹은 crash가 발생할 수 있다.
  • CreateThread 호출. 다른 Thread와 동기화 작업을 하지 않는다면, 생성중인 Thread가 할 수 있는데, 이는 위험할 수 있다.
  • Named Pipe 혹은 다른 Named Object의 생성(Windows 2000만 해당). Windows 200에서는 Named Object는 Terminal Service DLL에 의해 제공되는데, 만일, 이 DLL이 초기화되지 않았다면, DLL을 로드하게 되어 crash가 유발될 수 있다.
  • 메모리 관리 CRT 함수 호출. 만약 CRT DLL이 초기화 되지 않았다면, crash가 유발된다.
  • User32.dll 혹은 Gdi32.dll 함수 호출. 몇몇 함수들은 아직 초기화되지 않은 DLL을 로드한다.
  • 관리 코드의 사용

■ DLLMain 내에서 안전한 작업은 다음과 같습니다.

  • compile time의 static data의 초기화
  • 동기화 객체의 생성과 초기화
  • 메모리 할당과 dynamic data의 초기화 (위 금지 함수 이외)
  • Thread local storage(TLS) 초기화
  • File의 열기/읽기/쓰기
  • kernel32.dll 함수의 호출 (위 금지 함수 제외)
  • 전역 포인터 변수를 NULL로 할당

Lock 순서의 역(Lock order inversion)에 의한 deadlock



Lock과 같은 다중 동기화 객체 사용을 구현할 때, Lock 순서를 따르는 것은 굉장히 중요합니다. 어느 시점에서 한개 이상의 Lock을 획득하는 것이 필요할때 반드시 Lock hierachy 혹은 Lock 순서라 불리는 명시적인 순서를 정의해야 합니다. 예를 들어, Lock A가 Lock B이전에 획득되었고, Lock C 이전에 Lock B가 획득되었다면 Lock 순서는 A,B,C가 되고, 이 순서는 코드에서 지켜줘야 됩니다. 만약 Lock 순서가 역으로 되는경우가 발생했다면, 예를 들어, Lock A를 획득 하기 전에 Lock B가 획득되었을 경우, 이는 Lock 순서의 역에 의한 deadlock이 발생하게 됩니다. 이렇게 발생한 deadlock은 디버깅하기 힘든 면이 있습니다. 이것을 방지하기 위해 모든 쓰레드에서는 같은 순서대로 Lock을 획득해야만 합니다.

Loader는 이미 획득한 Loader Lock으로 DLLMain을 호출한다는 사실은 굉장히 중요합니다. 그래서 Loader Lock은 Locking hierachy의 가장 높은 우선순위가 되어야 합니다. 그와 마찬가지로 적합한 동기화를 위해 요구된 Lock을 획득하해야 하는 것도 알아야 합니다. 물론 hierachy에 정의된 모든 단일 Lock을 획득해야 할 필요는 없습니다. 예를 들어, A와 C를 적합한 동기화를 위해 획득하였다면, C를 획득하기 이전에 A를 획득해야 하며, B를 획득할 필요는 없습니다. 더 나아가 설명하자면, 프로그램 코드에서는 Loader Lock을 명시적으로 획득 할 수 없습니다. 만약 사적인 Lock을 획득한 상황에서 Loader Lock을 간접적으로 획득하려는 ::GetModuleFileName(...)과 같은 API를 호출해야 한다면, 사설 Lock을 획득하기 이전에 ::GetModuleFileName(...)을 획득해야만 합니다. 이는 Load 순서를 따르게 하기 위함입니다.

그림 2

그림 2. Lock 순서의 역에 의한 deadlock



그림 2. 는 이러한 Lock 순서의 역을 보여주고 있습니다. DLLMain을 포함하는 Main 쓰레드를 가지는 DLL을 생각해 보십시요. Library Loader는 Lock L을 획득했으며, DLLMain을 호출하려고 합니다. Main 쓰레드에서는 동기화 객체인 A, B 그리고 공유 데이터를 접근하기 위해 필요한 G를 생성하고 G를 획득하기 위해 시도하려고 합니다. 그와 별도로, Worker 쓰레드에서는 이미 G를 획득한 상황이며 ::GetModuleHandle(...)를 호출하여 Loader Lock인 L을 획득하려고 시도할 것입니다. 그러면, Worker 쓰레드는 L에 의해 Block 되며, Main 쓰레드는 G에 의해 Block 되며, 이로 인해 deadlock이 발생하게 됩니다.

이러한 상황을 방지하기 위해서는, 모든 쓰레드에서는 항상 순서에 맞게 동기화 객체를 획득하도록 시도해야 합니다.

동기화를 위한 최고의 습관



초기화의 한 부분으로 DLL이 Worker 쓰레드를 생성해야 하는 경우를 생각해 보십시요. DLL이 Cleanup되면 data의 무결성(consistent)을 확신하기 위해 모든 Worker 쓰레드의 동기화가 필요하며, 그 다음 Worker 쓰레드는 종료하게 됩니다. 오늘날, 멀티쓰레드 환경의 DLL을 종료하고 동기화하는데에는 완벽하고 정확한 방법은 없습니다. 다음은 DLL 종료를 하는 동안 이루어질 쓰레드 동기화를 위해 현재까지 나와있는 최고의 습관을 설명하고 있습니다.

■ 프로세스 종료시 DLLMain에서의 쓰레드 동기화
  • 프로세스 종료시 DLLMain이 호출되었다면 모든 프로세스의 쓰레드들은 Clean up이 이뤄지며 주소 공간(Address Space)는 더이상 유지되지 않습니다. 동기화는 이런 경우에는 필요하지 않습니다. 다시 말해 DLL_PROCESS_DETACH는 비워둬도 됩니다.
  • Windows Vista에서는 핵심 data들(환경 변수, 현재 디렉토리, 프로세스 힙, ...)의 유지가 보장됩니다. 그러나 다른 동적 할당된 사설 data는 망가져서 더이상 안전하지 않습니다.
  • 저장이 필요한 영구 유지될 상태들은 저장 매체에 플러쉬되어야 합니다.

■ DLL UnLoad시의 DLL_THREAD_DETACH를 위한 DLLMain의 쓰레드 동기화

  • DLL이 UnLoad될때 주소 공간(Address Space)는 사라지는 것은 아닙니다. 따라서 DLL은 Clean될 예정인 상태입니다. 이것은 쓰레드 동기화, Open된 핸들, 영구 유지해야 하는 상태 그리고 할당된 자원들을 포함합니다.
  • 쓰레드 동기화는 종잡을 수 없는데, DLLMain에서 쓰레드의 종료를 기다리는 것은 deadlock을 유발할 수 있기 때문입니다. 예를 들어, DLL A가 Loader Lock을 획득했습니다. 그리고 Thread T를 종료시키기 위해 Signal을 보냈고 종료를 기다리도록 합니다. 쓰레드 T는 종료가 되며, Loader는 DLL A의 DLL_THREAD_DETACH 호출을 위해 Loader Lock 획득을 시도할 것입니다. 이것이 deadlock을 유발시키게 됩니다. 이러한 리스크를 최소화 하는 방법은 다음과 같습니다.
    • DLL A는 DLLMain에서 DLL_THREAD_DETACH 메시지를 받고, 쓰레드 T에게 종료해라는 Signal을 보냅니다.
    • Thread T는 현재의 작업을 마치고 스스로 상태를 유지하며 DLL A에게 Signal을 보냅니다. 단, 이러한 유지된 상태 체크를 위해서 DLLMain의 deadlock 회피를 위한 제약을 지켜야 합니다.
    • DLL A가 쓰레드 T를 종료 시켰으며, 그것이 아직 유지된 상태임을 알수 있습니다.

만약 DLL이 그것의 모든 쓰레드를 생성하고 나서 UnLoad되었고 실행이 시작되기 전이었다면, 그 쓰레드들은 crash가 발생할 수 있습니다. 만약 DLL이 초기화의 단계로 DLLMain에서 Thread를 생성하였다면, 몇몇 쓰레드들이 아직 초기화가 완료되지 못했고 그들의 DLL_THREAD_ATTACH 메시지가 여전히 DLL에게 전달되기를 기다리고 있을 것입니다. 이런 상황에서 DLL이 UnLoad된다면 쓰레드들의 Terminate가 시작될 것입니다. 그러나 몇몇 쓰레드들은 Loader Lock에 의해 block되어 있을 겁니다. 그들의 DLL_THREAD_ATTACH 메시지들은 DLL이 unmap된 이후 진행될 것이며, 이는 crash를 유발할 것입니다.



///////////// 두번째


오랜만에 블로그에 글을 쓰고 있습니다. 일종의 생존신고랄까요? 
VS2005 로 처음 작업할때도 이런 비슷한 문제로 필요없는 삽질을 꽤 했었습니다.  여기 ㅜ.ㅜ
요즘 VS2008 로 해야할 작업이 있어서 쪼물딱 거리는 와중에 이상한 현상이 있어서 삽질하고, 구글링한 결과를 정리해 볼까 합니다.
어떤 X 같은 일이 생겼나면.. 

개발머신   : VS2008, SP1 
테스트머신: 윈도우 XP SP3 

이렇게 PC 가 두대가 있습니다. 
당연히 VS2005 에서 작업하던 대로 Debug 빌드시에는 
Microsoft Visual Studio 9.0\VC\redist\Debug_NonRedist\x86\Microsoft.VC90.DebugCRT
아래 있는 파일들을 실행파일과 함께 테스트 머신에 복사하고, 실행을 했습니다. (private assembly 형태의 배포죠)
그런데 실행이 안되는겁니다.

뭐 요딴 메세지만 나오는 군요. 
아래 그림에서처럼 DLL 이 뭔가 없는것도 아니고요. 아무 문제 없습니다. (VLdr.exe 는 VLdr.dll 을 사용합니다)


Manifest 파일과 배포해야 하는 DLL 들도 잘 복사되어있고요.


대체 뭐가 문제란 말입니까.. ㅠ.ㅠ

한참을 원인을 찾던 중.. (뭐 딱히 구글링할 키워드도 떠오르지 않더군요. -_-)
뭐 급한대로 VLdr.exe.config 파일을 만들어서 dll 들을 redirect 시켜서 대충 해결이 되긴 하더군요. 
하지만 dll 이 앞으로 한두개도 아니고.. 3rd party library 라도 사용하게 되면 대략 난감해 지겠죠. 
config 파일로 해결이 된다는것은 결국 바인딩 되는 dll 의 버전 문제 아니겠는가 라는 결론이 나오죠.

VS 링커 옵션에 보면 minifest 를 자동생성하도록 되어있습니다.(디폴트로)
역시 평소에 VS 각 옵션 설정을 꼼꼼히 보아두었던 것이 도움이 되는군요 ^^.
바인딩되는 dll 들은 링크타임에 manifest 형태로 만들어지는 것이고요. 생성된 manifest 는 exe 나 dll 에 내장되는것이겠죠. (아님 말구요)

결국 debug 디렉토리(중간 오브젝트 생성경로)에 생성된 ****.exe.embed.manifest 를 살펴보니 (****.exe.intermediate.manifest 는 뭐 중간에 잠깐 만들어지는 넘 같으니 패스) 아래와 같은 내용이 있네요.


그런데 C:\Program Files\Microsoft Visual Studio 9.0\VC\redist\Debug_NonRedist\x86\Microsoft.VC90.DebugCRT 에 있는 minifest 파일을 열어보면 (private assembly 로 배포하던 파일이죠)



이런 ...ㅅㅂ... 욕나옵니다. -_-;; 
결국 SP 1 이 설치되었는데도 VS 는 예전 버전(시스템 런타임 버전)으로 바인딩을 하고 있는겁니다. 
이러니 당연히 실행이 안될 수 밖에요 (이해가 안되시는 분들은 WinSxS 나 Strong named assembly 라는 키워드로 검색을.. ).

결국 이런 저런 삽질과 구글링을 통해 알아낸 결과
 - VS 2008 SP1 설치 미디어에 있는 VC_x86Runtime.exe 을 디버기에서 실행하거나
 - 아래 전처리기를 stdafx.h 같은 곳에 (가능하면 가장 위에) 정의해 주는 겁니다.
#define _BIND_TO_CURRENT_MFC_VERSION 1
#define _BIND_TO_CURRENT_CRT_VERSION 1
프로젝트 속성 -> C/C++ -> 명령줄에 추가하는 것이 더 좋은것 같습니다. 
/D "_BIND_TO_CURRENT_MFC_VERSION=1"
/D "_BIND_TO_CURRENT_CRT_VERSION=1"
MFC 프로젝트에서 stdafx.h 에 추가했더니 여전히 잘못된 manifest 를 만들어내는군요. -_-;;

이 전처리기를 이용하면 정상적으로 ***.exe.embed.manifest  에 버전이 SP 1 용 MFC, CRT 의 버전으로 만들어집니다.
이게 VS 2005 와 VS2008 에서 이렇게 요상하게 다르게 동작하는 원인은 고객들의 요구에 의한거라고 하는군요. -_-




'게임개발공부 > 무작정퍼오기' 카테고리의 다른 글

explicit 키워드에 대하여  (0) 2014.01.09
함수객체  (0) 2013.12.29
volatile (퍼온글)  (0) 2013.12.21
extern "C" 이건 뭔가? (퍼온글)  (0) 2013.12.21
dll의 기본. (퍼온글)  (0) 2013.12.21
Posted by JJOREG

volatile 키워드는 간단히 '코드 최적화를 막아주는 키워드'라고 설명할 수 있습니다. 즉 컴파일러의 최적화와 관련된 키워드이고 CPU 내/외부 캐시와 같은 하드웨어 최적화와도 관계가 있습니다. 지금처럼 임베디드 기반 프로그래밍이 보편화 되지 않았던 시절에는 volatile 키워드의 활용 빈도가 매우 낮았으나, 최근 임베디드 시스템이나 멀티 스레드를 고려한 프로그램이 늘어 가면서 volatile 키워드의 사용이 많아 지고 있습니다.

volatile 키워드는 주로 memory-maped I/O에서 사용됩니다. 임베디드 시스템에서는 MCU의 각종 레지스터가 메모리에 매핑되어 있는 경우가 많고 프로그램은 매핑된 메모리 주소에 값을 반복적으로 쓰게 되는데 이때 volatile 키워드가 사용됩니다. 다음의 코드로 volatile 키워드가 어떻게 동작하는지 살펴봅시다.

 

unsigned int *uart = 0x40700000;
*uart = 0x00080001;
*uart = 0x00080002;
*uart = 0x00080003;
*uart = 0x00080004;
*uart = 0x00080005;


위 코드를 보면 다섯 번째 메모리 쓰기가 모두 0x4070000번지에 행해집니다. 일반적인 상황에서 위 코드를 수행하고 나면 0x40700000 번지에는 가장 마지막 값인 0x00080005만 남게 될 것입니다. 따라서 똑똑한 컴파일러는 위 코드를 아래 코드처럼 최적화합니다.

 

unsigned int *uart = 0x40700000;
*uart = 0x00080005;

 

어차피 변수에는 가장 마지막 값만 남으므로 이렇게 고치는 것입니다. 일반적인 프로르매에서라면 아무런 문제 없이 동작하고 속도도 빨라집니다. 하지만 이 코드가 MMIO(Memory-Maped I/O) 상황에서 사용된다면 이것은 분명히 잘모된 최적화입니다. 각각의 메모리 쓰기 작업이 하드웨어에 특정한 작업을 명령하기 때문에 저렇게 최적화를 해 버리면 하드웨어는 오작동을 하게 됩니다. 그러므로 컴파일러에 최적화를 하지 말라는 '지시'를 내려야 합니다. 이러한 지시는 내리는 키워드가 바로 volatile입니다.

 

volatile unsigned int *uart = 0x40700000;
*uart = 0x00080001;
*uart = 0x00080002;
*uart = 0x00080003;
*uart = 0x00080004;
*uart = 0x00080005;

 

위와 같이 uart변수를 volatile로 선언하면 컴파일러는 uart변수에 최적화를 하지 않고모든 읽기 쓰기 작업을 메모리에서 직접하게 됩니다. 위 코드의 쓰기 작업 뿐만 아니라 읽기 작업도 마찬가지 입니다.

 

unsigned int *uart = 0x40700000;
char ch;
int i;
for(i = 0;i < 5;i++)ch = *uart;

 

위와 같은 코드 역시 최적화를 하면 *uart의 값을 한번만 읽어서 캐시에 저장한 다음 이것을 반복해서 사용합니다. 하지만 아래 코드처럼 uart 변수가 volatile로 선언되면 루프안에서 uart변수를 요구할 때마다 매번 메모리에서 값을 가져오기 때문에 그때 그때 변경되는 값을 읽을 수 있습니다.


volatile unsigned int *uart = 0x40700000;
char ch;
int i;
for(i = 0;i < 5;i++)ch = *uart;

 

---------------



'게임개발공부 > 무작정퍼오기' 카테고리의 다른 글

함수객체  (0) 2013.12.29
DLL만 로딩해도 컴퓨터가 뻗는경우.  (0) 2013.12.21
extern "C" 이건 뭔가? (퍼온글)  (0) 2013.12.21
dll의 기본. (퍼온글)  (0) 2013.12.21
컨테이너 종류 복습  (0) 2013.12.21
Posted by JJOREG

가끔 일하다보면 extern "C"를 왜 써야 되는지도 모르고
사용하는 신입 코더들을 볼 수 있다. 때론 귀찮게 물어보기도 하고...
나중에 또 물어보면 이 페이지의 주소만 날려주리라. ㅋ

컴파일러는 link 작업시 오브젝트간 함수 이용 및 위치를 파악할 수 있도록,
컴파일시 사용된 함수에 관련된 정보를 오브젝트 파일에 기록하며, 이를 linkage라고 한다.

C++ 컴파일러는 컴파일 과정에서 Name mangling 이란 작업을 한다.
이는 정의되어 있는 함수명을 정해진 규칙에 의해 바꿔버리는 것이다.
(링크 에러날 때 본 적 있는가? 함수명 앞뒤에 붙은 이상한 기호와 숫자들을?)
(그리고 C++ 컴파일러가 왜 바꾸냐고? 그거 설명하려면 두꺼운 책 가져와야 된다... ㅈㅈ
굳이 목적 중에 하나를 설명하자면 override된 함수들의 구별을 위해... 이 정도)

참고로, C는 함수명 앞에 _ (underbar) 하나를 붙인다.

중요한 사실은 "코딩시 함수명과 컴파일 완료된 바이너리 코드에서의 함수명은 다르다" 라는 것이다.
이름을 바꾸는 규칙은 컴파일러에 따라 다르기 때문에 C++ 컴파일러가 다르면 호환이 되지 않는다.
즉, linkage type이 달라져 함수를 찾을 수 없게 되는 것이다.

이는 C++ 컴파일러 간에도 발생하는 문제이며 C와 C++ 컴파일러간에도 발생한다.
(당연하지 않은가, 컴파일러가 다르다니까 ㅋ)

공용의 라이브러리를 제작하였고 이 라이브러리를 사용하는 코더의 계층이 다양하다고 가정해보자.
C 환경에서도 사용을 할 것이고, 다른 C++ 환경에서도 사용할 것이다.
라이브러리 개발&배포자 입장으로써 링크 에러로 아우성치는 유저들 당황스럽다.

이를 피하기 위해 C++ 문법에서는 네임 맹글링을 막기 위해 아래 문장이 제공된다.
즉, C의 네임 형식으로 함수를 컴파일하는 것이다.

extern "C"

따라서 범용적인 라이브러리 헤더라면 아래와 같이 작성되는 것이 좋을 것이다.

// 상략
#ifdef __cplusplus
extern "C" {
#endif

// 중략
#ifdef __cplusplus
}
#endif
// 하략

자 이제 함수를 C 형식으로 컴파일 했다.
그러면 어떻게 되겠는가? name mangling 회피로 인한 link 문제는 해결이 되었다.

하지만 C 형식이므로 class의 멤버 변수가 될 수도, override가 될 수도 없다.
즉, C++ 고유 특성을 모두 잃어버리게 된다.

출처:extern "C"


'게임개발공부 > 무작정퍼오기' 카테고리의 다른 글

DLL만 로딩해도 컴퓨터가 뻗는경우.  (0) 2013.12.21
volatile (퍼온글)  (0) 2013.12.21
dll의 기본. (퍼온글)  (0) 2013.12.21
컨테이너 종류 복습  (0) 2013.12.21
텍스처 아틀라스  (0) 2013.12.20
Posted by JJOREG

1. DLL의 주요 장점


Dynamic Link Libray(DLL)에 대해 설명하기 전에, 
이것이 어떠한 장점이 있고 어떠한 경우 사용하기 좋은지 살펴보도록 하자.

단순히 라이브러리화 함으로써 얻는 이점이 아닌, DLL만이 가질 수 있는 내용으로 정리하였다.

1. 애플리케이션의 기능 확장

DLL은 프로세스의 주소 공간에 동적으로 로드될 수 있기 때문에,
애플리케이션이 수행 중이더라도 수행해야 할 작업이 결정되면 해당 작업을 수행할 수 있는 코드를 로드할 수 있다.

어떤 회사가 제품을 개발하고, 다른 회사가 이 제품의 기능을 
확장하거나 보강할 수 있도록 하려는 경우에도 DLL은 상당히 유용하게 사용될 수 있다.

2. 메모리 절약

두 개 혹은 그 이상의 애플리케이션이 동일한 DLL 파일을 사용할 경우, DLL을 램에 단 한 번만 로드하고, 
이 DLL을 필요로 하는 애플리케이션들은 앞서 로드한 내용을 공유할 수 있다.

C/C++ 런타임 라이브러리의 경우가 대표적인 예라고 할 수 있다.

매우 많은 애플리케이션들이 CRT 라이브러리를 사용하는데, 
모든 애플리케이션들이 이를 정적으로 링크하게 되면, 동일 기능들이 메모리에 여러 번 로드될 것이다.

하지만, DLL 형태의 CRT 라이브러리를 링크하게 되면, 이러한 기능들은 메모리상에 
단 한번만 로드될 것이므로 메모리를 좀 더 효율적으로 사용할 수 있게 된다.

3. 지역화 촉진

Localization을 위해 DLL을 사용하곤 한다.

예를 들어, 코드로만 구성되어 있어서, 어떠한 UI 컴포넌트도 포함하고 있지 않은 형태로 구동한 뒤,
해당 지역에 맞는 DLL를 로드하여, 지역화된 UI 컴포넌트를 사용할 수 있는 것이다.

4. 플랫폼 차별성 해소

다양한 윈도우 버전들은 각기 서로 다른 함수들을 제공하고 있다.

개발자들은 운영체제가 제공하는 최신의 기능을 사용하고 싶어 하지만,
그 기능이 제공되지 않는 낮은 버전의 운영체제에서는 프로그램이 실행조차 되지 않을 수 있다.

이러한 기능을 DLL 파일로 분리해 두면, OS 버전에 맞는 DLL을 로드하여
OS가 지원하는 최대한의 신 기능을 활용하도록 환경을 구성할 수 있게 된다.

5. 특수한 목적 달성

윈도우는 단지 DLL에서만 사용 가능한 몇몇 기능들을 가지고 있다.

Hook을 설치하는 것과 같은 작업(SetWindowsHookEx, SetWinEventHook)이 그 예가 될 수 있는데,
이 때 사용하는 훅 통지 함수는 반드시 DLL 내에 존재해야 한다.

윈도우 탐색기의 shell은 DLL 파일로 작성된 COM 오브젝트를 구성함으로써 그 기능을 확장할 수 있으며,

웹 브라우저는 DLL을 이용하여 ActiveX 컨트롤을 사용, 다양한 기능을 웹 환경에 제공하고 있다.


2. DLL과 프로세스 주소 공간

DLL 파일의 이미지는 실행 파일이나 다른 DLL이 DLL 내 포함되어 있는 함수를 호출하기 전에
반드시 프로세스의 주소 공간에 매핑되어 있어야 한다.

이를 위해 다음 두 가지 방법 중 하나를 선택할 수 있다.
  • 암시적 로드타임 링킹
  • 명시적 런타임 링킹
위 방법들에 대해선 아래 챕터에서 각각 자세히 설명하도록 하겠다.

DLL 파일 이미지가 프로세스의 주소 공간에 매핑되고 나면,
DLL이 가지고 있는 모든 함수들은 프로세스 내의 모든 쓰레드에 의해 호출될 수 있게 된다.

사실 이렇게 로드가 완료되고 나면, DLL 고유의 특성은 거의 없어진다고 볼 수 있다.

프로세스 내의 쓰레드 관점에서는 DLL이 가지고 있는 코드와 데이터들은
단순히 프로세스의 주소 공간에 로드된 추가적인 코드와 데이터들로 여겨질 뿐이다.

쓰레드가 DLL에 포함되어 있는 함수를 호출하게 되면, 
호출된 DLL 함수는 호출한 쓰레드의 스택으로부터 전달된 인자 값을 얻어내고,
호출한 쓰레드의 스택을 이용하여 지역변수를 할당하게 된다.

뿐만 아니라 DLL 함수 내부에서 생성하는 모든 오브젝트들도 
DLL 함수를 호출하는 쓰레드나 프로세스가 소유하게 되며, DLL 자체가 소유하는 오브젝트는 존재하지 않는다.

예를 들어, DLL 내의 특정 함수가 VirtualAlloc 함수를 호출하게 되면,
해당 함수를 호출한 쓰레드가 속해 있는 프로세스의 주소 공간 내에 영역이 예약된다.

만일 DLL이 프로세스의 주소 공간으로부터 내려간다 하더라도
앞서 프로세스의 주소 공간에 예약했던 영역은 그대로 남게 되는데,
이는 시스템이 해당 영역이 DLL로부터 예약되었다는 사실을 특별히 관리하지 않기 때문이다.

하지만, 단일의 주소 공간은 하나의 실행 모듈과 다수의 DLL 모듈로 구성되어 있음을 반드시 알아두어야 한다.

이 중 일부 모듈은 C/C++ 런타임 라이브러리를 정적으로 링크하고 있을 수도 있으며,
또 다른 모듈은 C/C++ 런타임 라이브러리를 동적으로 링크하고 있을 수도 있다.

따라서, 단일의 주소 공간 내에 C/C++ 런타임 라이브러리가 여러 번 로드될 수 있다는 사실을 잊어버리면 곤란한다.
아래 예제를 살펴보자.

  1. -- 실행 파일의 함수 --
  2. void EXEFunc()
  3. {
  4.     void* pv = DLLFunc();
  5.  
  6.     // pv가 가리키는 저장소를 사용한다.
  7.  
  8.     // pv가 EXE의 C/C++ 런타임 힙 내에 있을 것이라고 가정한다.
  9.     free(pv);
  10. }
  11.  
  12. -- DLL의 함수 --
  13. void* DLLFunc()
  14. {
  15.     // DLL의 C/C++ 런타임 힙으로부터 메모리를 할당받는다.
  16.     return (malloc(100));
  17. }

이 코드가 정상적으로 동작할 것인가?
DLL 함수 내에서 할당받은 메모리 블럭을 EXE 함수 내에서 정상적으로 해제할 수 있는가?

위 예제는 제대로 동작할수도 있고, 그렇지 않을 수도 있다.

만일 EXE와 DLL이 모두 DLL로 구성된 C/C++ 런타임 라이브러리를 사용하고 있다면, 위 예제는 정상 동작한다.

하지만, 둘 중 하나라도 C/C++ 런타임 라이브러리를 정적으로 링크하고 있다면, free 호출 과정에서 문제가 발생할 것이다.

이러한 문제는 애초에 습관을 제대로 들이면 된다.
DLL 내 메모리를 할당하는 함수가 있다면, 해제하는 함수도 DLL에 만들고 그걸 사용하는 것이다.

  1. -- 실행 파일의 함수 --
  2. void EXEFunc()
  3. {
  4.     // DLL 함수에서 할당한 메모리 블럭의 주소를 얻어온다.
  5.     void* pv = DLLAllocFunc();
  6.  
  7.     // pv가 가리키는 메모리 블럭을 사용한다.
  8.  
  9.     // DLL 함수를 이용해 pv를 메모리로 반환한다.
  10.     DLLFreeFunc(pv);
  11. }
  12.  
  13. -- DLL의 할당 함수 --
  14. void* DLLAllocFunc()
  15. {
  16.     // DLL의 C/C++ 런타임 힙으로부터 메모리를 할당받는다.
  17.     return (malloc(100));
  18. }
  19.  
  20. -- DLL의 해제 함수 --
  21. void DLLFreeFunc(void* p)
  22. {
  23.     // DLL의 C/C++ 런타임 힙에서 메모리를 해제한다.
  24.     free(p);
  25. }

그리고, 실행 파일 내에 전역으로 선언된 정적 변수는 동일한 실행 파일이
여러 번 실행될 경우라도 Copy-on-write 메커니즘에 의해 공유되지 않는다.

DLL 파일 내에 전역으로 선언된 정적 변수 역시 이와 동일한 메커니즘이 적용된다.

프로세스가 DLL 이미지 파일을 자신의 주소 공간 내에 매핑하는 경우
실행 파일의 경우와 동일하게 전역으로 선언된 정적변수의 새로운 인스턴스가 생성된다.

이를 공유하게 하는 방법에 대해선 PE/COFF의 Section 문서의 챕터 4. 공유 섹션을 보기 바란다.


3. DLL과 실행 파일 작성법

이제, DLL을 생성하는 방법과 실행 파일이 어떻게 DLL 파일의 함수나 변수를 사용해야 하는지에 대해 알아보자.

1. DLL 모듈 생성

DLL은 변수, 함수, C++ 클래스를 다른 모듈에 export 할 수 있다.

하지만, 코드의 계층적 추상화를 유지하고 DLL 코드를 좀 더 쉽게 유지/관리하기 위해 
변수는 가급적 익스포트 하지 않는 것이 좋다.

또한, C++ 클래스는 export 한 C++ 클래스를 사용하는 모듈을 
동일한 회사의 컴파일러를 사용하는 컴파일한 경우에만 사용할 수 있으므로 주의하도록 하자.

DLL을 작성할 때 export 하고자 하는 변수나 함수를 포함하고 있는 헤더 파일을 먼저 작성하는 것이 좋다.
이러한 헤더 파일에는 export 할 함수나 변수가 사용하는 심벌이나 데이터 구조체도 반드시 정의되어 있어야 한다.

이 헤더는 DLL과 함께 배포되어야 하며, 이 DLL을 사용하는 모듈은 이 헤더를 반드시 인클루드 해야 한다.

또한, 유지보수의 편의성을 위해 DLL 하나당 헤더 파일 1개씩 페어로 작성하는 것이 좋다.

이제 간단한 DLL 헤더의 예제를 살펴보도록 하자.
(아래 예제는 vs2010에서 DLLTest라는 DLL 프로젝트를 생성하면 기본적으로 생성시켜주는 헤더 파일이다)


  1. #ifdef DLLTEST_EXPORTS
  2. #define DLLTEST_API __declspec(dllexport)
  3. #else
  4. #define DLLTEST_API __declspec(dllimport)
  5. #endif
     
  6. // 이 변수는 DLLTest.dll에서 내보낸 것입니다.
  7. extern DLLTEST_API int nDLLTest;
  8.  
  9. // 이 함수는 DLLTest.dll에서 내보낸 것입니다.
  10. DLLTEST_API int fnDLLTest(void);

DLLTest 프로젝트의 전처리기에는 DLLTEST_API 가 선언되어 있다.
즉, 함수나 변수, 클래스 앞에 __declspec(dllexport) 선언 지정자가 붙는 것이다.

그리고 아래 예제는 역시 DLLTest 프로젝트가 기본 생성해준 cpp 파일이다.

  1. // DLLTest.cpp : DLL 응용 프로그램을 위해 내보낸 함수를 정의합니다.
  2. //
  3. #include "stdafx.h"
  4. #include "DLLTest.h"
  5.  
  6. // 내보낸 변수의 예제입니다.
  7. DLLTEST_API int nDLLTest=0;
  8.  
  9. // 내보낸 함수의 예제입니다.
  10. DLLTEST_API int fnDLLTest(void)
  11. {
  12.     return 42;
  13. }

위 cpp 예제에서 nDLLTest 변수와 fnDLLTest 함수 앞에 DLLTEST_API 매크로가 붙어 있지만,
cpp에서는 DLLTEST_API를 붙이지 않아도 무방하다.

자, 이제 위와 같이 기본 생성된 DLL 프로젝트를 빌드하고, DLL을 생성해 보자.
Output 폴더에 가보면, 다음 두 개의 파일이 생성되어 있다.
  • DLLTest.lib
  • DLLTest.dll
DLL 파일을 컴파일하면, 컴파일이 끝난 후 링커는 DLL의 소스코드가 
적어도 하나 이상의 함수/변수를 export 하고 있는지를 확인하고, 그 경우 .lib 파일을 생성한다.

.lib 파일은 어떠한 함수나 변수도 포함하고 있지 않기 때문에 그 크기가 매우 작으며,
단순히 DLL 파일이 export 하고 있는 함수나 변수의 심벌 이름만을 유지하고 있다.
이 파일은 DLL 파일이 export 하는 심볼을 참조하는 실행 모듈을 링크하는 과정에서 반드시 필요하다.
(이 내용은 DLL을 암시적 로드타임 링크할 때만 유효하다)

.lib 파일을 생성하는 것 외에도 링커는 DLL 파일 내에 해당 파일이 
export 하고 있는 심볼에 대한 정보를 테이블 형태로 포함시켜 준다.

export section이라고 불리는 이 테이블은 export 하고 있는 
변수, 함수, 클래스 심볼에 대한 목록을 (알파벳순으로) 가지고 있다.

링커는 이 외에도 상대 가상 주소(RVA:Relative Virtual Address)를 DLL 파일 내에 포함시키는데,
이 값은 각각의 심볼들이 DLL 모듈 내의 어느 위치에 있는지를 가리키는 값이다.

그럼 이제 DLLTest에서 export 하고자 한 변수와 함수가 제대로 export 되어 있는지를 dumpbin으로 확인해 보자.
꾸엑~ 
함수명 fnDLLTest은 ?fnDLLTest@@YAHZX로 
변수명 nDLLTest은 ?nDLLTest@@3HA로 naming 되어 있다.

이것은 C++ 컴파일러가 C++ 특성(override나 등등의)을 위해 컴파일시 함수나 변수명에 name mangling을 하기 때문이다.
물론 DLL도 C++로 작성하고, 사용 모듈 역시 C++로 작성된다면 실행에 아무런 방해가 되지 않는다.

하지만, DLL은 C++로 작성하고, 사용 모듈이 C로 작성될 수도 있는 상황이라면,
C로 작성한 사용 모듈에서 DLL 함수를 호출하면 링크 과정에서 존재하지 않는 심볼 참조 에러가 발생하게 된다.

이를 해결하기 위해선, extern "C" 를 사용하는 방법이 있다.

즉, 위 헤더를 다음과 같이 변경하는 것이다.

  1. #ifdef DLLTEST_EXPORTS
  2. #define DLLTEST_API extern "C" __declspec(dllexport)
  3. #else
  4. #define DLLTEST_API extern "C" __declspec(dllimport)
  5. #endif
  6.  
  7. // 이 변수는 DLLTest.dll에서 내보낸 것입니다.
  8. DLLTEST_API int nDLLTest;
  9.  
  10. // 이 함수는 DLLTest.dll에서 내보낸 것입니다.
  11. DLLTEST_API int fnDLLTest(void);

      하지만, extern "C" 는 C++ 클래스 사용시엔 문제가 발생하므로, 클래스는 조심해서 한정자를 사용해야 한다.
      자세한 내용은 http://sweeper.egloos.com/1792976 참고

      자 이제, 다시 한번 DLL 프로젝트를 빌드하고, dumpbin으로 확인해 보자.

      오~ 깨끗하게 나온다.
      헌데, 이 상태에서 fnDLLTest 함수의 calling convention을 __stdcall로 바꾸고 싶어졌다.

      문제는 M$ C 컴파일러가 __stdcall 함수에 대해선 C++를 사용하지 않다 하더라도,
      함수의 이름을 멋대로 바꾸는 작업을 수행한다는 것이다.

      함수의 이름 앞에 _ (underbar)를 붙이고, 함수의 이름 뒤엔 매개변수 크기의 총합을 @와 함께 표시한다.

      위 예제의 fnDLLTest 함수를 __stdcall로 호출하면, dumpbin 결과가 아래와 같이 나온다.
      역시 fnDLLTest 함수명이 바뀌었기에, 
      DLL을 사용하는 모듈이 타사의 컴파일러를 사용하는 경우 링크 에러를 발생시키게 된다.

      다른 회사의 컴파일러에서 사용될 DLL 파일을 M$ 컴파일러를 사용해 컴파일하려 하면,
      컴파일러에게 이름 변환을 수행하지 않도록 확실한 명령을 주어야만 한다.

      두 가지 방법이 있으나, 두 번째 방법은 별로이므로, 한가지만 소개하겠다.

      바로 .def 파일을 프로젝트에 추가하고 EXPORTS 섹션을 구성하는 것이다.
      .def 파일은 DLL의 ouput name과 동일하게 맞추어야 한다.

      1. LIBRARY DLLTest
      2.  
      3. EXPORTS
      4.         fnDLLTest
      5.         nDLLTest

      M$ 링커는 .def 파일을 분석하는 과정에서 fnDLLTest와 _fnDLLTest@0 이라는 두 개의 함수가 export 되었음을 인지하게 된다.
      그러나 이 두 함수의 이름이 서로 일치하기 때문에 (이름 변환 과정을 배제할 경우)
      .def 파일에 정의되어 있는 fnDLLTest 라는 함수만 export 하고, _fnDLLTest@0 는 export 하지 않는다.

      사실 .def 방식이 가장 확실하다.

      .def 파일을 사용하면, 
      • extern "C"를 붙이든 그렇지 않든,
      • 함수의 calling convention으로 __stdcall을 사용하든
      naming에 있어 아무런 문제가 되지 않는다.

      만약, 자신이 만든 DLL이 널리 배포될 용도로 제작되어야 한다면,
      조금 귀찮더라도 .def 파일을 프로젝트에 추가하고, EXPORTS 섹션을 구성함을 가장 추천한다.

      물론, M$ 컴파일러로 DLL을 만들고, 사용 모듈 역시 M$ 컴파일러로 만들고, 둘의 환경 모두 C++이라면
      아무 것도 하지 않아도 문제가 되진 않는다.


      2. 실행 모듈 생성

      우선, 아래 내용은 DLL을 Implicit Loadtime Link 할 경우에 초점이 맞추어져 있다.

      실행 파일의 소스 코드를 개발할 때에는 반드시 DLL과 함께 제공되는 헤더 파일을 인클루드해 주어야 한다.

      실행 파일의 소스 코드에서는 DLL 헤더 파일을 인클루드할 때, DLLTEST_EXPORTS 를 정의해선 안 된다.
      정의하지 않은 채 코드를 컴파일하게 되면,
      DLLTest.h 파일 내에서 DLLTEST_EXPORTS를 __declspec(dllimport)로 정의하게 된다.

      1. // DLL 헤더 인클루드
      2. #include "DLLTest.h"
      3.  
      4. // 테스트를 위한 암시적 로드타임 링킹
      5. #pragma comment(lib, "DLLTest.lib")
      6.  
      7. int _tmain(int argc, _TCHAR* argv[])
      8. {
      9.         // DLL의 변수에 값 설정
      10.         nDLLTest = 1;
      11.  
      12.         // DLL의 함수 호출
      13.         int num = fnDLLTest();
      14.  
      15.         return 0;
      16. }

      컴파일러가 변수, 함수, C++ 클래스에 대해 __declspec(dllimport) 한정자를 발견하게 되면, 
      이러한 심볼들이 다른 DLL 모듈에서 임포트된 것임을 알게 된다.

      다음 단계로 링커는 실행 파일의 소스 코드에서 사용되고 있는 심볼들이 어떤 DLL로부터 export 된 것인지를 확인해야 한다.
      이를 위해 링커에게 .lib 파일을 전달해 주어야 한다.

      앞서 말한 것과 같이 .lib 파일은 단순히 DLL 모듈이 export 하고 있는 심볼들에 대한 목록만을 가지고 있다.

      링커는 import 된 심볼을 찾는 과정에서 import section이라는 특수한 섹션을 실행 파일 내에 추가한다.
      이 import section은 실행 파일이 필요로 하는 DLL 모듈과 해당 DLL 모듈에서 참조되는 심볼에 대한 목록이 포함되어 있다.

      이제 실행 파일을 생성하고, dumpbin으로 내용을 확인해 보자.
      DLLTester.exe의 import 섹션에서 DLLTest.dll의 fnDLLTest 함수와 nDLLTest 변수의 심볼을 확인할 수 있다.

      이제 실행 파일을 실행해 보자.

      실행 파일이 실행되면...
      1. 운영체제의 로더는 프로세스를 위한 가상 주소 공간을 생성한다.
      2. 이후, 로더는 실행 모듈을 프로세스의 주소 공간에 매핑한다.
      3. 로더는 실행 파일의 import section을 확인하여 필요한 DLL 파일들을 찾아서 프로세스의 주소 공간에 추가 매핑한다.
      Import section 내에 포함된 DLL 이름은 전체 경로명을 포함하고 있지 않기 때문에,
      로더는 사용자의 디스크 드라이브로부터 DLL을 검색해야 한다.

      아래에 로더의 검색 순서를 나타내었다.
      1. 실행 파일 이미지가 있는 디렉토리
      2. GetSystemDirectory 함수의 반환 값인 윈도우 시스템 디렉토리 (Windows\System32)
      3. 16비트 시스템 디렉토리 (Windows\System)
      4. GetWindowsDirectory 함수의 반환 값인 윈도우 디렉토리 (Windows\)
      5. 프로세스의 현재 디렉토리
      6. PATH 환경 변수에 포함된 디렉토리
      DLL 모듈이 프로세스의 주소 공간에 매핑되면, 로더는 각 DLL 파일의 import section을 조사한다.
      (DLL 역시 다른 DLL을 링크할 수 있기 때문에...)

      만일 import section이 존재한다면, 로더는 계속해서 프로세스의 주소 공간에 
      추가적으로 필요한 DLL 파일들을 매핑시켜 나단다.

      로더는 DLL 모듈을 지속적으로 추적하여, 설사 여라 차례 참조되는 모듈이라 하더라도,
      단 한번만 로드되고 매핑될 수 있도록 한다.

      모든 DLL 모듈들이 프로세스의 주소 공간에 로드되고 매핑되면,
      로더는 import 된 심볼의 모든 참조 정보를 수정해 나간다.
      이를 위해 각 모듈의 import section을 다시 한번 살펴보게 된다.

      로더는 각각의 심볼에 대해 관련 DLL의 export section을 검토하고 심볼이 실제로 존재하는지 확인한다.

      로더는 심볼의 RVA 정보를 가져와서 DLL 모듈이 로드되어 있는 가상 주소 공간에 그 값을 더한다.
      이후, 실행 모듈의 import section 내에 계산된 가상 주소 값을 기록한다.

      이제 코드에서 import 된 심볼을 참조하게 되면 호출 모듈의 import section으로부터
      import 된 심볼의 위치 정보를 가져와서 import 된 변수, 함수, C++ 클래스의 멤버 함수에 성공적으로 접근할 수 있게 된다.

      애플리케이션이 새로 실행될 때마다 위와 같은 과정을 거쳐야 하므로, 초기 구동에 많은 비용을 소비하게 된다.

      애플리케이션의 로딩 속도를 향상시키기 위해 실행 파일과 DLL 모듈에 대해
      시작 위치 변경(rebase)과 바인딩(binding) 작업을 수행하는 것이 좋다.

      Rebase와 binding에 대해서는 추후 별도로 글을 하나 더 쓰는 것이 좋을 듯 하다.


      4. Implicit Loadtime Link

      암시적 로드타임 링크는 static libaray를 링크하는 방법과 완전히 동일한 방법으로 수행할 수 있다.
      또한, static library를 링크하는 것처럼 DLL의 export된 모든 변수와 함수, C++ 클래스가 
      프로세스의 주소 공간에 매핑된다.

      암시적 로드타임 링크시 어떻게 프로세스 주소 공간에 매핑되고,
      프로세스에서 DLL 내 심볼에 접근하는지에 대한 과정은 위 3-2. 실행 모듈 작성 챕터에 자세히 설명되어 있다.

      암시적 로드타임 링크는 역시 편안함에 기인한다.

      프로세스의 주 쓰레드가 돌기 시작한 이후
      별도로 DLL을 로드/해제할 필요가 없고, 심볼 역시 별도의 시작 주소를 얻어오는 과정 없이 바로 사용할 수 있기 때문이다.

      Static library와 링크하는 방법이 똑같지만, 이왕 정리하는 김에 
      암시적 로드타임 링크를 하는 두 가지 방법에 대해 다시 한번 소개하겠다.

      1. 프로젝트 속성의 링커-입력에 .lib 추가

      프로젝트 속성 - 링커 - 입력에 DLL의 .lib 파일을 추가하는 것이다.

      2. comment 지시어 사용

      위 3-2 예제에 나왔듯이 comment 지시어를 사용하여, 암시적 로드타임 링크를 시킬 수 있다.

      1. // 암시적 로드타임 링킹
      2. #pragma comment(lib, "DLLTest.lib")

      개인적으로는 2번 방법을 선호하는 편이다.

      위에서도 썼듯이, 프로젝트 속성에서 제어하는 방법은
      공동 작업자가 꼼꼼히 속성을 살피지 않으면 지나치기 일수라,
      코드에서 명백하게 링크하는 것이 경험상 훨씬 더 직관적인 것 같다.


      5. Explicit Runtime Link

      암시적 로드타임 링크가 프로세스가 시작되는 과정에서 
      암묵적으로 로더가 DLL을 프로세스의 주소 공간에 로드/매핑 시켜주는 것이라면,

      명시적 런타임 링크는 애플리케이션이 수행 중인 상황에서 필요한 심볼을 명시적으로 링크하는 방법을 말한다.

      다르게 표현하면, 애플리케이션이 수행되고 있는 상황에서 
      특정 쓰레드가 DLL 내에 포함되어 있는 함수를 호출하기로 결정한 경우
      프로세스의 주소 공간에 필요한 DLL 파일을 명시적으로 로드하여
      DLL 내에 포함되어 있는 함수의 가상 메모리 주소를 획득한 후, 이 값을 이용하여 함수를 호출하는 것을 말한다.

      이 방식의 매력은 모든 과정들이 애플리케이션이 수행 중인 상황에서 이루어진다는 것이다.

      또한, 암시적 로드타임 링크는 프로세스 시작 과정에서 DLL 로드/매핑까지 함께 이루어지기에,
      많은 수의 DLL을 링크할 경우 프로세스 구동 자체가 오래 걸릴 수 있는 문제가 있으나.
      명시적 런타임 링크의 경우 이 비용을 분산시킬 수 있다.

      그리고, DLL 사용 후 더 이상 필요치 않을 경우 
      가상 메모리에서 해제시킬수도 있기에 메모리도 효율적으로 관리할 수 있다.

      또한, 암시적 로드타임 링크와 다르게 명시적 런타임 링크시에는 .lib 파일에 사용되지 않는다.


      이제 명시적 런타임 링크를 사용하는 방법에 대해 본격적으로 설명하겠다.

      명시적 런타임 링크에 사용되는 세 가지 함수는 아래와 같다.
      • LoadLibrary
      • GetProcAddress
      • FreeLibrary

      1. LoadLibrary

      프로세스 내의 쓰레드는 LoadLibrary 함수를 통해 프로세스의 주소 공간에 DLL을 런타임에 매핑할 수 있다.

      1. HMODULE LoadLibrary(PCTSTR pszDLLPathName);
      2.  
      3. // 또는...
      4.  
      5. HMODULE LoadLibraryEx(
      6.     PCTSTR pszDLLPathName,
      7.     HANDLE hFile,    // reserved. 반드시 NULL을 넘겨야 한다.
      8.     DWORD flags);   // 0을 넘기거나 다양한 flag를 OR해서 넘기면 된다.

      이 함수들은 사용자의 시스템에서 파일 이미지를 검색하고 (검색 순서는 3-2. 로더의 검색 순서와 동일하다)
      함수를 호출한 프로세스의 주소 공간에 DLL 파일 이미지를 매핑하려고 시도한다.

      함수 호출이 성공하면, 파일 이미지가 매핑된 가상 메모리 주소를 나타내는 HMODULE 값을 반환한다.
      (HMODULE은 HINSTANCE와 완전히 동일한 의미이며, 혼용 또한 가능하다)

      함수 호출이 실패하면? NULL을 반환한다.

      LoadLibraryEx는 flags 변수를 통해 다양한 옵션을 줄 수 있다.
      자세한 설명은 생략한다 ㅋ 가 아니라, MSDN 페이지에서 확인하기 바란다.

      다만, 이 둘을 혼용하는 것은 원하지 않은 결과를 초래할 수 있으므로, 추천하지 않는다.

      디스크 상에 동일한 DLL을 이용하여 로드하였다 하여도,
      LoadLibrary와 LoadLibraryEx 함수가 반환하는 매핑 주소 값은 LoadLibraryEx의 flag에 따라 달라질 수 있다.

      LoadLibrary(Ex) 함수를 사용하게 되면, 프로세스별로 DLL에 대한 usage count를 증가시킨다.

      예를 들어, DLL을 로드하기 위해 LoadLibrary 함수를 최초로 수행한 경우
      시스템은 DLL 파일 이미지를 프로세스의 주소 공간에 매핑하고, DLL의 usage count를 1로 설정한다.

      만일 동일 프로세스 내의 쓰레드가 동일한 DLL 파일 이미지에 대해 LoadLibrary를 또다시 호출하게 되면,
      시스템은 DLL 파일 이미지를 프로세스의 주소 공간에 두 번 매핑하지 않고, DLL의 usage count만 2로 증가시킨다.

      이후 설명할 FreeLibrary를 호출하면, 해당 프로세스의 usage count가 감소되며,
      usage count가 0 이 되면, 시스템은 프로세스의 주소 공간으로부터 DLL 파일 이미지를 매핑 해제한다.

      위에서도 썼지만, 시스템은 DLL의 usage count를 프로세스별로 유지한다.

      A 프로세스에서와 B 프로세스가 XXX.dll을 로드하게 되면,
      A 프로세스 / B 프로세스 모두 XXX.dll의 usage count는 1로 설정된다.

      이후 B 프로세스가 FreeLibrary를 호출하여, usage count를 0으로 감소시키면,
      B 프로세스의 주소 공간으로부터 DLL 파일 이미지를 매핑 해제시키겠지만
      A 프로세스는 여전히 usage count가 1이며, 프로세스 주소 공간에 DLL이 매핑되어 있다.

      2. GetProcAddress

      쓰레드가 DLL 모듈을 명시적으로 로드하였다면, 
      이제 GetProcAddress를 통해 export 된 심볼에 대한 시작 주소를 얻어와야 한다.

      FARPROC GetProcAddress(HMODULE hInstDll, PCSTR pszSymbolName);

      첫번째 인자인 hInstDll은 LoadLibrary(Ex) 반환 값을 넘겨주면 된다.

      두번째 인자인 pszSymbolName이 ANSI 문자열임에 주목하라.
      이는 컴파일러/링커가 심볼의 이름을 DLL의 export section에 기록할 때, 항상 ANSI 문자열로 기록하기 때문이다.

      두번째 인자로 문자열 대신 숫자를 넘기는 방법도 있으나,
      이 방법은 M$가 deprecate 시킬 것이니 그만 사용할 것을 강조하고 있으니, 패스하겠다.

      GetProcAddress로 함수 심볼을 얻어오려 할 때엔,
      GetProcAddress로 얻은 주소를 적절한 원형의 함수 포인터로 형변환을 해 주어야 한다.

      즉, DLL 내의 함수가 다음과 같은 원형이라면

      bool GetParrity(int type);

      다음과 같이 함수 포인터 타입을 만들어 놓고,

      typedef bool (*pfnGetParrity)(int);

      GetProcAddress의 반환값을 함수 포인터 타입으로 형변환을 해 주어야 하는 것이다.

      1. pfnGetParrity fnGetParrity = (pfnGetParrity)GetProcAddress(hInstDll, "fnGetParrity");
      2.  
      3. bool result = fnGetParrity(1);

      3. FreeLibrary

      프로세스 내의 쓰레드에서 더 이상 DLL 파일 내의 심볼을 사용할 필요가 없게 되면,
      FreeLibrary 함수를 호출하여 프로세스의 주소 공간으로부터 DLL 파일을 명시적으로 unload 할 수 있다.

      BOOL FreeLibrary(HMODULE hInstDll);

      이 함수의 인자인 hInstDll은 LoadLibrary(Ex)의 반환값을 넘겨주면 된다.

      위 LoadLibrary 설명에서도 썼듯이, FreeLibrary는 프로세스의 DLL usage count를 감소시킨다.
      usage count가 0 이 되면, 시스템은 프로세스의 주소 공간으로부터 DLL 파일 이미지를 매핑 해제한다.

      4. 정리 예제

      마지막으로 지금까지 알아본 LoadLibrary, GetProcAddress, FreeLibrary 함수들을 사용하는 간단한 예제를 첨부하겠다.

      1. #include <Windows.h>
      2. // DLL 헤더 인클루드
      3. #include "DLLTest.h"
      4.  
      5. // fnDLL 함수 포인터 타입데푸
      6. typedef int (*pfnDLLTest)(void);
      7.  
      8. int _tmain(int argc, _TCHAR* argv[])
      9. {
      10.         ///////////////////////////////////////////////////////////////////
      11.         // DLLTest.dll을 명시적으로 로드한다.
      12.         // 프로세스의 DLL usage count 증가
      13.         HMODULE hModule = LoadLibrary(_T("DLLTest.dll"));
      14.         if (nullptr == hModule)
      15.         {
      16.                 return -1;
      17.         }
      18.  
      19.         ///////////////////////////////////////////////////////////////////
      20.         // DLL의 변수 심볼의 주소 얻어오기
      21.         // GetProcAddress의 두번째 인자는 ANSI 스트링만 넘길 수 있다.
      22.         // 이는 export section에 심볼 이름이 ANSI 스트링으로 저장되기 때문이다.
      23.         int* pDllNum = (int*)GetProcAddress(hModule, "nDLLTest");
      24.         if (nullptr == pDllNum)
      25.         {
      26.                 return -1;
      27.         }
      28.         // DLL 변수 참조
      29.         int numInDLL = *pDllNum;
      30.  
      31.         ///////////////////////////////////////////////////////////////////
      32.         // DLL의 함수 심볼의 주소 얻어오기
      33.         pfnDLLTest pfnDLLTestFunc = (pfnDLLTest)GetProcAddress(hModule, "fnDLLTest");
      34.         if (nullptr == pfnDLLTestFunc)
      35.         {
      36.                 return -1;
      37.         }
      38.  
      39.         // DLL 함수 호출
      40.         int dllFuncNum = pfnDLLTestFunc();
      41.  
      42.         ///////////////////////////////////////////////////////////////////
      43.         // DLLTest.dll을 명시적으로 해제한다
      44.         // 프로세스의 DLL usage count 감소
      45.         // DLL의 reference count가 0이면, 해당 프로세스의 주소 공간에서 영역 해제
      46.         FreeLibrary(hModule);
      47.        
      48.         return 0;
      49. }


      '게임개발공부 > 무작정퍼오기' 카테고리의 다른 글

      volatile (퍼온글)  (0) 2013.12.21
      extern "C" 이건 뭔가? (퍼온글)  (0) 2013.12.21
      컨테이너 종류 복습  (0) 2013.12.21
      텍스처 아틀라스  (0) 2013.12.20
      맴버함수를 쓰레드로 쓰는법  (0) 2013.11.27
      Posted by JJOREG