|
지난 번 시간에는 C++ 클래스의 생성자와 소멸자에 대한 프록시 함수를 디자인 하는 것에 관해서 다루어보았습니다. 이번 시간에는 일반 멤버 함수, 정적 멤버 함수, 멤버 변수에 관한 사항을 다루어보도록 하겠습니다. 그리고 강좌를 시작하기 전에 따로 팁&강좌란에 옮겨주신 운영자 내길님께 감사의 뜻을 전합니다. 미약한 강좌이지만 여러 분께 도움이 되길 바라는 마음에서 힘이 닿는대로 계속 강좌를 써 나가도록 하겠습니다. * 지난번 글을 다시 여시지 않도록 하기 위하여 클래스를 다시 정의합니다. // TestClass.h에 정의하는 내용입니다. class TestClass { public: void TestFunction(int value); int MyFunction(void); private: int m_myValue; public: int GetMyValue(void); void SetMyValue(int value); public: TestClass(int value); } // TestClass.cpp에 정의하는 내용입니다. #include <iostream> void TestClass::TestFunction(int value) { stdout << value; } int TestClass::MyFunction(void) { return 123; } int TestClass::GetMyValue(void) { return this->m_myValue; } void TestClass::SetMyValue(int value) { this->m_myValue = value; } TestClass::TestClass(int value) { SetMyValue(value); } 3. 일반적인 멤버 함수의 프록시 함수 만들기 멤버 함수는 크게 두 종류로 구분이 됩니다. 하나는 일반 멤버 함수이며 또 하나는 정적 멤버 함수입니다. 일반 멤버 함수는 클래스의 인스턴스가 생성된 이후에 호출할 수 있는 함수를 뜻합니다. 반면 정적 멤버 함수는 클래스의 이름을 네임스페이스로 취급하여 호출될 수 있는 함수를 뜻합니다. 쉽게 생각하시면 static 키워드를 붙인것과 붙이지 않은 것의 차이입니다. (C++의 static 키워드가 C#에서도 쓰이고 있으며, VB .NET에서는 Shared 키워드를 사용합니다.) 우선은 일반적인 멤버 함수에 관하여 생각해 보기로 하겠습니다. void TestClass_TestFunction(TestClass* pInstance, int value) { if(pInstance) pInstance->TestFunction(value); } int TestClass_MyFunction(TestClass* pInstance) { if(pInstance) return pInstance->MyFunction(); return 0; } 값을 반환하지 않는 멤버 함수의 프록시 함수를 만드는 것은 반환할 값에 관해 연연할 필요가 없으며 단지 받은 포인터가 NULL 참조인지 아닌지만을 확인해서 선택적으로 호출하기만 하면 되는 것입니다. 값을 받는 멤버 함수의 프록시 함수를 만드는 것은 정상적으로 호출이 이루어졌을 경우와 그렇지 않을 경우를 구분하여 값을 반환해야 합니다. 즉, 성공하면 메서드가 반환하는 값 그 자체를 반환해야 하며 호출에 실패할 경우 실패하였음을 공지하여야 합니다. 반환 값을 반환할 때에는 실행이 어디서 실패한 것인지를 분명히 구분해주어야 하므로 복잡한 로직을 설계하는 경우 신경써야 합니다. 4. 정적 멤버 함수의 프록시 함수 만들기 정적 멤버 함수는 클래스의 이름을 하나의 네임스페이스로 취급하는 함수이기 때문에 클래스의 인스턴스를 필요로 하지 않습니다. 단지 static으로 선언된 C++ 멤버 함수 하나만을 호출해주면 됩니다. 여기서는 앞에서 선언한 TestFunction과 MyFunction이 정적 멤버 함수라고 가정하고 예제를 보여드리도록 하겠습니다. void TestClass_static_TestFunction(int value) { TestClass::TestFunction(value); } int TestClass_static_MyFunction() { return TestClass::MyFunction(); } 5. 멤버 변수에 관한 프록시 함수 만들기 멤버 변수에 관한 프록시 함수를 만들기 위해서는 몇 가지 사항을 더 고려해 봐야 합니다. 첫 번째 조건은 멤버 변수의 액세스 제한이 public 인지 아니면 private, protected와 같은 제약이 걸려있는지 여부입니다. 두 번째는 일반 멤버 변수인지 정적 변수인지 확인해야 합니다. 첫 번째 조건에서 후자의 경우에 해당된다면 클래스의 멤버 함수로 getter와 setter 멤버 함수를 도입해야 합니다. 보호된 멤버를 직접 액세스해서 값을 변경할 수는 없기 때문입니다. 그러나 전자의 경우에 해당된다면 getter와 setter 멤버 함수를 도입하지 말고 프록시 함수로 getter 함수와 setter 함수를 정의하시면 됩니다. 두 번째 조건에서 후자의 경우에 해당된다면 멤버 함수와 마찬가지로 getter와 setter 함수 모두에서 클래스의 인스턴스는 필요치 않습니다. 그러나 전자의 경우에 해당된다면 getter와 setter 함수 모두에서 클래스의 인스턴스가 필요합니다. getter에서는 멤버 변수의 값에 접근할 수 없었음을 알리는 실패 코드를 적절히 통지해야 하며 setter에서는 멤버 변수의 값에 접근할 수 없었음을 알리는 통지 코드나 출력용 매개 변수를 받아서 통지하는 것이 좋습니다. 저수준 코드에서는 C#의 try-catch 핸들러를 알지 못하기 때문입니다. 모두 네 가지 경우의 수가 존재합니다. 각각 살펴보도록 하겠습니다. 5.1. 만약 m_myValue가 public: 영역에 있으면서 일반 멤버 변수인 경우 getter와 setter 함수 구현 int TestClass_public_GetMyValue(TestClass* pInstance) { if(pInstance != NULL) return pInstance->m_myValue;
else return 0; } int TestClass_public_SetMyValue(TestClass* pInstance, int value) { if(pInstance != NULL) { pInstance->m_myValue = value; return 1; // 성공했음을 알리는 표현 } return 0; // 액세스에 실패했음을 알리는 표현 } 5.2. 만약 m_myValue가 public: 영역에 있으면서 정적 멤버 변수인 경우 getter와 setter 함수 구현 int TestClass_public_static_GetMyValue() { return TestClass::m_myValue; } // 정적 멤버 변수는 특별한 상황이 아닌 이상 실패하는 경우는 없습니다. // 굳이 정확성을 추구하자면 C++의 Structured Exception Handling을 사용해도 좋지만 // 복잡한 로직을 사용하지 않을 경우 성능상의 손해가 있을 수 있습니다. void TestClass_public_static_SetMyValue(int value) { TestClass::m_myValue = value; } 5.3. 만약 m_myValue가 public:이 아닌 영역에 있으면서 일반 멤버 변수인 경우 getter와 setter 함수 구현 (단, 클래스의 멤버 함수에 있는 getter와 setter 함수는 public: 이어야 합니다.) int TestClass_private_GetMyValue() { if(pInstance != NULL) return pInstance->GetMyValue(); else return 0; } int TestClass_private_SetMyValue(int value) { if(pInstance != NULL) { pInstance->SetMyValue(value); return 1; // 성공했음을 알리는 표현 } return 0; // 액세스에 실패했음을 알리는 표현 } 5.4. 만약 m_myValue가 public:이 아닌 영역에 있으면서 정적 멤버 변수인 경우 getter와 setter 함수 구현 (단, 클래스의 멤버 함수에 있는 getter와 setter 함수는 public: 이면서 정적 멤버 함수여야 합니다.) int TestClass_private_static_GetMyValue() { return TestClass::GetMyValue(); } void TestClass_private_static_SetMyValue(int value) { TestClass::SetMyValue(value); } 오늘 시간에는 세 가지 사항을 살펴봤습니다. 다음 시간에는 마지막으로 C#과의 연동 방법에 관하여 살펴보도록 하겠습니다. 오늘 시간에 강의한 내용이 프록시 함수를 설계하는 패턴의 전부는 아닙니다. 얼마든지 새로운 방식을 사용하여 입맛에 맞게 고치실 수 있음을 인지하시고 활용해 주십시오. 그리고 이 프록시 함수는 포인터를 활용해서 구현된다는 점을 잊으시면 안됩니다. 긴 강좌를 봐주셔서 감사합니다. 행복한 하루 되세요. ^^ 추신: Windows API의 Interop 코드를 DB화한 사이트가 하나 있습니다. Windows에서 MS .NET이나 Mono로 프로그래밍하시는 분들은 참고하세요. ^^ http://www.pinvoke.net wxWindows의 .NET용 포팅인 wx.NET의 소스 코드를 살펴보다가 재미있는 토픽을 한 가지 발견해서 글을 써올립니다. C#은 System.Interop.DllImportAttribute 라는 속성을 사용해서 MS .NET에서는 DLL을, Mono의 경우 Win32 버전에서는 DLL을, Linux, Unix, Mac OS X 등의 OS에서는 Dynamic Library와의 Interop을 가능하게 해줍니다. Interop 할 수 있는 대상은 라이브러리 외부로 노출된 C 언어 스타일의 함수입니다. 하지만 C++은 C#에서 직접적으로 Interop을 성립시킬 수 없는데, 운영 체제와 컴파일러에 따라서 C++ 클래스를 참조하는 방식이 제각기 다르기 때문입니다. 지금 소개하는 방법은 매우 간단하면서도 생각보다 잘 작동하는 것 같습니다. 바로 C++의 멤버 메서드와 멤버 변수, 생성자와 소멸자에 대한 프록시 함수를 작성하는 방법입니다. 1. 시작하기에 앞서 간단한 C++ 클래스 정의하기 // TestClass.h에 정의하는 내용입니다. class TestClass { public: void TestFunction(int value); int MyFunction(void); private: int m_myValue; public: int GetMyValue(void); void SetMyValue(int value); public: TestClass(int value); } // TestClass.cpp에 정의하는 내용입니다. #include <iostream> void TestClass::TestFunction(int value) { stdout << value; } int TestClass::MyFunction(void) { return 123; } int TestClass::GetMyValue(void) { return this->m_myValue; } void TestClass::SetMyValue(int value) { this->m_myValue = value; } TestClass::TestClass(int value) { SetMyValue(value); } 2. 생성자와 소멸자에 대한 프록시 함수 만들기 프록시 함수에서 생성자와 소멸자의 정의는 반드시 이루어져야 합니다. 모든 프록시 함수는 클래스의 인스턴스를 레퍼런스 형식으로 참조해야 하기 때문에 레퍼런스를 할당하고 제거하는 일을 하는 함수가 있어야합니다. C#에서는 이러한 일을 하기 위해서 복잡한 시스템 API (예를 들자면 malloc()과 free()와 유사한) 를 다시 참조해야 하므로 C++에서 대신 처리하는 것이 바람직합니다. 또한 생성자는 C++ 클래스를 바인딩하는 C# 클래스의 생성자에서 호출되어야 하며 소멸자는 System.IDisposable 인터페이스의 구현 메서드인 Dispose() 메서드 또는 C# 클래스의 소멸자에서 호출되어야 합니다. 프록시 함수는 라이브러리 외부로 노출되어야 하는 함수입니다. Win32에서는 DLL로 제작해야 하며 이 때에는 .def 파일을 선언하여 컴파일러에게 전달하거나 Visual C++ 컴파일러를 사용한다면 extern "C" __declspec(dllexport) 키워드를 사용해도 됩니다. 생성자 프록시 함수는 매개 변수로는 생성자의 매개 변수 그대로를 받습니다. 반환값은 포인터로 반환하면 됩니다. 만약 여러개의 생성자가 정의된 클래스라면 프록시 함수도 이름을 달리해서 여러개로 정의해주면 됩니다. TestClass* TestClass_ctor(int value) // Creator { return new TestClass(value); } 소멸자 프록시 함수는 다른 매개 변수는 받을 필요 없이 포인터 매개 변수 하나만 받습니다. 단, 메모리 액세스 위반을 방지하기 위하여 받은 인스턴스가 이미 정리된 인스턴스인지 확인하는 단계를 거쳐서 delete 연산자로 소멸시키면 됩니다. void TestClass_dtor(TestClass* pInstance) // Destructor { if(pInstance != NULL) delete pInstance;
pInstance = NULL; } 일단 여기까지 글을 올립니다. 다음 글에서 멤버 함수와 멤버 변수에 관한 프록시를 만드는 방법을 소개하도록 하겠습니다. * 곁들여서 살펴보면 좋은 자료: http://www.jprl.com/~jon/interop.html * 추신1: Visual C++ 컴파일러에서 클래스에 대해 __declspec(dllexport)를 사용하거나 MFC의 확장 매크로를 사용해도 이 강좌와 비슷한 디자인의 프록시 함수를 생성합니다. 하지만 Visual C++에 종속적인 내용이므로 다른 컴파일러에서는 사용할 수 없는 방법입니다. 참고해 주십시오. * 추신2: 또한 추신1과 같은 방법으로 생성된 프록시 함수들은 컴파일러가 임의로 이름을 붙이기 때문에 가독성은 물론 식별 가능성 또한 0%입니다. 그리고 이러한 함수를 찾기 위해서는 Dependency Walker라는 Visual Studio의 부가 도구를 사용해야 합니다. (Visual Studio Command Line에서 depends.exe 실행) C/C++ 구조체와 함수를 C# 에서 사용하기 기존의 C/C++ 의 구조체와 함수들을 C# 의 클래스로 캡슐화하는 작업을 설명드립니다. 먼저, C/C++ 에서 DLL 프로젝트를 생성합니다. (프로젝트 이름을 'BND.Native' 라고 하면 출력물은 'BND.Native.dll' 이 됩니다.) 1단계: C/C++ 구조체 정의 (MD5.h) typedef struct mD5Context { UINT state[4]; /* state (ABCD) */ UINT count[2]; /* number of bits, modulo 2^64 (lsb first) */ BYTE buffer[64]; /* input buffer */ } MD5Context; 구조체 내부에 고정 크기의 배열이 정의되어 있습니다. 2단계: C/C++ 함수 정의 (MD5.cpp) void MD5Init(MD5Context * context) { ... } void MD5Update(MD5Context * context, BYTE * input, UINT input_size) { ... } void MD5Final(MD5Context * context, BYTE * output_digest) { ... } C/C++ 언어답게(?) 포인터를 마구 사용하고 있습니다. 3단계: C/C++ 함수 선언 (MD5.h) extern "C" __declspec(dllexport) void MD5Init(MD5Context * context); extern "C" __declspec(dllexport) void MD5Update(MD5Context * context, BYTE * input, UINT input_size); extern "C" __declspec(dllexport) void MD5Final(MD5Context * context, BYTE * output_digest); 함수들을 DLL 외부로 노출합니다. 이를 위해서 #include <Windows.h> 라인을 추가해야 합니다. 이상의 3단계를 작업한 후 컴파일하여 C/C++ DLL 을 생성합니다. 다음으로 C# 프로젝트를 생성합니다. C# 프로그램에서 C/C++ DLL 을 참조하기 위해 [C# 프로젝트의 속성] - [빌드 이벤트] - [빌드 후 이벤트 명령줄] 에 다음을 추가합니다. COPY /Y "$(SolutionDir)BND.Native$(ConfigurationName)BND.Native.dll" "$(TargetDir)BND.Native.dll" C/C++ DLL 파일을 C# 프로젝트의 출력 디렉토리로 복사해 오는 것입니다. 이를 위해서 프로젝트 '종속성'에 'BND.Native' 프로젝트를 추가해야 합니다. 4단계: 구조체 캡슐화 public class MD5Calculator { // C/C++ 의 구조체와 동일한 형태가 되도록 정의합니다. public struct MD5Context { [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] public UInt32[] state; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)] public UInt32[] count; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 64)] public byte[] buffer; } // 클래스 내부에 구조체 인스턴스를 캡슐화합니다. private MD5Context mContext = new MD5Context(); // 구조체를 초기화하는 코드를 생성자 내부에 작성합니다. public MD5Calculator() { /* 따로 new 하지 않아도 이미 공간이 확보되어 있음. mContext.state = new UInt32[4]; mContext.count = new UInt32[2]; mContext.buffer = new byte[64]; */ } C/C++ 에서 정의한 구조체와 동일한 구조체를 정의하고, 그 인스턴스를 정의하고, 초기화 코드를 작성하였습니다. 이 작업들을 편의상 하나의 클래스(MD5Calculator) 내에 캡슐화하였습니다. 5단계: 함수 참조 선언 // C언어 함수들 [DllImport("BND.Native.dll")] extern public static void MD5Init(ref MD5Context ctx); [DllImport("BND.Native.dll")] extern public static void MD5Update(ref MD5Context ctx, byte[] input, int input_size); [DllImport("BND.Native.dll")] extern public static void MD5Final(ref MD5Context ctx, byte[] output_digest); 각종 포인터 파라미터가 위와 같이 매핑됩니다. 6단계: 함수 캡슐화 public void Initial() { MD5Init(ref mContext); } public void Update(byte[] input, int input_size) { MD5Update(ref mContext, input, input_size); } public byte[] Final() { byte[] output_digest = new byte[16]; MD5Final(ref mContext, output_digest); return output_digest; } 구조체를 캡슐화 했듯이, 함수들도 캡슐화해 줍니다. 혹시 저와 같은 고민을 하시는 분들이 보시고 조금이라도 도움이 되기를 바라며 1주일 동안 고생하며 스스로 찾아낸 답들을 올려보겠습니다. << C++ 멤버 메쏘드 >> void CalOutputs(float* ivec); float* GetOutput(void); void GetOutput(float* ovec); void TeachOnce(float* ivec, float* ovec, float Rate, float momentum); 위의 함수들은 예전에 작성된 C++ 클래스의 멤버 메쏘드입니다. 모두 포인터 배열을 넘기고 받기 때문에 C#에서는 그대로 사용하기 어려운 면이 있어 C# wrapper class를 만들고 다음과 같이 메쏘드를 wrapping 했습니다. [[ 배열 넘기기 ]] public void CalOutputs(float[] ivec) { unsafe { fixed(float* _ivec = ivec) { this.bpNet.CalOutputs(_ivec); } } } [[ 배열 리턴 값 받기 ]] 특히 이 부분이 어려웠는데, 배열을 바로 리턴 받을 방법은 없고 그렇다고 마셜링을 한다든가 하면 퍼포먼스에 문제가 있을 거 같아서 원본 C++ 클래스에는 float* GetOutput(void); 으로 되어 있던 메쏘드를 void GetOutput(float* ovec); 로 추가하여 wrapping 했습니다. 만일 소스코드를 바로 다룰 수가 없는 DLL 상태라든가 하면 DLL 과 C# 사이에 이 wrapping 해주는 메쏘드를 포함하는 C++ wrapper 클래스를 추가해주면 되겠죠. public float[] GetOutput() { float[] result = new float[LENGTH]; unsafe { fixed(float* _result = result) { this.bpNet.GetOutput(_result); } } return result; } [[ 같은 타입의 배열을 두 개 이상 넘길 때 ]] public void TeachOnce(float[] ivec, float[] ovec, float Rate, float momentum) { unsafe { fixed(float* _ivec = ivec, _ovec = ovec) { this.bpNet.TeachOnce(_ivec, _ovec, Rate, momentum); } } } ![]() c#으로 구현한 Ogre 일단은 잘 되는것으로 확인 휴~ 갈길이 멀구나 ㅎㅎ
// OgreMFCBase.h
#pragma once #ifndef __OGREMFCBASE_H__ #define __OGREMFCBASE_H__ #include "Ogre.h" #include "OgreErrorDialog.h" #include "OgreEventListeners.h" #if OGRE_DEBUG_MEMORY_MANAGER != 0 #error "MFC에서 Ogre를 사용하기 위하여, OgreConfig.h파일의 OGRE_DEBUG_MEMORY_MANAGER는 0으로 설정되어야 합니다." #endif #endif//__OGREMFCBASE_H__ //OgreMFCBase.cpp #include "stdafx.h" #include "OgreMFCBase.h" #pragma message("메모리 릭이 Detect된다면, 정적 라이브러리 포함으로 빌드하셔야 합니다.") http://blog.naver.com/xtar/70001566500 (출처 네이버 오우거 카페 주바뤼님 블로그) 보통 Material을 만들기 위해서는 jpg나 bmp같은 파일을 이용합니다만, 간단한 이미지를 코드에서 생성하거나 또는 데이터 파일에서 읽은 값을 가지고 Material을 생성시키는 간단한 방법을 소개합니다. 여기서는 메모리에 생성한 데이터를 사용해서 텍스처를 생성하고 Material에 적용하기까지의 과정을 정리했습니다. void BuildMaterial(const String &matName) // 임의의 배열을 생성한다. Image BaseImage; // Material을 create()하고, (matName, ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME); void BuildImage(uchar* data, const int size, const int nChannel) { // 여기서 data에 원하는 값을 채워 넣는다. } 또 다른 방법은 Ogre Wiki에서 Creating Dynamic Texture 참조
http://blog.naver.com/xtar/70001566500 (출처 네이버 오우거 카페 주바뤼님 블로그)
1. 소스 코드에서 직접 매터리얼 생성하기
ResourcePtr res = MaterialManager::getSingleton().create("SolidMaterial", // diffuse 값은 선택적임. 2. material 스크립트 파일에서 생성하기 위의 소스 코딩은 스크립트 파일에서 아래와 같이 선언하는 것과 동일하다 material SolidMaterial 중요한 것은 * 매터리얼이 의미를 가지려면 Light를 최소한 1개 이상 설정해주어야 한다. * 테스트해보니 diffuse는 없어도 되고, ambient값은 반드시 있어야 한다.
|
카테고리
이전블로그
이글루링크
EBC (Egloos Broad..
웅이- 출판과 가치 있는 삶 올리버네 냥이(모블로깅) 이스 Sleepless beauty 나른한 검문소~ 휴식. 앙팡짱의 교육육아 이야기 패션에디터 박소영의 패.. lifestylist 최근 등록된 덧글
It%20is%20a%20parado..
by Jorden at 12/27 우아아아 너무 열심히 .. by 병사_A at 04/03 놀러 왔습니다 ^_^ .. by 병사_A at 02/15 |