Cloudtree references class를 약 3년만에 새버전으로 갱신합니다.
이전 포스팅된 버전이 v1.0.2인데 왜 하나 건너뛰어 v1.0.4로 건너뛰었냐 물으신다면,
v1.0.3을 만들어놓고 포스팅하기도 전에 상당한 기간의 상주 개발에 투입되는 바람에... ㅠㅠ
게다가 v1.0.3을 프로젝트에 쓰면서 사업에 맞게 약간씩 수정하다보니, 배포하기엔 코드가 너저분해진 덕분에 이번 배포 버전은 v1.0.4가 되었네요.

각설하고, 이번엔 CPP와 C# 양쪽의 레퍼런스 양식을 최대한 통일하고, 어느 한쪽에만 있던 기능을 양쪽에 상호 이식하였습니다.
물론 이번에도 최종 릴리즈 DLL과 해당 DLL을 적용한 예제 어플리케이션이 포함된 프로젝트를 함께 업로드합니다.

 

 

Cloudtree erences v1.0.4
수학 유클리딘 거리 계산 2차원 EuclideanDistance(src_x, src_y, dest_x, dest_y)
3차원 EuclideanDistance(src_x, src_y, src_z, dest_x, dest_y, dest_z)
가우시안 랜덤 값 도출 Gaussian(mean, stdev)
Radian↔Degree 변환 Deg2Rad(deg)
Rad2Deg(rad)
좌표계 위경고도를 이용한
방위각,  고각 계산
LosAzimuthP1toP2(P1_lat, P1_lon, P2_lat, P2_lon)
LosElevationP1toP2(P1_lat, P1_lon, P1_alt, P2_lat, P2_lon, P2_alt)
위경고도를 이용한
두지점 사이의 거리
LatLonDistance(P1_Lat_Deg, P1_Lon_Deg, P2_Lat_Deg, P2_Lon_Deg)
LatLonAltDistance(P1_Lat_Deg, P1_Lon_Deg, P1_Alt_m, P2_Lat_Deg,
 P2_Lon_Deg, P2_Alt_m)
Geocentric(ECEF) ↔ Geodetic(LLA) 변환 Geoc2Geod(x, y, z, lat, lon, alt)
Geod2Geoc(lat, lon, alt, x, y, z)
시간 시간 경과 체크 GetDuration(start_time)
공유
메모리
공유메모리 할당과 해제,
쓰기와 읽기
CreateSharedMemory(SharedMemKey, BufferSize)
CloseSharedMem()
WriteSharedMemory(data)
ReadSharedMemory(data)
보간곡선 보간곡선생성 베지어 곡선 생성 getBezier2DPoList(pos, PosOnCurve, result)
캣멀롬 곡선 생성 getCatmullRom2DPoList(pos, result)
통신 TCP/IP서버 서버 생성 CreateTcpServer(IP, PORT, MaxDatumLength)
서버 연결 ConnectTcpServer()
클라이언트로 데이터 전송 SendDataTcpServer(sendData, length)
서버 종료 DisconnectTcpServer()
TCP/IP 클라이언트 클라이언트 생성 CreateTcpClient(IP, PORT, MaxDatumLength)
클라이언트 연결 ConnectTcpClient()
서버로 데이터 전송 SendDataTcpClient(sendData, length)
클라이언트 종료 DisconnectTcpClient()
UDP 멀티캐스트 멀티캐스트 생성 CreateUdpMulticast(IP, PORT, MaxDatumLength, Purpose)
멀티캐스트 Join ConnectUdpMulticast()
멀티캐스트로 데이터 전송 SendDataUdpMulticast(sendData, length)
멀티캐스트 종료 DisconnectUdpMulticast()
UDP 유니캐스트 유니캐스트 생성 CreateUdpUnicast(myIP, myPORT, DestIP, DestPort, MaxDatumLength)
유니캐스트 연결 ConnectUdpUnicast()
유니캐스트 데이터 전송 SendDataUdpUnicast(sendData, length)
유니캐스트 종료 DisconnectUdpUnicast()

 

