Microsoft MVP성태의 닷넷 이야기
글쓴 사람
홈페이지
첨부 파일

포인터 형 매개 변수를 갖는 C++ DLL의 함수를 C#에서 호출하는 방법

종종 다음과 같은 질문을 보게 되는데요.

C++ dll C#에서 사용하는데 보호된 메모리 오류 떠요 한 번만 도와주세요ㅜ
; https://www.sysnet.pe.kr/3/0/4997

이쯤에서 한번 정리를 해봐야겠습니다. ^^

우선, C/C++ DLL의 함수를 정상적으로 호출하려면 Calling Convention이 맞아야 합니다. 이에 대해서는 지난 글에 잘 정리해 두었으니 참고하시고.

C# 개발자를 위한 Win32 DLL export 함수의 호출 규약 (1) - x86 환경에서의 __cdecl, __stdcall에 대한 Name mangling
; https://www.sysnet.pe.kr/2/0/11132

일단 호출 규약이 맞았다면 이제 매개 변수에 대한 인자 값을 잘 맞춰주는 문제만 남습니다. 이때 C# 개발자들이 어려워하는 것 중의 하나가 바로 C/C++의 포인터를 받는 함수에 대한 interop입니다. 이를 이해하려면 C/C++에서 포인터 변수를 받는 방식에 대한 이해가 필요합니다.

가령 C#에서 다음과 같은 코드를 보겠습니다.

static unsafe void Main(string[] args)
{
    int value = 5;

    PassByValue(value);
    Console.WriteLine(value); // 출력 결과 5

    PassByRef(ref value);
    Console.WriteLine(value); // 출력 결과 50
}

private static void PassByValue(int value)
{
    value = 50;
}

private static void PassByRef(ref int value)
{
    value = 50;
}

저런 코드를 C/C++에서는 다음과 같이 작성할 수 있습니다.

int value = 5;

PassByValue(value);
printf("%d\n", value); // 출력 결과 5

PassByPtr(&value);
printf("%d\n", value); // 출력 결과 50

void PassByValue(int value)
{
    value = 50;
}

void PassByPtr(int *value)
{
    *value = 50;
}

즉, C#의 ref와 같은 역할을 위해 포인터 변수(int *)를 넘겨줌으로써 다룰 수 있게 하는 것입니다. 그런데, 문제는 C/C++의 경우 포인터와 배열의 차이점이 없다는 것입니다. 그래서 다음 코드에서 보는 것처럼 호출 측에서 동일하게 &value 포인터 변수를 전달했지만,

int value = 5;

PassByPtr(&value);
printf("%d\n", value); // 출력 결과 50

PassByArray(&value);
printf("%d\n", value); // 출력 결과 500

void PassByPtr(int *value) // 포인터로도 받을 수 있고,
{
    *value = 50;
}

void PassByArray(int value[]) // 배열로도 받을 수 있음.
{
    value[0] = 500;
}

받는 측의 함수에서는 &value 변수를 int *와, int []로 취향에 맞게 처리할 수 있습니다. 이 때문에, 표면상으로는 포인터를 받는 함수일지라도 내부적으로 그것을 배열로 처리할 수도 있습니다.

int value = 5;

PassByPtr(&value);
printf("%d\n", value); // 출력 결과 500

void PassByPtr(int *value)
{
    value[0] = 500;
}

대개의 경우, 포인터(또는 배열)를 받는 함수가 그것을 내부적으로 배열로 다룬다면 안전하게 접근할 수 있도록 명시적으로 배열의 수를 함께 전달할 수 있게 만듭니다.

int value = 5; // 단일 변수이지만, 
PassByPtr(&value, 1); // 1개의 배열로써 전달

int array[10] = { 0 }; // 10개의 요소를 갖는 배열을,
int *pArray = &array[0];
PassByPtr(pArray, 10); // 명시적으로 10개라고 지정

void PassByPtr(int *value, int len)
{
    for (int i = 0; i < len; i ++)
    {
        value[i] = 500;
    }
}

물론 이것은 해당 함수를 개발하는 프로그래머의 결정에 따르기 때문에 꼭 저런 식으로 개발되었다고 장담할 수 없습니다. 실제로 과거에 많은 수의 C/C++ 함수들 중 문자열 처리의 경우 입력 버퍼의 수를 명시하지 않고 널(\0) 문자를 인식하는 것으로 작성했기 때문에,

char buf[256];
strcpy(buf, "test"); // strcpy 함수는 buf의 배열 크기를 알지 못함

수많은 버퍼 오버런 취약점이 발생하는 원인이 되었습니다. 그래서 나중에는 좀 더 안전한(secure) 함수라고 해서 배열의 크기를 명시하는 버전이 나오게 된 것입니다.

char buf[256];
strcpy_s(buf, 256, "test");




자, 그럼 C/C++ 개발자가 DLL을 만들어 제공하는 함수의 시그니처를 다음과 같이 전달해 줬다고 가정해 보겠습니다.

void ExternC_STD_Func_Ptr(int *value);

C# 개발자는 위의 시그니처를 보면 반드시 C/C++ 개발자에게 물어봐야 합니다. 저 value가 배열이냐? 아니면 순수하게 단일 int 형에 대한 포인터냐? 라고! (물어볼 개발자가 없다면 문서라도 꼼꼼하게 확인해야 합니다.)

만약 단일 int 형에 대한 포인터라면 C#에서 다음과 같이 처리할 수 있습니다.

[DllImport("Win32Project1.dll", SetLastError = true)]
internal static unsafe extern int ExternC_STD_Func_Ptr(int *value);

static unsafe void Main(string[] args)
{
    int value = 5;
    int* pValue = &value;

    ExternC_STD_Func_Ptr(pValue);
}

