본문 바로가기

프로그래밍/MaxSDK

[MAXSDK] DLL, 라이브러리 함수, 클래스 기술자

원문 : MAX SDK 6.0 도움말. DLL, Library Functions, and Class Descriptors

 

DLL, 라이브러리 함수, 클래스 기술자

개관

다음 DLL 과 라이브러리 함수는 반드시 3ds 맥스 플러그인 개발자에 의해서 항상 구현되어야만 한다. 그것들은 로드 시간에 3ds 맥스와 윈도우즈에 의해서 호출되며, 로드되고 있는 DLL 에 대한 정보와 DLL 에 의해 제공되는 플러그인 클래스들에 대한 정보를 제공한다. 함수는 다음과 같다 :

    DllMain(HINSTANCE hinstDLL, ULONG fdwReason, LPVOID lpvReserved)
    LibNumberClass()
    LibClassDesc(i)
    LibDescription()
    LibVersion()


이 섹션은 Class Descriptor 의 세부사항과 함께 이들 함수 각각에 대한 정보를 제공한다. Class Descriptor 는 플러그인 클래스들에 대한 정보를 제공하며, LibClassDesc() 함수를 구현하는 중에 개발자에 의해 사용된다.

DllMain() 함수

3ds 맥스 플러그인은 DLL 로 구현된다. DLL 은 Dynamic Link Library 의 약자이다. DLL 은 목적 코드 라이브러리(object code library)인데, 이는 다중 프로그램들이 코드, 데이터, 자원을 공유하도록 해 준다. 개발자가 자신의 플러그인 코드를 컴파일하고 링크할 때, 출력은 DLL 이다. DllMain() 함수는 개발자에 의해 구현되며, 플러그인 DLL 이 로드될 때 그 시작점에서 윈도우즈에 의해 호출된다.

DllMain()

이 함수는 DLL 이 로드될 때 윈도우즈에 의해 호출된다(또한 다른 경우에 렌더링 동안 인스턴스를 위해서도 호출된다). 대부분의 플러그인은 이 함수 내부에 단순히 두 개의 호출을 만든다. 그것은 3ds 맥스 커스텀 사용자 인터페이스 컨트롤을 초기화하는 것과 윈도우즈95 공통 컨트롤을 초기화하는 것이다. 아래의 샘플코드는 대부분의 플러그인 유형에 대해서 일반적인데, 이것이 수행되는 방식을 보여준다. 개발자들은 DllMain() 함수에 대한 세부적인 사항을 알기 위해서 VC++ 도움말을 참조할 수 있다.

컨트롤을 초기화하기 위한 호출이 한 번만 이루어짐에 주의하라. 이것은 컨트롤이 첫 번째로 초기화될 때 전역변수를 TRUE로 설정함으로써, 그리고 매번 함수가 호출될 때마다 그것을 검사함으로써 수행된다.

위에서 말했듯이 이 함수는 렌더링과 같은 중요한 연산을 하는 동안 여러 번 호출될 수도 있다. 결국 개발자는 이 함수 내부에서 수행되는 것에 대해 주의를 할 필요가 있다. 아래의 코드에서 DLL 이 처음 로드된 이후에는 단지 몇몇 문장만이 실행되는 방식에 대해서 주목하기 바란다. 이 함수는 TRUE를 반환해야 한다.

int controlsInit = FALSE;

BOOL WINAPI DllMain(HINSTANCE hinstDLL,ULONG fdwReason,LPVOID lpvReserved)
{

    // 이 DLL 의 인스턴스 핸들을 저장한다.
    hInstance = hinstDLL;

    if (! controlsInit)
    {
        controlsInit = TRUE;

        // Initialize MAX's custom controls
        InitCustomControls(hInstance);

        // Initialize Win95 controls
        InitCommonControls();
    }

    return(TRUE);

}


라이브러리 함수들

이 함수들은 플러그인 DLL 내부에 제출된 클래스에 대한 정보 및 그 클래스에 대한 접근을 제공한다.

LibNumberClasses()

3ds 맥스가 처음 시작하면, 그것은 로드할 DLL 을 검색한다. 일단 찾으면 그것은 DLL 내부의 플러그인 개수를 결정할 방식을 필요로 한다. 프로그래머는 이 정보를 LibNumberClasses() 함수를 정의함으로써 맥스에 제공한다. 예를 보자 :

_declspec(dllexport) int LibNumberClasses(){    return 1;    }


개발자는 DLL 에 포함되는 플러그인 개수를 반환해야 한다.