하기 첨부된 레퍼런스 클래스 예제 어플리케이션 프로젝트를 열어보시면 사용법은 쉽게 아실 수 있으리라 생각됩니다.

 

 

이번 레퍼런스 클래스와 예제 어플리케이션은 Microsoft Visual Studio 2019(VC16.0)에서 작성되었으며, C# 어플리케이션의 경우 .Net Frameworks 4.0을 기반으로 작성되었습니다.

1.HLA (High Level Architecture)란?
 - 상이한 조직에서 상이한 목적으로 개발된 개별 시뮬레이션들을 하나의 커다란 시뮬레이션으로

  결합시켜주는 SW구조
 - 재사용성과 상호 운용성 유지를 위한 미국방성의 표준규격 & IEEE 표준 규격
 - Federate : Federation 구성을 위해 결합된 개별 시뮬레이션 시스템
 - Federation : Federate들이 결합된 시뮬레이션 시스템

2. Federation의 구성요소
 - 지원 SW : RunTime Ifrastructure (RTI)
 - 하나의 Federation에서 각 Federate들 사이의 데이터 교환을 위한 공통객체모델

  (FOM : Federation Object Model)
 - Federation에 가입(Join)된 다수의 Federate

3. Federation Object Model (FOM)
 - Federation 개발자에 의해 생성되는 자료
 - 하나의 Federation Execution에서 각 Federate간 교환하는 데이터의 종류 및 관계성에 대해 정의

4. 기능 분산 구현
 - Federate는 모델 또는 시뮬레이션 관련 행위를 구현
 - RTI는 시뮬레이션의 상호 운용성 관련 모든 기능을 구현


 4.1. RTI Ambassador
  - RTI가 개별 Federate에게 제공해주는 인터페이스
  - Federate는 RTI 서비스를 요청하기 위해 RTI Ambassador operation을 호출

 4.2. Federate Ambassador
  - Feserate가 RTI에게 제공해주는 인터페이스
  - RTI는 Feserate에게 정보를 전달하기 위해 Federate Ambassador operation을 호출

5. Federation 규칙
 - Federation은 OMT(Object Model Template)에 의해 작성된 하나의 HLA FOM을 갖는다.
 - FOM에 있는 모든 객체들의 표현은 RTI가 아닌 Federate에 있어야 한다.
 - Federation이 실행되는 동안 Federate간 모든 FOM 자료의 교환은 RTI를 통해 이루어진다.
 - Federation이 실행되는 동안 Federate는 HLA 인터페이스 명세에 따라 RTI와 상호작용(Interaction) 한다.
 - Federation이 실행되는 동안 객체 인스턴스 속성의 소유권은 단 하나의 Federate가 갖는다.


6. Federate 규칙
 - Federate는 OMT에 의해 작성된 하나의 SOM(Simulation Object Model)을 갖는다.
 - Federate는 자신의 SOM에 있는 객체의 속성을 갱신하거나 반영할 수 있으며, 상호작용을 외부적으로

  보내거나 받을 수 있다.
 - Federate는 자신의 SOM에 명시된대로 Federation이 실행되는 동안 속성의 소유권을 동적으로 주고 받을수 있다.
 - Federate는 자신의 SOM에 명시된대로 객체 속성의 갱신을 제공하는 조건을 변경할 수 있다.
 - Federate는 Federation의 다른 멤버들과 데이터 교환을 가능하게 하기 위해 자신의 Local Time을 관리할 수 있다.

7. Federation 실행 시퀀스
 1) RTIExec.exe 실행 : RTI 시작
 2) Federate.exe 실행 : Federate 시작
 3) Federation 생성 및 가입 : CreateFederationExecution() / JoinFederationExecution()
 4) 그 외 나머지 Federate들의 Federation 가입 : JoinFederationExecution() x n
 5) 시간 스위치 설정 (Regulating / Constrained)
 6) Publish / Subscribe 선언
 7) 객체 생성, 등록 및 갱신
 ============== 시뮬레이션 반복 루프 ==============
 8) 시뮬레이션 시간진행 요청
 9) 외부사건 처리 : Subscribe로 수신한 인스턴스 내용 시뮬레이션에 반영
 10) 내부사건 처리 : Publish한 인스턴스 정보 갱신
 ============== 시뮬레이션 반복 루프 ==============
 11) Federation 탈퇴 요청
 12) 시뮬레이션 실행 종료

