C# 에서 C/C++ 라이브러리(dll) API 함수 호출하기
- 프로그래밍/윈도우즈 프로그래밍
- 2020. 1. 27.
C# 에서 C/C++ 라이브러리(dll) API 함수 호출하기
본 글에서는 C/C++ 로 구현된 라이브러리가 제공하는 API 함수를 C# 어플리케이션에서 호출하는 방법을 설명한다.
C/C++ 로 구현된 라이브러리의 경우, 일반적으로 헤더파일(*.h) 상에 라이브러리가 제공하는 API 함수의 원형과 각종 파라미터(매크로, 변수형, 구조체 등)를 정의하여 제공한다. 해당 라이브러리를 사용하는 프로그램이 동일하게 C/C++ 로 구현될 경우, 이러한 헤더 파일을 이용하여 라이브러리가 제공하는 API 함수를 호출할 수 있다.
하지만 C# 에서는 헤더파일을 사용할 수 없기 때문에, C/C++ 라이브러리가 제공하는 API 함수와 파라미터들을 C# 구현에 맞는 형태로 Import 및 재선언하여 사용해야 한다.
본 글에서는 다음과 같은 예제를 통해, C# 어플리케이션에서 C/C++ 라이브러리 API 함수를 호출하는 방법을 설명한다.
- 정수형 인자와 반환 값을 갖는 C/C++ 라이브러리 API 함수 호출
- 구조체형 인자를 사용하는 C/C++ 라이브러리 API 함수 호출
- 중첩 구조체형 인자를 사용하는 C/C++ 라이브러리 API 함수 호출
본 글에서 사용된 라이브러리 및 어플리케이션 개발 환경은 다음과 같다.
- 윈도우즈 10 프로
- Visual studio 2017
프로젝트 생성
C/C++ 라이브러리 프로젝트 생성
새 프로젝트 생성 창에서 Visual C++ → Windows 데스크톱 → DLL(동적 연결 라이브러리) 를 선택하여 동적 연결 라이브러리로 프로젝트를 생성한다.
- C# 에서는 동적 연결 라이브러리만을 사용할 수 있으므로, 동적 연결 라이브러리(dll)로 구현해야 한다.
- 프로젝트 이름(=라이브러리 파일명)은 임의로 "c-lib"로 한다 → 빌드 시 c-lib.dll 파일이 생성된다.
C# 어플리케이션 프로젝트 생성
새 프로젝트 생성 창에서 Visual C# → 콘솔 앱(.NET Core) 를 선택하여 콘솔 앱으로 프로젝트를 생성한다.
- 라이브러리 API 호출 결과를 콘솔 출력으로 간단하게 확인하기 위하여 콘솔 앱으로 구현한다.
- 프로젝트 이름은 임의로 "csharp-app"로 한다.
정수형 인자와 반환 값을 갖는 C/C++ 라이브러리 API 함수 호출 예제
다음은 정수형 인자와 반환 값을 갖는 C/C++ 라이브러리 API 함수를 C# 어플리케이션에서 호출하는 예제이다.
C/C++ 라이브러리 함수 정의
정수형 변수 a, b 를 인자로 전달 받아, 둘을 더한 값을 반환하는 AddInteger() API 함수를 구현한다.
- __declpsec(dllexport) 구문을 사용하여 라이브러리 외부에서 호출이 가능하도록 한다.
/// @file test.cpp
/// @brief 테스트용 API 함수를 구현한 파일
#include "pch.h"
/// @brief 정수 인자 및 결과 전달 테스트용 함수
/// @param[in] a 입력값
/// @param[in] b 입력값
/// @return 결과값
extern "C" __declspec(dllexport) int AddInteger(int a, int b)
{
return a + b;
}
C# 어플리케이션에서 호출
C/C++ 라이브러리와 API 함수를 Program 클래스 내에 import 및 선언한다.
- [DllImport("")] 명령을 통해 라이브러리를 Import 한다.
- AddInteger() API 함수를 외부함수로 선언한다.
Main() 함수 내에서 라이브러리 API 함수를 호출한 결과를 콘솔에 출력하여 정상 동작 여부를 확인한다.
/// @file Program.cs
/// @brief C/C++ 라이브러리 호출 테스트 기능 구현 파일
using System;
using System.Runtime.InteropServices; ///< 라이브러리 import 를 위해 선언
namespace csharp_app
{
class Program
{
// C/C++ 라이브러리 Import
[DllImport("c-lib.dll")]
// C/C++ 라이브러리 API 함수를 외부함수로 선언
public static extern int AddInteger(int a, int b);
static void Main(string[] args)
{
Console.WriteLine("----- Integer result start --------------------");
Console.WriteLine("Result: {0}", AddInteger(3, 4));
Console.WriteLine("-----------------------------------------------");
Console.WriteLine("");
}
}
}
위 어플리케이션의 실행 결과로 3 + 4 = 7 이 출력되는 것을 확인할 수 있다.
구조체형 인자를 사용하는 C/C++ 라이브러리 API 함수 호출 예제
다음은 구조체형 인자를 갖는 C/C++ 라이브러리 API 함수를 C# 어플리케이션에서 호출하는 예제이다.
C/C++ 라이브러리 함수 정의
정수형, 문자열, 배열, 구조체형 변수들을 인자로 전달 받아, 구조체형 변수의 값을 업데이트하는 UpdateAlignedStructure() API 함수를 구현한다.
- __declpsec(dllexport) 구문을 사용하여 라이브러리 외부에서 호출이 가능하도록 한다.
/// @file test.cpp
/// @brief 테스트용 API 함수를 구현한 파일
#include "pch.h"
/// 바이트 정렬된 구조체
struct AlignedStructure
{
int a;
char b[128];
int c;
unsigned char d[64];
int e;
};
/// 바이트 정렬된 구조체 전달 테스트용 함수
/// @param[in] a 정수형 입력값
/// @param[in] b 문자열 입력값
/// @param[in] c 정수형 입력값
/// @param[in] d 배열 입력값
/// @param[in] e 정수형 입력값
/// @param[out] res 결과가 저장될 구조체 포인터
extern "C" __declspec(dllexport) void UpdateAlignedStructure(
int a, char *b, int c, unsigned char *d, int e,
struct AlignedStructure *res)
{
memset(res, 0, sizeof(struct AlignedStructure));
res->a = a;
memcpy(res->b, b, 128);
res->c = c;
memcpy(res->d, d, 64);
res->e = e;
}
C# 어플리케이션에서 호출
C/C++ 라이브러리와 API 함수, 인자로 사용되는 구조체를 Program 클래스 내에 import 및 선언한다.
- C/C++ 라이브러리에 정의된 구조체와 동일한 구조 및 형태를 갖는 구조체를 선언한다.
- 라이브러리와 어플리케이션 사이에 메모리 영역이 그대로 전달 되므로, 각 멤버 변수의 순서나 크기가 완전히 동일해야 한다.
- 배열의 경우, [MarshalAs()] 명령을 통해 동일한 크기로 정의한다.
- 구조체 내에 int 형 변수와 같이 플랫폼(예: 32비트, 64비트)에 따라 변수의 크기가 달라지는 변수가 포함되어 있는 경우에는 라이브러리와 어플리케이션 간 플랫폼을 통일해야 한다.
- [DllImport("")] 명령을 통해 라이브러리를 Import 한다.
- UpdateAlignedStructure() API 함수를 외부함수로 선언한다.
Main() 함수 내에서 라이브러리 API 함수를 호출한 결과를 콘솔에 출력하여 정상 동작 여부를 확인한다.
/// @file Program.cs
/// @brief C/C++ 라이브러리 호출 테스트 기능 구현 파일
using System;
using System.Runtime.InteropServices; ///< 라이브러리 import 를 위해 선언
namespace csharp_app
{
class Program
{
// C 라이브러리 구조체 Import
public struct AlignedStructure
{
public int a;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
public String b;
public int c;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 64)]
public byte[] d;
public int e;
}
// C/C++ 라이브러리 Import
[DllImport("c-lib.dll")]
// C/C++ 라이브러리 API 함수를 외부함수로 선언
public static extern void UpdateAlignedStructure(
int a, string b, int c, byte[] d, int e,
ref AlignedStructure res);
static void Main(string[] args)
{
int a1 = 3;
String b1 = "abcd";
int c1 = 4;
byte[] d1 = new byte[64];
d1[0] = 0x01;
d1[1] = 0x02;
d1[2] = 0x03;
d1[3] = 0x04;
int e1 = 5;
AlignedStructure res1 = new AlignedStructure();
UpdateAlignedStructure(a1, b1, c1, d1, e1, ref res1);
Console.WriteLine("----- Aligned structure test ------------------");
Console.WriteLine("a: {0}", res1.a);
Console.WriteLine("b: {0}", res1.b);
Console.WriteLine("c: {0}", res1.c);
Console.WriteLine("d: 0x{0:X2}{1:X2}{2:X2}{3:X2}", res1.d[0], res1.d[1], res1.d[2], res1.d[3]);
Console.WriteLine("e: {0}", res1.e);
Console.WriteLine("-----------------------------------------------");
Console.WriteLine("");
}
}
}
위 어플리케이션의 실행 결과로 구조체 내 각 멤버변수의 값이 업데이트된 것을 확인할 수 있다.
중첩된 구조체형 인자를 사용하는 C/C++ 라이브러리 API 함수 호출 예제
다음은 중첩된 구조체형 인자를 갖는 C/C++ 라이브러리 API 함수를 C# 어플리케이션에서 호출하는 예제이다. 방법은 바로 위에서 설명한 예제와 동일하다.
C/C++ 라이브러리 함수 정의
정수형, 문자열, 배열, 구조체형 변수들을 인자로 전달 받아, 구조체형 변수의 값을 업데이트하는 UpdateNestedStructure() API 함수를 구현한다.
- __declpsec(dllexport) 구문을 사용하여 라이브러리 외부에서 호출이 가능하도록 한다.
/// @file test.cpp
/// @brief 테스트용 API 함수를 구현한 파일
#include "pch.h"
/// 바이트 정렬된 구조체
struct AlignedStructure
{
int a;
char b[128];
int c;
unsigned char d[64];
int e;
};
/// 바이트 정렬되지 않은 구조체
struct NotAlignedStructure
{
int a;
char b[129];
int c;
unsigned char d[65];
int e;
};
/// 중첩된 구조체로 구성된 구조체
struct NestedStructure
{
struct AlignedStructure aligned;
int a12;
struct NotAlignedStructure not_aligned;
};
/// 중첩 구조체 전달 테스트용 함수
/// @param[in] a1 정수형 입력값
/// @param[in] b1 문자열 입력값
/// @param[in] c1 정수형 입력값
/// @param[in] d1 배열 입력값
/// @param[in] e1 정수형 입력값
/// @param[in] a2 정수형 입력값
/// @param[in] b2 문자열 입력값
/// @param[in] c2 정수형 입력값
/// @param[in] d2 배열 입력값
/// @param[in] e2 정수형 입력값
/// @param[out] res 결과가 저장될 구조체 포인터
extern "C" __declspec(dllexport) void UpdateNestedStructure(
int a1, char *b1, int c1, unsigned char *d1, int e1,
int a2, char *b2, int c2, unsigned char *d2, int e2,
struct NestedStructure *res)
{
memset(res, 0, sizeof(struct NestedStructure));
res->aligned.a = a1;
memcpy(res->aligned.b, b1, 128);
res->aligned.c = c1;
memcpy(res->aligned.d, d1, 64);
res->aligned.e = e1;
res->a12 = a1 + a2;
res->not_aligned.a = a2;
memcpy(res->not_aligned.b, b2, 129);
res->not_aligned.c = c2;
memcpy(res->not_aligned.d, d2, 65);
res->not_aligned.e = e2;
}
C# 어플리케이션에서 호출
C/C++ 라이브러리와 API 함수, 인자로 사용되는 구조체를 Program 클래스 내에 import 및 선언한다.
- C/C++ 라이브러리에 정의된 구조체와 동일한 구조 및 형태를 갖는 구조체를 선언한다.
- 라이브러리와 어플리케이션 사이에 메모리 영역이 그대로 전달 되므로, 각 멤버 변수의 순서나 크기가 완전히 동일해야 한다.
- 배열의 경우, [MarshalAs()] 명령을 통해 동일한 크기로 정의한다.
- 구조체 내에 int 형 변수와 같이 플랫폼(예: 32비트, 64비트)에 따라 변수의 크기가 달라지는 변수가 포함되어 있는 경우에는 라이브러리와 어플리케이션 간 플랫폼을 통일해야 한다.
- 또한 본 예제에는 바이트 정렬되지 않은 구조체(struct NotAlignedStructure)도 함께 사용되는데, 이 경우에도 라이브러리와 어플리케이션간에 동일한 구조/형태의 구조체가 사용되면 문제가 없다.
- [DllImport("")] 명령을 통해 라이브러리를 Import 한다.
- UpdateNestedStructure() API 함수를 외부함수로 선언한다.
Main() 함수 내에서 라이브러리 API 함수를 호출한 결과를 콘솔에 출력하여 정상 동작 여부를 확인한다.
/// @file Program.cs
/// @brief C/C++ 라이브러리 호출 테스트 기능 구현 파일
using System;
using System.Runtime.InteropServices; ///< 라이브러리 import 를 위해 선언
namespace csharp_app
{
class Program
{
// C 라이브러리 구조체 Import
public struct AlignedStructure
{
public int a;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
public String b;
public int c;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 64)]
public byte[] d;
public int e;
}
// C 라이브러리 구조체 Import
public struct NotAlignedStructure
{
public int a;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 129)]
public String b;
public int c;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 65)]
public byte[] d;
public int e;
}
// C 라이브러리 구조체 Import
public struct NestedStructure
{
public AlignedStructure aligned;
public int a12;
public NotAlignedStructure not_aligned;
}
// C 라이브러리 Import
[DllImport("c-lib.dll")]
// C/C++ 라이브러리 API 함수를 외부함수로 선언
public static extern void UpdateNestedStructure(
int a1, string b1, int c1, byte[] d1, int e1,
int a2, string b2, int c2, byte[] d2, int e2,
ref NestedStructure res);
static void Main(string[] args)
{
int a1 = 3;
String b1 = "abcd";
int c1 = 4;
byte[] d1 = new byte[64];
d1[0] = 0x01;
d1[1] = 0x02;
d1[2] = 0x03;
d1[3] = 0x04;
int e1 = 5;
int a2 = 10;
String b2 = "1234";
int c2 = 11;
byte[] d2 = new byte[65];
d2[0] = 0x11;
d2[1] = 0x12;
d2[2] = 0x13;
d2[3] = 0x14;
int e2 = 15;
NestedStructure res3 = new NestedStructure();
UpdateNestedStructure(a1, b1, c1, d1, e1, a2, b2, c2, d2, e2, ref res3);
Console.WriteLine("----- Nested structure test -------------------");
Console.WriteLine("a1: {0}", res3.aligned.a);
Console.WriteLine("b1: {0}", res3.aligned.b);
Console.WriteLine("c1: {0}", res3.aligned.c);
Console.WriteLine("d1: 0x{0:X2}{1:X2}{2:X2}{3:X2}",
res3.aligned.d[0], res3.aligned.d[1], res3.aligned.d[2], res3.aligned.d[3]);
Console.WriteLine("e1: {0}", res3.not_aligned.e);
Console.WriteLine("a12: {0}", res3.a12);
Console.WriteLine("a2: {0}", res3.not_aligned.a);
Console.WriteLine("b2: {0}", res3.not_aligned.b);
Console.WriteLine("c2: {0}", res3.not_aligned.c);
Console.WriteLine("d2: 0x{0:X2}{1:X2}{2:X2}{3:X2}",
res3.not_aligned.d[0], res3.not_aligned.d[1], res3.not_aligned.d[2], res3.not_aligned.d[3]);
Console.WriteLine("e2: {0}", res3.not_aligned.e);
Console.WriteLine("-----------------------------------------------");
Console.WriteLine("");
}
}
}
위 어플리케이션의 실행 결과로 구조체 내 각 멤버변수의 값이 정상적으로 업데이트된 것을 확인할 수 있다.
파트너스 활동을 통해 일정액의 수수료를 제공받을 수 있음