하지만 배열이라고 한다면, 그 배열의 크기를 묻고 그에 맞게 전달해야 합니다.

[DllImport("Win32Project1.dll", SetLastError = true)]
internal static unsafe extern int ExternC_STD_Func_Ptr(int *value);

static unsafe void Main(string[] args)
{
    int[] array = new int[10];
    fixed (int* pArray = &array[0])
    {
        ExternC_STD_Func_Ptr(pArray);

        for (int i = 0; i < array.Length; i ++)
        {
            Console.Write(array[i] + ",");
        }

        Console.WriteLine();
    }
}

포인터 변수에 대해 위의 규칙 정도만 맞춰주면 AV(Access Violation) 오류로 인한 비정상 종료 문제는 사라질 것입니다.

(첨부 파일은 이 글의 예제 - C#, C/C++ 코드를 포함합니다.)

참고로 COM DLL에 대해서는 다음의 글도 도움이 될 것입니다.

[in, out] 배열을 C#에서 C/C++로 넘기는 방법 - 두 번째 이야기
; https://www.sysnet.pe.kr/2/0/811

[in, out] 배열을 C#에서 C/C++로 넘기는 방법
; https://www.sysnet.pe.kr/2/0/810




[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]

[연관 글]





[최초 등록일: ]
[최종 수정일: 6/8/2018 ]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer@outlook.com

비밀번호

댓글 쓴 사람
 




... 16  [17]  18  19  20  21  22  23  24  25  26  27  28  29  30  ...
NoWriterDateCnt.TitleFile(s)
11823정성태2/21/20191241오류 유형: 518. IIS 관리 콘솔이 뜨지 않는 문제
11822정성태2/20/20191201오류 유형: 517. docker에 설치한 MongoDB 서버로 연결이 안 되는 경우
11821정성태2/20/20191370오류 유형: 516. Visual Studio 2019 - This extension uses deprecated APIs and is at risk of not functioning in a future VS update. [1]
11820정성태2/20/20193879오류 유형: 515. 윈도우 10 1809 업데이트 후 "User Profiles Service" 1534 경고 발생
11819정성태2/20/20191682Windows: 158. 컴퓨터와 사용자의 SID(security identifier) 확인 방법
11818정성태2/20/20191903VS.NET IDE: 131. Visual Studio 2019 Preview의 닷넷 프로젝트 빌드가 20초 이상 걸리는 경우 [2]
11817정성태2/17/20191507오류 유형: 514. WinDbg Preview 실행 오류 - Error : DbgX.dll : WindowsDebugger.WindowsDebuggerException: Could not load dbgeng.dll
11816정성태2/17/20191453Windows: 157. 윈도우 스토어 앱(Microsoft Store App)을 명령행에서 직접 실행하는 방법
11815정성태2/14/20191788오류 유형: 513. Visual Studio 2019 - VSIX 설치 시 "The extension cannot be installed to this product due to prerequisites that cannot be resolved." 오류 발생
11814정성태2/12/20191180오류 유형: 512. VM(가상 머신)의 NT 서비스들이 자동 시작되지 않는 문제
11813정성태2/12/20191485.NET Framework: 809. C# - ("Save File Dialog" 등의) 대화 창에 확장 속성을 보이는 방법
11812정성태1/2/20201087오류 유형: 511. Windows Server 2003 VM 부팅 후 로그인 시점에 0xC0000005 BSOD 발생
11811정성태2/11/20191571오류 유형: 510. 서버 운영체제에 NVIDIA GeForce Experience 실행 시 wlanapi.dll 누락 문제
11810정성태2/11/20191131.NET Framework: 808. .NET Profiler - GAC 모듈에서 GAC 비-등록 모듈을 참조하는 경우의 문제
11809정성태2/11/20191414.NET Framework: 807. ClrMD를 이용해 메모리 덤프 파일로부터 특정 인스턴스를 참조하고 있는 소유자 확인
11808정성태2/8/20191965디버깅 기술: 123. windbg - 닷넷 응용 프로그램의 메모리 누수 분석
11807정성태1/29/20191610Windows: 156. 가상 디스크의 용량을 복구 파티션으로 인해 늘리지 못하는 경우 [3]
11806정성태1/29/20191379디버깅 기술: 122. windbg - 덤프 파일로부터 PID와 환경 변수 등의 정보를 구하는 방법
11805정성태1/28/20192222.NET Framework: 806. C# - int []와 object []의 차이로 이해하는 제네릭의 필요성 [4]파일 다운로드1
11804정성태1/24/20191579Windows: 155. diskpart - remove letter 이후 재부팅 시 다시 드라이브 문자가 할당되는 경우
11803정성태1/10/20191750디버깅 기술: 121. windbg - 닷넷 Finalizer 스레드가 멈춰있는 현상
11802정성태1/7/20191810.NET Framework: 805. 두 개의 윈도우를 각각 실행하는 방법(Windows Forms, WPF)파일 다운로드1
11801정성태1/1/20191984개발 환경 구성: 427. Netsh의 네트워크 모니터링 기능 [3]
11800정성태12/28/20181817오류 유형: 509. WCF 호출 오류 메시지 - System.ServiceModel.CommunicationException: Internal Server Error
11799정성태12/19/20182584.NET Framework: 804. WPF(또는 WinForm)에서 UWP UI 구성 요소 사용하는 방법 [3]파일 다운로드1
11798정성태12/19/20182361개발 환경 구성: 426. vcpkg - "Building vcpkg.exe failed. Please ensure you have installed Visual Studio with the Desktop C++ workload and the Windows SDK for Desktop C++"
... 16  [17]  18  19  20  21  22  23  24  25  26  27  28  29  30  ...