8. Publish와 Subscribe
 - Publish : 하나의 Federate가 객체 인스턴스를 등록하여 해당 인스턴스의 속성을 갱신하고 상호작용을 전송
 - Subscribe : Publish된 객체 인스턴스를 구독하여 해당 인스턴스의 갱신된 속성값을 반영하며 상호작용을 수신

오랜만에 Cloudtree reference class를 업데이트 하여 관련 포스팅을 합니다.

이전 버전의 레퍼런스 1.0.1 버전을 여러 사업에서 적용해 사용하기도 했고, 직장 후배에게 공유해주어 사용하기도 했는데요.

TCP 통신의 사용 방법이 복잡하다는 피드백을 두어번 받았습니다.

 

그렇다면 수정을 다시 한번 더 해야겠죠.

 

레퍼런스 클래스의 복잡했던 통신 관련 구조를 싹 갱신해버렸습니다.

초보자가 적용하려 해도 사용방법이 쉽도록 최대한 간결하게 바꾸어 보았습니다.

 

Cloudtree References v1.0.2

수학 함수

유클리딘 거리 함수

2차원

  EuclideanDistance(x1, y1, x2, y2)

3차원

  EuclideanDistance(x1, y1, z1, x2, y2, z2)

가우시한 랜덤 함수

  Gaussian(mean, stdev)

좌표계

위경도 두 점간 시선각

  bearingP1toP2(p1_Lat, p1_Lon, p2_Lat, p2_Lon)

위경도 두 점간 거리

  LatLonDistance(p1_Lat, p1_Lon, p2_Lat, p2_Lon)

ECEF to LLA(위경고도)

  ECEF2LatLonAlt(x, y, z, result_Lat, result_Lon, result_Alt)

시간

클럭 타이머 시간 경과

  GetDuration(DateTime start_time)

공유메모리

공유메모리 생성

  CreateSharedMemory(SharedMemKey)

공유메모리 해제

  CloseSharedMem()

공유메모리 쓰기

  WriteSharedMemory(SharedMemKey, ref data)

공유메모리 읽기

  ReadSharedMemory(SharedMemKey, ref data)

곡선 생성

베지어 곡선

  getBezier2DPointList(ref points, int point_of_curves, ref result)

캣멀롬 곡선

  getCatmullRom2DPointList(ref points, ref result)

통신

TCP 서버

  Netxxxxxxx(string ip, int port); // 생성자

  SetMaxDataLength(int length); // 송수신 패킷 최대 크기 설정

  Connect(); // 소켓 생성 및 접속

  OnUpdate += RcvdDataProcess; // 데이터 수신 처리부 델리게이트

  SendData(byte[] data, int length); // 데이터 송신

  DIsconnect(); // 통신 종료

TCP 클라이언트 

UDP 유니캐스트

UDP 멀티캐스트


레퍼런스 클래스 DLL의 public 함수는 위와같이 구성되어 있습니다.

 

이전 게시물을 보시거나 아래 첨부할 레퍼런스 클래스 예제 폼 샘플을 열어보시면 사용법을 쉽게 아실 수 있으리라 생각합니다.

이번 포스팅에서는 이전 버전에서의 변경점에 대해서만 언급하고자 합니다.

 

이전 버전과의 주요 변경점은 TCP 서버/클라이언트, UDP 유니캐스트,  UDP 멀티캐스트 함수가 있습니다.

각 통신 방법에 따른 인스턴스를 선언한 이후로 크게 여섯가지 정도의 함수만으로 각 통신 소켓을 사용할 수 있도록 구성했습니다. 이는 TCP 서버/클라이언트와 UDP 유니캐스트, UDP 멀티캐스트에 일괄 공통 적용 사항입니다.

 

각 통신 방법에 따른 함수를 TCP 서버를 대표 예로 설명드리자면...

 

 

 