LibClassDesc(i)

플러그인은 플러그인에 의해 정의된 Class Descriptor 를 획득할 수 있는 방법을 시스템에 제공해야만 한다. Class Descriptor 는 DLL 내의 플러그인 클래스들에 대한 정보를 시스템에 제공한다. 함수 LibClassDesc(i) 는 시스템으로 하여금 클래스 기술자에 접근할 수 있도록 허용한다. DLL 은 각 플러그인 클래스를 위해서 하나씩, 총 몇 개의 클래스 기술자를 가지고 있을 것이다. 이 함수는 해당 순서의 클래스 기술자에 대한 포인터를 반환해야 한다.

__decl(dllexport) ClassDesc * LibClassDesc(int i)
{
    switch(i)
    {
    case 0 : return &MeltCD;
    case 1 : return &CrumpleCD;
    default : return NULL;
    }
}


이 예제는 Melt 플러그인 클래스 기술자나 Crumple 플러그인 클래스 기술자에 대한 포인터를 반환한다. LibClassDesc(i) 에 의해 반환되는 클래스 기술자에 대한 논의를 위해서 이 토픽의 마지막에 있는 섹션을 참조하라.

LibDescription()

시스템이 접근할 수 없는 (예를 들어 DLL 이 이용 불가능한) 엔터티(절차적 오브젝트, 수정자, 컨트롤러 등)을 포함하는 3ds 맥스 파일이 로드될 때, 메시지가 사용자에게 보내진다. 시스템은 각 DLL 에 만약 그것이 이용 불가능할 때 사용자에게 제출해야 하는 텍스트 문자열을 요구하게 된다.

예를 들어 사용자가 Melf 수정자를 가지고 있다고 하자. 그는 씬에서 오브젝트에 melt 를 적용하여 그 파일을 저장한다. 그리고 나서 그는 이 파일을 Melt DLL을 가지고 있지 않은 친구에게 보냈다. 친구가 그 파일을 로드할 때, 시스템은 파일에 있는 엔터티가 찾을 수 없는 DLL 에 의존하고 있음을 지시하는 메시지를 올리게 된다. 이 메시지는 다음과 같을 것이다 : "Melt Modifier : To obtain a copy, call 1-800-PLUG-INS". 시스템에 이 문자열을 제공하기 위해 DLL은 반드시 LibDescription() 함수를 구현해야 한다. 이 함수는 DLL 을 찾을 수 없을 때 사 제출되는 문자열을 반환한다. 이 문자열은 File/Summary Info/Plug-in Info... 명령을 통해 다이얼로그에서 3ds 맥스 사용자에게 제출된다. 씬에서 DLL 의 플러그인이 사용되면, 그 시스템은 3ds 맥스 파일에 그 문자열을 저장하게 된다(왜냐하면 그것은 DLL 이 이용불가능할 때 반드시 제출되어야 하기 때문이다).

씬은 DLL 이 제출되지 않더라도 여전히 로드된다는 점에 주의하라. 3ds 맥스는 DLL을 찾을 수 없는 상황에도 모든 엔터티를 보존한다. 이러한 방식으로 그 파일이 수정되거나 저장되면, 사용자는 DLL 을 획득한 다음 수정된 파일을 로드할 수 있으며, 그 엔터티는 여전히 씬에 제출되고 연결되어 있다. DLL 에 대한 접근 없이 로드된 엔터티는 orphaned(고아가 된) 엔터티로서 참조된다.

orphaned 엔터티는 자신의 SuperClass 를 위한 일반적인 '대역(stand-in)'으로 로드될 것이다. 대역은 최소한의 씬 표현을 디스플레이할 것이다. 예를 들어 엔터티가 수정자라면, 그것은 수정자 리스트에서 자신의 이름을 디스플레이할 것이다. 그러나 어떠한 파리미터도 제출되지는 않는다. Missing 오브젝트 유형은 씬에서 더미유형의 표현을 가진다. 그것들은 이동/회전/스케일링/링크/그룹화/제거될 수 있다: 단지 노드를 포함하는 어떤 것이다. Missing 컨트롤러는 단지 조정될 수 없는 상수 기본 값만을 제공한다.

Read Only Plug-Ins 에 있는 Advanced Topics 를 참조하라. 플러그인이 읽기 전용 모드를 가지도록 허용함으로써, 사용자는 DLL을 자유롭게 배포할 수 있다. 그러나 그들이 특정 하드웨어 lock 의 ID에 기반해 실행되기 위한 권한을 가지고 있지 않은 이상, 그들은 사용자가 자신만의 복사본을 구입할 때까지 제한된 사용을 하게 될 것이다. 이 함수의 샘플 구현이 아래에 나와 있다.

