개요
C# Application에서 C++ Dll을 호출하기 위해서는 마샬링 과정이 필수적이다. 또한, 마샬링 과정에서 Calling Convention이 조율되지 않으면 스택에서 오류가 발생하는데, 어떻게 이러한 문제가 발생하는 것인지 알아보자.
목표
- 마샬링이 무엇인지 설명할 수 있다.
- Calling Convention이 무엇인지 설명할 수 있다.
C# Application과 C++ Dll의 통신
마샬링(Marshalling)
마샬링이란 한 객체의 메모리에서의 표현방식을 저장 또한 전송에 적합한 다른 데이터 형식으로 변환하는 과정입니다.
이는 데이터를 서로 다른 프로그램간에 전달할 필요가 있을 경우 사용합니다.
즉, 이는 직렬화와 유사하며 직렬화된 한 객체로, 멀리 떨어진 객체와 통신하기 위해 사용합니다.
이렇듯 복잡한 통신을 단순화하여 쉽게 데이터를 주고받을 수 있도록 해주는 것이 마샬링 입니다.
따라서, Managed Language인 C#과, Unmanaged Language인 C++ 프로그램 간 통신을 위해서는, 어떤 타입으로 데이터를 송수신할 지 마샬링을 통해서 조율할 수 있는 것이다.
마샬링과 직렬화는 무엇이 다른가?
마샬링은 객체의 메모리 구조에서 저장 또는 전송에 적합한 다른 데이터 형식으로 변환하는 과정
- 마샬링은 프로그램간 이동할 때 사용하는 변환 과정
→ 이 과정에서 비마샬링할 때 객체의 복사본을 얻을 수 있게 원격 객체의 상태와 코드베이스를 기록한다.
직렬화는 객체의 상태를 저장하기 위해 객체를 바이트 스트림(Byte Stream) 형태로 변환하는 것
- 객체에 저장된 데이터를 스트림 형태로 쓰기위해 연속적인 데이터로 변환하는 것이다
마샬링은 직렬화보다 더 큰 개념으로 사용된다. 그래서 직렬화 가능하거나 리모트 가능한 모든 객체는 마샬링이 가능하다.
→ 큰 차이는 직렬화는 객체가 대상이고 마샬링은 변환 그 자체의 목적으로 코드베이스를 기록하는데 차이가 있다.
함수 호출 규약이란?
호출 규약은 어떻게 서브루틴이 그들의 호출자(caller)로부터 변수를 받고, 어떻게 결과를 반환하는지에 대한 규약이다.
- 프로그래밍 언어, 플랫폼 마다 각기 다른 호출 규약을 사용한다. (CPU 아키텍쳐 + 운영체제)
- 여러 언어에서 작성한 모듈을 병합할 때, 또는 작성 장소가 다른 언어의 운영 체제 및 라이브러리 API를 호출할 때 문제를 일으킬 수도 있다
- 호출자(caller)와 피호출자(callee)가 사용하는 호출 규약을 조율하는데 관심을 가져야 한다.
- 단일 프로그래밍 언어를 사용하는 프로그램에서 여러 개의 호출 규약을 사용할 수 있다.
왜 이렇게 다양한 함수 호출 규약이 존재하는가?
왜 이렇게 다양한 함수 호출 규약이 존재하는지 궁금하여 Stack Overflow 문서를 확인하던 중 아래의 답변을 발견했다.
호출 규칙은 수십 년에 걸쳐 다양한 언어와 하드웨어에 맞게 설계되었습니다. 그들은 모두 다른 목표를 가지고 있었습니다. cdecl은 printf에 대한 가변 인수를 지원합니다. stdcall로 인해 코드 생성이 더 작아졌지만 가변 인수는 없었습니다. Fastcall은 구형 시스템에서 하나 또는 두 개의 인수만 사용하여 간단한 함수의 성능을 크게 향상시킬 수 있습니다(그러나 오늘날에는 속도가 거의 향상되지 않습니다). x64가 도입되었을 때 적어도 Windows에서는 단일 호출 규칙을 갖도록 설계되었습니다.
즉, 언어와 하드웨어의 발전 및 다른 목표를 위하여 여러개의 Calling Convention(cdecl, stdcall, fastcall…)이 설계되었고, 현대의 64bit 운영체제에서는 단일 호출 규칙을 사용한다고 한다.
그렇다면 현재 사용되고 있는 단일 호출 규약은 무엇일까?
64bit 기본 함수 호출 규약
MS 공식 문서의 x64 calling convention 페이지에 의하면, 현재 기본적으로 사용되는 함수 호출 규약은 __vectorcall 이라는 호출 규약을 사용하는 것으로 보인다. 또한, __vectorcall 문서에 의하면 이 __vectorcall의 경우 __fastcall 호출 규약을 베이스로 만들어진 것으로 보인다.
<aside> 💡 The __vectorcall calling convention specifies that arguments to functions are to be passed in registers, when possible. __vectorcall uses more registers for arguments than [__fastcall](<https://learn.microsoft.com/en-us/cpp/cpp/fastcall?view=msvc-170>) or the default x64 calling convention use.
</aside>
마무리
마샬링과정에서 함수 호출 규약을 조율하지 않는다면 호출자와 피호출자간의 스택 정리 방법이 달라서 문제가 발생할 수 있다. 예를들어 호출자는 __stdcall 호출 규약을 사용하고, 피호출자는 __cdecl 호출 규약을 사용한다면, 호출자도 스택 관리에 관여하고, 피 호출자도 스택 관리에 관여하기 때문에 문제가 발생할 수 밖에 없는 것이다.
Reference
https://hwanine.github.io/network/Marshalling/
https://velog.io/@wlsrhkd4023/CS-마샬링Marshalling
https://stackoverflow.com/questions/3428332/why-are-there-so-many-different-calling-conventions
https://learn.microsoft.com/en-us/cpp/build/x64-calling-convention?view=msvc-170
https://learn.microsoft.com/en-us/cpp/cpp/vectorcall?view=msvc-170
'TIL' 카테고리의 다른 글
SPOPARTY 서비스 파일 업로드, 삭제 장애 대응 (1) | 2024.03.02 |
---|---|
Jenkins Container 접속 http 경로 변경하기 (0) | 2024.02.28 |
[키워드 정리] VNC VS RDP, ICMP (0) | 2022.03.09 |
JavaScript ES6 문법 정리 (0) | 2021.10.17 |
2021.09.11 (0) | 2021.09.11 |