1. 인스턴스에 의한 통신 클래스 생성

 

  NetTcpServer m_NetTcpServer = new NetTcpServer(tbTcpServerIP.Text, Convert.ToInt32(tbTcpServerPort.Text)); 

 

생성자에 서버 IP와 사용할 포트를 인자로 전달합니다.

예외적으로 UDP 유니캐스트의 경우 송수신 안정성을 위하여 전송과 수신 소켓을 분리하였으므로, 수신 IP, 수신 포트, 송신 목적지 IP, 송신 목적지 Port 네 개의 인자를 생성자에 전달합니다.

 

 

2. 송수신 패킷의 최대 크기 설정

 

  m_NetTcpServer.SetDataMaxLength(2048); 

 

이전 버전에서는 네트워크 관련 함수에 쓸 Constants 클래스를 두고 이를 활용하였으나, 한 눈에 파악되지 않고 처음 사용자의 경우 소스코드 여기저기를 뒤져봐야 파악이 된다는 지적이 있어 직관적으로 SetDataMaxLength() 함수를 만들었습니다.

 

 

3. 통신 접속

 

  m_NetTcpServer.Connect(); 

 

각 통신 방법에 따른 소켓을 열어 서버라면 바인딩, 클라이언트라면 서버에 접속 등을 수행하는 Connect() 함수입니다.

 

 

4. 데이터 수신 

 

  m_NetTcpServer.OnUpdate += OnUpdate_TcpServer; 

 

이전 버전까지는 데이터 수신부를 클래스 외부에서 각 사용자가 직접 만들어 콜백을 붙이도록 하였으나, 초심자의 경우 콜백 함수를 구성해서 적용하는 로직을 제대로 이해하지 못하는 경우도 많았고, 적지않은 예외 상황으로 결국 레퍼런스 클래스 내부를 건드리는 일도 발생함을 확인하였습니다.

이번 버전에서는 각 통신 방법에서 위 1~3의 과정을 거치면 데이터 수신용 스레드를 자동으로 생성하고, OnUpdate로 델리게이트된 함수에 수신된 byte array를 전달하도록 변경했습니다.

패킷 수신은 신경쓸 필요 없이, 수신된 데이터의 처리부만 사용자가 직접 코딩하면 되겠습니다.

 

 

5. 데이터 전송

 

   m_NetTcpServer.SendData(StrByte, StrByte.Length);

 

각 통신 방법에 따라 TCP는 서버와 클라이언트간 데이터 전송, UDP는 유니캐스트일 경우 목적지에 직접, 멀티캐스트일 경우엔 멀티캐스트 주소에 publish하는 SendData() 함수입니다.

 

 

6. 통신 종료

 

   m_NetTcpServer.Disconnect();

 

각 방법에 따른 통신을 수행 후 통신 종료를 원할 경우 호출하는 Disconnect() 함수입니다.

데이터 수신용 스레드를 종료한 후 각 소켓을 닫아주는 것으로 통신을 종료합니다.

 

 

 

추가적으로, 기존 버전에서는 내 시스템의 IP를 자동으로 읽어올 때 vmWare 등의 가상 IP가 있는 경우, 또는 둘 이상의 네트워크 어댑터가 있는 경우를 상정하지 않았기에 제대로 동작하지 않는 경우가 있었습니다.

따라서 이번 버전에서는 게이트웨이를 통해 패킷이 나가는지를 검사하여 실제 외부로 연결중인 IP를 할당하도록 수정하였습니다.

 

 

 

 

 

 

 

 

 

 

위 예제 프로그램과 레퍼런스 클래스의 소스코드가 포함된 압축파일을 첨부합니다.

 

CloudtreeReference_v1.0.2.zip
다운로드

 

 

 

제가 쓰기 편하려고 만든 레퍼런스 클래스이긴 하지만, 필요하신 분이 계시다면 자유롭게 사용하셔도 무방합니다.

다만, 다른곳으로 공유 링크를 남기시거나 할 경우에는 본 포스팅 댓글에 공유한 주소를 알려주시면 감사하겠습니다.

 

 

차회 갱신은 비동기 TCP 통신이 될것같다고 예고해놓고는, 비동기 통신이 아닌 보간곡선때문에 버전업을 하게되었네요.