__declspec( dllexport ) const TCHAR *LibDescription() {

    return _T("Melt Modifier. Call 1-800-PLUG-INS to obtain a copy");

}


LibVersion

개발자는 시스템이 3ds 맥스 플러그인 DLL 의 구(obsolute) 버전을 다룰 수 있도록 하기 위해 함수를 구현해야만 한다. 3ds 맥스 아키텍처는 그것의 플러그인에 대해 그러한 닫힌 연관(close relationship)을 제공하기 때문에, 시스템은 이전의 플러그인 DLL을 로드되고 있는 것으로부터 보호할 필요가 있게 된다. 3ds 맥스가 이를 수행하도록 하기 위해서, DLL 은 반드시 LibVersion() 이라는 함수를 구현해야만 한다. 이 함수는 단지 기정의된 상수만을 반환하는데, 이는 컴파일되었던 상황에서의 시스템 버전을 가리킨다. 3ds 맥스의 이후 버전은 이 상수를 갱신할 것이지만, 아직 이전 DLL 들은 항상 이전 값을 반환할 것이다. 이 함수를 사용해 시스템은 구버전 DLL 이 로드되고 있는지 검사할 수 있으며, 만약 그렇다면 메시지를 출력하게 된다.

__declspec( dllexport ) ULONG LibVersion() { return VERSION_3DSMAX; }


주의 : VERSION_3DSMAX 는 현재 3ds 맥스 릴리즈와 함께 3ds 맥스 API 릴리즈 번호, 3ds 맥스 SDK 릴리즈 번호에 대한 정보를 포함하도록 R2 에서 강화되었다.

상위 워드는 이제 3ds 맥스 릴리즈 번호 * 1000 을 포함하며, 하위 워드는 API 번호와 API 버전 내부의 SDK revision 이다. 3ds 맥스는 단지 현재 API 번호를 가지고 있는 DLL 만을 로드할 것이다.

버전 번호는 다음과 같이 작성된다 :

#define VERSION_3DSMAX ((MAX_RELEASE<<16)+(MAX_API_NUM<<8)+MAX_SDK_REV)


각 부분에 대한 설명이 아래에 나와 있다 :

    MAX_RELEASE
    이것은 3ds 맥스 릴리즈 번호 * 1000 이다.

    MAX_API_NUM
    이것은 3ds 맥스 API 번호이다. API에서의 변경이 DLL이 재컴파일될 것을 요구하면, 이 번호는 증가하며, MAX_SDK_REV 이 0으로 설정된다. 이것은 모든 DLL 들이 로드할 수 없는 이전의 MAX_API_NUM을 가지도록 만든다.

    MAX_SDK_REV이것은 주어진 API 에 대한 SDK 의 revision 을 표현한다. 이것은 SDK 기능이 중요한 방식으로 변경되었을 때 증가한다(예를 들어 새로운 GetProperty() 질의 응답이 추가되었을 때). 그러나 헤더는 변경되지 않는다.



주의 : 개발자는 이 값들에 전역 함수들을 사용해 접근해야 한다.

Function :

DWORD Get3DSMAXVersion()

Remark : 

실행중인 3ds 맥스의 버전이 컴파일되었을 때  \MAXSDK\INCLUDE\PLUGAPI.H 에 #define 된 VERSION_3DSMAX 의 상태를 반환한다.

다음 매크로들이 VERSION_3DSMAX 의 일부를 추출하기 위해서 사용될 수 있다.

#define GET_MAX_RELEASE(x) ((x>>16)&0xffff)
#define GET_MAX_API_NUM(x) ((x>>8)&0xff)
#define GET_MAX_SDK_REV(x) (x&0xff)
#define GET_MAX_SDK_NUMREV(x) (x&0xffff)



 

Function :


APPLICATION_ID GetAppID();

Remarks :

이 함수는 릴리즈 2.5 이상에서 이용 가능하다.
이 함수는 ApplicationID인 VIZ 나 MAX 를 반환한다. 만약 플러그인이 하나의 제품에서만 작동하도록 설계되었다면, 그것은 이 함수를 ClassDesc::IsPublic() 내부에서 호출해 플러그인을 외부로 보일 것인지 아닌지를 변경하게 된다.
예를 들어 :
IsPublic() { return(GetAppID()==kAPP_VIZ?1:0); } // VIZ only

