C# 에서 C/C++ 라이브러리(dll) API 함수 호출하기

반응형

 

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/C++ 라이브러리 프로젝트 생성 - DLL(동적 연결 라이브러리)
C/C++ 라이브러리 프로젝트 속성 페이지

 

 

C# 어플리케이션 프로젝트 생성

새 프로젝트 생성 창에서 Visual C#콘솔 앱(.NET Core) 를 선택하여 콘솔 앱으로 프로젝트를 생성한다.

  • 라이브러리 API 호출 결과를 콘솔 출력으로 간단하게 확인하기 위하여 콘솔 앱으로 구현한다.
  • 프로젝트 이름은 임의로 "csharp-app"로 한다.

C# 어플리케이션 프로젝트 생성 - 콘솔 앱(.NET Core)
C# 어플리케이션 프로젝트 속성 페이지

 

 

정수형 인자와 반환 값을 갖는 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 이 출력되는 것을 확인할 수 있다.

AddInteger() 함수 호출 결과

 

 

구조체형 인자를 사용하는 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("");
    }
  }
}

 

위 어플리케이션의 실행 결과로 구조체 내 각 멤버변수의 값이 업데이트된 것을 확인할 수 있다.

UpdateAlignedStructure() API 함수 호출 결과

 

 

중첩된 구조체형 인자를 사용하는 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("");
    }
  }
}

 

위 어플리케이션의 실행 결과로 구조체 내 각 멤버변수의 값이 정상적으로 업데이트된 것을 확인할 수 있다.

UpdateNestedStructure() API 함수 호출 결과


파트너스 활동을 통해 일정액의 수수료를 제공받을 수 있음

댓글

Designed by JB FACTORY