최근 완료된 업무에서 필요해서 모듈을 만들어뒀던게 급 생각나서 추가하게 되었습니다.


이번 업데이트에 추가된 내용은 보간 곡선 생성 기능입니다.


제가 사용하는 보간 곡선은 경우에 따라 두가지를 용도에 맞게 적용하는데,

베지어 곡선과 캣멀롬 곡선이 그 두가지입니다.


베지어 곡선은 다음 그림과 같은 방식으로 그려지며, 부드러운 곡선을 만드는데 유용합니다.



< 베지어 4점 곡선 >


하지만 베지어 곡선은 지정한 웨이포인트를 지나지 않으며 최대한 부드럽게 유사 경로로 지나가게 되지요.


캣멀롬 곡선은 베지어 곡선보다 부드러운 곡이 나오진 않지만, 베지어 곡선과의 가장 큰 차이점으로 지정한 웨이포인트를 반드시 지나도록 구성됩니다. 다음 그림과 같이요.


< 캣멀롬 5점 곡선 >


위 두 보간곡선의 기능을 기존 레퍼런스에 추가하였습니다.

추가된 기능을 포함하여 제 개인 레퍼런스 클래스의 기능을 종합해보면 다음 표와 같습니다.


TCP/IP

서버

클라이언트

UDP

UDP

수학함수

Euclidean distance 2D

Euclidean distance 3D

가우시안 랜덤수 생성

좌표계

위경도 방위각 계산

위경도 거리 계산

ECEF-위경도 좌표계 변환

시간경과

시간경과 체크

공유메모리

쓰기

읽기

보간곡선

베지어 곡선

캣멀롬 곡선


레퍼런스 클래스 DLL을 적용한 샘플 윈도우 어플리케이션의 구성중 보간곡선 부분의 GUI만 추가로 설명합니다.