Return Value :

다음 열거형 값 중 하나를 반환 :
kAPP_NONE
kAPP_MAX
kAPP_VIZ



라이브러리 함수 요약

플러그인은 반드시 다 섯개의 함수를 구현해야만 한다 : DllMain(), LibNumberClasses(), LibClassDesc(i), LibDescription(), LibVersion(). 이 함수들은 시스템이 DLL 에 포함된 플러그인에 대한 정보를 결정하게 해 준다.

아래의 섹션은 LibClassDesc(i) 에서 반환되어야 하는 Class Descriptor 에 대한 정보를 제공한다.

class Descriptor

클래스 기술자는 시스템에 DLL 에 있는 플러그인들에 대한 정보를 제공한다. 클래스 기술자의 메서드는 플러그인 클래스의 새로운 인스턴스를 할당할 책임이 있다. 개발자는 ClassDesc 로부터 상속되고 그것의 몇 몇 메서드를 구현한 클래스로부터 클래스 기술자를 생성한다. 아래는 샘플 클래스 기술자와 단일 정적 인스턴스의 선언이다.

class MeltClassDesc : public ClassDesc {

    public:

    int     IsPublic() { return TRUE; }

    void *    Create(BOOL loading=FALSE) { return new MeltMod(); }

    const TCHAR *  ClassName() { return _T("Melt"); }

    SClass_ID   SuperClassID() { return OSM_CLASS_ID; }

    Class_ID   ClassID() { return Class_ID(0xA1C8E1D1, 0xE7AA2BE5); }

    const TCHAR*  Category() { return _T(""); }

};

static MeltClassDesc MeltCD;


클래스 기술자의 6 개의 메서드들이 아래에 설명되어 있다 : IsPublic(), Create(), ClassName(), SuperClassID(), ClassID(), Category(). 전체 메서드 집합에 대해서 알고자 하는 개발자는 Class ClassDesc 를 참조하라.

IsPublic()

이 메서드는 Boolean 값을 반환한다. 보통의 경우 플러그인이 사용자에 의해 선택되고 할당될 수 있다면 그것은 TRUE 를 반환한다. 어떤 플러그인은 같은 DLL 에서 구현된 다른 플러그인에 의해서 사적으로 사용될 것이며, 사용자를 위해서 선택할 수 있도록 하는 리스트에서 보이지 않아야 한다.이런 플러그인들은 FALSE 를 반환한다.

Create(BOOL loading= FALSE)

3ds 맥스는 플러그인 클래스의 새로운 인스턴스에 대한 포인터를 필요로 할 때 이 메서드를 호출한다. 예를 들어 3ds 맥스가 디스크로부터 이전에 사용된 플러그인(절차적 오브젝트, 수정자, 컨트롤러 등)을 포함하고 있는 파일을 로드하고 있다면, 그것은 플러그인의 Create() 메서드를 호출할 것이다. 플러그인은 자신의 플러그인 클래스의 새로운 인스턴스를 할당함으로써 응답한다. 위에 있는 샘플 구현은 단순히 new 연산자만을 사용한다.

Create() 에는 선택적 인자가 넘겨지는데, 그것은 클래스가 생성되고 있을 때 디스크 파일로부터 로드될 것인지를 지시하는 플래그이다. 만약 이 플래그가 TRUE 라면 플러그인은 그 오브젝트에 대한 초기화코드를 수행할 필요가 없다. 왜냐하면 로딩 프로세스가 그것을 처리할 것이기 때문이다. 더 많은 정보를 원한다면 Advanced Topics 섹션 Loading and Saving 을 참조하라.

시스템이 플러그인 클래스의 인스턴스를 제거하고자할 필요가 있을 때, 그것은 보통 DeleteThis() 라는 메서드를 호출한다. 플러그인 개발자는 이 메서드도 반드시 구현해야 한다. 개발자는 메모리를 할당하기 위해서 new 연산자를 사용하기 때문에, 그/그녀는 그것을 해제하기 위해서 delete 연산자를 사용해야 한다. 예를 들어 개발자는 다음과 같이 Deletethis()를 구현할 수 있다 :

void DeleteThis() { delete this; }


더 많은 정보를 원한다면 nced Topics 섹션 Memory Allocation 을 참조하라.

ClassName

이 메서드는 클래스의 이름을 반환한다. 이 이름은 3ds 맥스 사용자 인터페이스에서 플러그인을 위한 버튼에 나타난다.