다른 기능의 GUI는 이전글(http://cloudtree.tistory.com/294)을 참고해 주시면 감사하겠습니다.


그리기 영역에 임의의 점을 2개 이상 찍어줍니다.

웨이포인트로써 곡선 생성의 기준이 됩니다.



베지어 곡선을 생성한 모습입니다.

웨이포인트를 최대한 부드럽게 접근하는 곡선이 생성됩니다.



캣멀롬 곡선을 생성한 모습입니다.

지정된 웨이포인트를 반드시 지나치는 최적 경로가 생성됩니다.



이전 글에서의 언급과 마찬가지로 최대한 쉽게 적용할 수 있도록 짜려 노력했으며, C#에 어느정도 익숙하신 분이라면 DLL 적용을 위한 샘플 폼 어플리케이션의 코드만 보셔도 큰 어려움 없이 적용이 가능하시리라 생각합니다.



CloudtreeReference_sample_v1.0.1.zip



소스코드의 편집 및 재배포는 자유롭게 허용되며, 혹시나 레퍼런스 샘플에 치명적 오류가 있을 시 알려주시면 빠르게 수정 반영하도록 하겠습니다.


오랜만에 프로그래밍 카테고리에 게시물을 포스팅하네요 ^^;;


예전에 VC++ 용으로 만들어두었던 레퍼런스 클래스가 있었습니다만, 최근 C# 코딩을 더 많이 하는 관계로

오랜만에 레퍼런스를 재정비 하여 C#용 레퍼런스 클래스를 작성했습니다.


C# 전용 레퍼런스로 넘어오며 가장 큰 변경점이라면...

이전에는 소스코드를 복&붙 하여 쓰도록 해놨던것을 클래스 라이브러리로 변경하여, 앞으로 C# 프로젝트에서 사용 시 DLL 파일만 복&붙 하면 쓸 수 있도록 수정하였습니다.


또한, TCP/IP와 UDP 소켓 통신의 경우 기존 레퍼런스에서는 소스 코드 안에 리시브 스레드를 만들어두었기에 적용할 때 마다 각 프로젝트에 맞도록 레퍼런스 소스 코드를 수정해야 했습니다. 하지만 이는 레퍼런스로서의 의미가 많이 퇴색된다 생각되어 이번  C#용 레퍼런스 클래스에서는 각 리시브 스레드를 레퍼런스 클래스 외부에서 작성하여 붙일 수 있도록 만들었습니다.


그 외에는 딱히 변경이나 추가사항 없이 기존의 레퍼런스와 같습니다.


작업 환경은 .Net Framework 4.0 기반, VS2010 기준으로 작성했으며, 솔루션 파일을 열면 두개의 프로젝트가 있습니다.


1. CloudtreeReference 프로젝트 : dll 파일을 생성하는 레퍼런스 클래스

2. CloudtreeReference_SampleForm 프로젝트 : 레퍼런스 적용 예시 윈도우 폼 어플리케이션



다음은 레퍼런스 클래스에 구현되어 있는 함수 내역입니다.

생각할 수 있는 최대한 쓰기 편하도록 짜보긴 했는데, 쓰면서 불편한 점이 생기면 그때마다 버전업 해 갈 예정입니다.



TCP/IP

서버

클라이언트

UDP

 UDP

수학함수

Euclidean distance 2D

Euclidean distance 3D

가우시안 랜덤수 생성

좌표계

위경도 방위각 계산

위경도 거리 계산

ECEF-위경도 좌표계 변환

시간경과

시간경과 체크 

공유메모리

공유메모리 생성

쓰기

읽기




레퍼런스 클래스 DLL을 적용한 샘플 윈도우 폼 어플리케이션 구성은 다음 그림과 같습니다.










C#에 어느정도 익숙하신 분이라면 코드만 봐도 큰 어려움 없이 적용이 가능하시리라 생각합니다.

원본 소스코드 및 바이너리가 포함된 압축파일을 첨부합니다.



CloudtreeReference_sample_v1.0.0.zip



ps. 차후 업데이트는 TCP 소켓 통신을 현재의 1:1 접속에서 1:n 다중 접속 방식으로 변경하기 위해 비동기 소켓 통신으로 변경하는 작업이 되지 않을까 합니다.






원문 출처 : 당근로리야스님 블로그 (http://blog.danggun.net/799)

 

클래스 마샬링을 할일이 없엇 신경을 안쓰다가 요번에 스카이프 api나 제가 직접 마샬링 해볼까 해서 클래스 마샬링을 정리해 보았습니다.

근데 왜이렇게 자료가 없지?
겨우 찾은것이 비주얼C++ 팀블로그인데....네...영어입니다 ㅡ.-;
(참고 : Visual C++ Team Blog - Inheriting From a Native C++ Class in C#)

일단 변환방법이 마음에 들지가 않아서 위글에 있는 내용을 그대로 사용하여 만들고 자료를 더 찾는다면 파트2로 돌아오 겠습니다 ㅎㅎㅎㅎ
그전에 이 글은 크게 2부분으로 나누어 설명할 예정입니다.

어찌됬건 프로그래머라면 일단 샘플부터 만들고 생각해야 하지 않겠습니까?

 
1. C++ DLL 만들기

C#에서 노출 시킬 클래스가 담겨있는 C++ DLL을 만들겠습니다.

1-1.프로젝트 생성

프로젝트 이름은 꼭 "cppexp"로 합니다.
진입점 문제 때문인데 해결방법은 있으나....좀더 편한 테스트 환경을 위해 "cppexp"로 합니다 ㅎㅎㅎ



1-2.코드 넣기(CSimpleClass.h)

클래스 이름도 "CSimpleClass"로 바꿔 줍니다.
샘플 코드와 마찬가지로 저도 해더에 모든  코드를 넣습니다.

아래 코드는 전체 코드입니다-_-;
잘 모고 따라하시길 ㅎㅎㅎ

// cppexp.h
#include <stdo.h>
#pragma once
 
using namespace System;
 
class __declspec(dllexport) CSimpleClass {
public:
      int value;
      CSimpleClass(int value) : value(value)
      {
      }
      ~CSimpleClass()
      {
            printf("~CSimpleClass\n");
      }
      void M1()
      {
            printf("C++/CSimpleClass::M1()\n");
            V0();
            V1(value);
            V2();
      }
      virtual void V0()
      {
            printf("C++/CSimpleClass::V0()\n");
      }
      virtual void V1(int x)
      {
            printf("C++/CSimpleClass::V1(%d)\n", x);
      }
      virtual void V2()
      {
            printf("C++/CSimpleClass::V2()\n", value);
      }
};

빌드를 돌려보면 별다른 문제 없이 dll이 생성됩니다.

여기서 생성된 "cppexp.dll", "cppexp.lib" 이 두개 파일이 중요합니다.

2. C# 샘플 만들기
이 샘플코드를 사용하기 위해서는 C#쪽에서 해야할 작업이 많습니다.
프로젝트는 아무것이나 상관없으나 이샘플자체가 콘솔응용프로그램이므로 'C# 콘솔 응용프로그램'으로 프로젝트를 생성합니다.

2-1. CSimpleClass 클래스 생성
cppexp.dll를 직접 핸들릴할 클래스입니다.

아래코드는 클래스의 전체 코드입니다.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
 
namespace ConsoleApplication1
{
    [StructLayout(LayoutKind.Sequential, Pack = 4)]
    public unsafe struct __CSimpleClass
    {
        public IntPtr* _vtable;
        public int value;
    }
 
    public unsafe class CSimpleClass : IDisposable
    {
        private __CSimpleClass* _cpp;
 
        // CSimpleClass constructor and destructor
        [DllImport("cppexp.dll", EntryPoint = "??0CSimpleClass@@QAE@H@Z", CallingConvention = CallingConvention.ThisCall)]
        private static extern int _CSimpleClass_Constructor(__CSimpleClass* ths, int value);
        [DllImport("cppexp.dll", EntryPoint = "??1CSimpleClass@@QAE@XZ", CallingConvention = CallingConvention.ThisCall)]
        private static extern int _CSimpleClass_Destructor(__CSimpleClass* ths);
        
        //      void M1();
        [DllImport("cppexp.dll", EntryPoint = "?M1@CSimpleClass@@QAEXXZ", CallingConvention = CallingConvention.ThisCall)]
        private static extern void _M1(__CSimpleClass* ths);
 
        public CSimpleClass(int value)
        {
            //Allocate storage for object
            _cpp = (__CSimpleClass*)Memory.Alloc(sizeof(__CSimpleClass));
            //Call constructor
            _CSimpleClass_Constructor(_cpp, value);
        }
        public void Dispose()
        {
            //call destructor
            _CSimpleClass_Destructor(_cpp);
            //release memory
            Memory.Free(_cpp);
            _cpp = null;
        }
        public void M1()
        {
            _M1(_cpp);
        }
    }
}


"??0CSimpleClass@@QAE@H@Z"와 같은 알수 없는 코드들은 코드의 진입점입니다-_-;
이 방법으로 클래스를 마샬링 하기 위해서는 위와같이 진입점 코드가 있어야 합니다.
진입점 코드는 어떻게 찾느냐? 아까 말했던 .lib파일을 메모장으로 열어보시면 나와 있습니다.

0CSimpleClass : 0클래스 이름 이 생성자
1CSimpleClass : 1클래스 이름 이 파괴자
나머지 함수들은 이름으로 검색하면  코드를 알수 있습니다.

이 클래스를 만들면 "Memory.Alloc"와 "Memory.Free" 같은 곳에서 에러가 납니다.
"Memory"라는 클래스가 없기 때문이죠 ㅎㅎㅎ


2-2. Memory 클래스 생성

Memory이 클래스는 내용이 어떤것인가 하고 찾아보았습니다.
C#에서 동적 메모리 할당을 위해 사용하는 클래스 샘플입니다.

닷넷에서는 메모리관리는 특별한 경우를 제외하면 가비지컬랙터가 처리합니다.
이 가바지컬랙터를 거치지 않고 사용할수 있게 하는 샘풀이죠.
말이 샘플이지 이대로 쓰는대 전혀 손색이 없습니다.

참고 : MSDN - A.8 동적 메모리 할당

아래 코드는 클래스 전체 코드 입니다.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
 
namespace ConsoleApplication1
{
    using System;
    using System.Runtime.InteropServices;
    public unsafe class Memory
    {
        // Handle for the process heap. This handle is used in all calls to the
        // HeapXXX APIs in the methods below.
        static int ph = GetProcessHeap();
        // Private instance constructor to prevent instantiation.
        private Memory() { }
        // Allocates a memory block of the given size. The allocated memory is
        // automatically initialized to zero.
        public static void* Alloc(int size)
        {
            void* result = HeapAlloc(ph, HEAP_ZERO_MEMORY, size);
            if (result == null) throw new OutOfMemoryException();
            return result;
        }
        // Copies count bytes from src to dst. The source and destination
        // blocks are permitted to overlap.
        public static void Copy(void* src, void* dst, int count)
        {
            byte* ps = (byte*)src;
            byte* pd = (byte*)dst;
            if (ps > pd)
            {
                for (; count != 0; count--) *pd++ = *ps++;
            }
            else if (ps < pd)
            {
                for (ps += count, pd += count; count != 0; count--) *--pd = *--ps;
            }
        }
        // Frees a memory block.
        public static void Free(void* block)
        {
            if (!HeapFree(ph, 0, block)) throw new InvalidOperationException();
        }
        // Re-allocates a memory block. If the reallocation request is for a
        // larger size, the additional region of memory is automatically
        // initialized to zero.
        public static void* ReAlloc(void* block, int size)
        {
            void* result = HeapReAlloc(ph, HEAP_ZERO_MEMORY, block, size);
            if (result == null) throw new OutOfMemoryException();
            return result;
        }
        // Returns the size of a memory block.
        public static int SizeOf(void* block)
        {
            int result = HeapSize(ph, 0, block);
            if (result == -1) throw new InvalidOperationException();
            return result;
        }
        // Heap API flags
        const int HEAP_ZERO_MEMORY = 0x00000008;
        // Heap API functions
        [DllImport("kernel32")]
        static extern int GetProcessHeap();
        [DllImport("kernel32")]
        static extern void* HeapAlloc(int hHeap, int flags, int size);
        [DllImport("kernel32")]
        static extern bool HeapFree(int hHeap, int flags, void* block);
        [DllImport("kernel32")]
        static extern void* HeapReAlloc(int hHeap, int flags,
           void* block, int size);
        [DllImport("kernel32")]
        static extern int HeapSize(int hHeap, int flags, void* block);
    }
}
코드에 [DllImport("kernel32")]가 있는데 이소리는 커널을 불러다 사용한다는 소리입니다.
운영체제에 따라 작동안할수도 있다는 것입니다 ㅡ.-;;

어찌됬건 이렇게 "Memory"라는 클래스를 만들고 유징하시면 에러는 사라 집니다.

2-3.테스트 코드
이제 준비가 끝났습니다.
2-1.에서 'M1()' 만 마샬링을 해두었으므로 'M1()'만 호출이 가능합니다.

프로그램의 진입점인 'Main()'으로 가서 다음 코드를 추가 합니다.


CSimpleClass sc = new CSimpleClass(10);
using (sc)
{
    //M1 calls all of the virtual functions V0,V1,V2
    sc.M1();
}


3.확인
빌드를 돌리면 
"Unsafe code may only appear if compiling with /unsafe"
이런 에러가 납니다.
비관리 코드를 사용하기위한 옵션을 켜라는 소리입니다 ㅡ.-;;

3-1.비관리 코드 사용 옵션
프로젝트 속성 > 빌드 > 안전하지 않는 코드 허용
옵션을 켜줍니다.



 
3-2. 출력 확인
화면이 순식간에 꺼진다면 알아서 화면 챙기시고 ㅋㅋㅋㅋ(그냥 중단점만 걸어도 됩니다-_-a)


정확하게 출력 되는 것을 볼수 있습니다.

5.마무리
위에 내용들을 정확하게 이해했다면 샘플에 나와있는 다른 클래스 맴버들도 마샬링을 할수 있습니다.
당연한 이야기지만 클래스는 무조건 통으로 마샬링해야 합니다.
안그러면 사용을 할수 없어요.


샘플 프로젝트

 

 Projects.zip