SuperClassID()

이 메서드는 이 플러그인이 상속한 클래스를 설명하는 시스템 정의 상수를 반환한다. 예를 들어 Bend 수정자는 OSM_CLASS_ID 이다. 이 슈퍼 클래스 ID 는 모든 오브젝트 공간 수정자에 의해 사용된다.슈퍼 클래스 ID 의 예는 다음과 같다 : CAMERA_CLASS_ID, LIGHT_CLASS_ID, SHAPE_CLASS_ID, HELPER_CLASS_ID, SYSTEM_CLASS_ID. 이용 가능한 슈퍼 클래스 ID 의 전체 리스트를 보려면 List Of Super IDs를 참조하라.

ClassID()

이 메서드는 플러그인 오브젝트를 위한 단일 아이디를 반환해야 한다. 이러한 ClassID 를 생성하기 위해 SDK 에 제공된 프로그램이 있다. 당신의 플러그인을 위한 ClassID 를 생성하기 위해서 이 프로그램을 사용하는 것은 매우 중요하다. 만약 당신의 플러그인을 생성하기 위해서 소스 코드의 예제 중 하나를 사용한다면, 반드시 현존하는 Class_ID 를 변경해야만 한다. 만약 그렇게 하지 않으면, 충돌이 날 것이다. 만약 두 개의 ClassID 가 충돌하면, 시스템은 단시 첫 번째 검색한 것만을 로드할 것이다(그리고 그것이 두 번째의 것을 로드하려고 시도할 때, Class_ID 가 충돌한다는 메시지를 발생할 것이다).

Class_ID 는 두 개의 부호없는 32 비트 요소로 구성되어 있다. 생성자는 이들 각각에 값을 할당하는데, 예를 들어 Class_ID(0xA1C864D1, 0xE7AA2BE5)이다. 레퍼런스 정보를 원하면 Class Class_ID 를 참조하라.

3ds 맥스에 있는 샘플 코드 플러그인은 Class_ID 의 두 번째 32 비트 요소로서 0을 사용한다는 데 주의하라. 단지 (맥스에 실려있는) 내장된 클래스들만이 두 번째 32 비트가 0인 값을 가질 수 있다. 개발자가 사용하는 모든 플러그인은 32비트 요소를 모두 사용한다. 이것은 플러그인 사이에 충돌이 일어나지 않도록 보장할 것이다. 임의의 Class_ID 를 생성하고 그것을 클립보드에 선택적으로 복사하기 위해서는, Generate a Clas ID 를 클릭하라.

Category()

카테고리는 커맨드 패널의 create branch 에서 가장 밑에 있는 드롭 다운 리스트에서 선택된다. 만약 이것이 현존하는 카테고리(예를 들어 "Standard Primitives", "Particle Systems" 등)로 설정되면, 플러그인은 그 카테고리에 나타날 것이다. 개발자는 3ds 맥스에 의해 제공되는 카테고리에 추가하면 안 된다.(아래의 주의를 보라) 만약 카테고리가 아직 존재하지 않는다면, 그것이 생성된다. 만약 플러그인이 리스트에 나타날 필요가 없다면, 그것은 단지 null 문자열을 반환할 것이며, _T("")와 같다. Category() 는 button sets 다이얼로그에서 그것들을 식별하기 위해서 수정자를 위해 사용된다.

중요한 주의사항 : 3ds 맥스 아키텍처는 Create branch 에 카테고리당 12 개의 플러그인 제한을 가지고 있다. 카테고리당 너무 많은 플러그인을 가지고 있는 문제를 막기 위해서, 개발자는 표준 3ds 맥스 플러그인에 의해 사용되고 있는 현존하는 카테고리를 사용하기 보다는 항상 자신의 플러그인을 위한 새로운 카테고리를 생성해야만 한다. 1.2 이전의 3ds 맥스 버전은 카테고리당 12 개의 버튼을 초과할 경우 폭주한다.


 

[출처] - 라이푸님의 http://blog.naver.com/lifeisforu 네이버 블로그

'프로그래밍 > MaxSDK' 카테고리의 다른 글

[MAXSDK] 기하 파이프라인 시스템  (0) 2009.12.03
[MAXSDK] 인터벌  (0) 2009.11.30
[MAXSDK] 메모리 할당  (0) 2009.11.30
[MAXSDK] 맥스 SDK의 기본 개념  (0) 2009.11.30
[MAXSDK] SDK 일반 용어  (0) 2009.11.30