클래스 구조의 C++ DLL 사용하기

 

이번에는 클래스 구조의 C++ DLL 사용하는 방법을 알아보곘습니다.


DLL 파일 준비하기

 

https://rurustory.com/12

 

[C++] 클래스를 DLL로 만들고 사용하기

클래스를 DLL로 만들고 사용하기 클래스를 DLL로 만들고 사용하는 방법을 알아보겠습니다. 클래스 구조 코드 준비하기 헤더파일(.h파일) #include using std::cout; using std::endl; #include using std::string; class

rurustory.com

 

저는 지난번 포스팅에서 만든 DLL를 사용하여 만들겠습니다.

 

DLL 구조

#include "ctest.h"
#define DLL __declspec(dllexport)

extern "C" {
	DLL TestClass* CreateTestClass();

	DLL void SetString(TestClass* obj, string data);
	DLL void SetBool(TestClass* obj, bool data);
	DLL void SetInt(TestClass* obj, int data);

	DLL string GetString(TestClass* obj);
	DLL bool GetBool(TestClass* obj);
	DLL int GetInt(TestClass* obj);

	DLL void DeleteTestClass(TestClass* obj);
}

DLL 호출하기

 

C#에서는 C++과 다르게 헤더파일(.h)이 필요가 없습니다.

 

DllImport 라는 함수를 사용하여 DLL의 함수를 가져올수 있습니다.

[DllImport("DLL 경로와 파일명")]
사용할 함수();

 


DLL 함수 클래스로 감싸기

 

사실 클래스로 감싸지않고 바로 사용을 할 수도 있지만 나중에 좀 더 사용하기 편하게 DLL를 C# 클래스 형태로 만들어보겠습니다.

 

코드

using System;
using System.Runtime.InteropServices;

namespace DLLTest
{
    class TestClass
    {
        [DllImport("D:\\C_Project\\ctest\\x64\\Release\\ctest.dll", CallingConvention = CallingConvention.Cdecl)]
        static private extern IntPtr CreateTestClass();

        [DllImport("D:\\C_Project\\ctest\\x64\\Release\\ctest.dll", CallingConvention = CallingConvention.Cdecl)]
        static private extern void SetBool(IntPtr obj, bool data);

        [DllImport("D:\\C_Project\\ctest\\x64\\Release\\ctest.dll", CallingConvention = CallingConvention.Cdecl)]
        static private extern void SetInt(IntPtr obj, int data);

        [DllImport("D:\\C_Project\\ctest\\x64\\Release\\ctest.dll", CallingConvention = CallingConvention.Cdecl)]
        static private extern bool GetBool(IntPtr obj);

        [DllImport("D:\\C_Project\\ctest\\x64\\Release\\ctest.dll", CallingConvention = CallingConvention.Cdecl)]
        static private extern int GetInt(IntPtr obj);

        [DllImport("D:\\C_Project\\ctest\\x64\\Release\\ctest.dll", CallingConvention = CallingConvention.Cdecl)]
        static private extern void DeleteTestClass(IntPtr obj);

        private IntPtr obj;

        public TestClass()
        {
            obj = CreateTestClass();
        }

        ~TestClass()
        {
            DeleteTestClass(obj);
        }

        public void SetBool(bool data) { SetBool(obj, data); }
        public void SetInt(int data) { SetInt(obj, data); }

        public bool GetBool() { return GetBool(obj); }
        public int GetInt() { return GetInt(obj); }

    }
}

 

코드 설명

[DllImport("D:\\C_Project\\ctest\\x64\\Release\\ctest.dll", CallingConvention = CallingConvention.Cdecl)]
static private extern IntPtr CreateTestClass();

[DllImport("D:\\C_Project\\ctest\\x64\\Release\\ctest.dll", CallingConvention = CallingConvention.Cdecl)]
static private extern void SetBool(IntPtr obj, bool data);

[DllImport("D:\\C_Project\\ctest\\x64\\Release\\ctest.dll", CallingConvention = CallingConvention.Cdecl)]
static private extern void SetInt(IntPtr obj, int data);

[DllImport("D:\\C_Project\\ctest\\x64\\Release\\ctest.dll", CallingConvention = CallingConvention.Cdecl)]
static private extern bool GetBool(IntPtr obj);

[DllImport("D:\\C_Project\\ctest\\x64\\Release\\ctest.dll", CallingConvention = CallingConvention.Cdecl)]
static private extern int GetInt(IntPtr obj);

[DllImport("D:\\C_Project\\ctest\\x64\\Release\\ctest.dll", CallingConvention = CallingConvention.Cdecl)]
static private extern void DeleteTestClass(IntPtr obj);

 

먼저 DllImport 를 이용하여 DLL의 함수들을 가져옵니다.

C#에서는 IntPtr 를이용하여 C++의 오브젝트 주소를 가져올 수 있습니다.

 

private IntPtr obj;

public TestClass()
{
    obj = CreateTestClass();
}

~TestClass()
{
    DeleteTestClass(obj);
}

 

IntPtr 형태의 obj를 생성하고 생성자에서 C++에서 만들어 놓은 CreateTestClass()를 이용하여 obj에 객체를 생성해줍니다.

그리고 소멸자에 꼭 C++에서 만든 DeleteTestClass()를 이용하여 동적할당을 해제해줍니다.

 

public void SetBool(bool data) { SetBool(obj, data); }
public void SetInt(int data) { SetInt(obj, data); }

public bool GetBool() { return GetBool(obj); }
public int GetInt() { return GetInt(obj); }

 

TestClass의 함수들을 만들어주고 return으로 C++ 의 함수들을 다시 재호출하여 TestClass 함수만 사용해도 C++ 함수들을 사용할 수 있도록 만들어줍니다.


클래스 테스트

 

전체 코드

using System;
using System.Runtime.InteropServices;

namespace DLLTest
{
	class TestClass
	{
		[DllImport("D:\\C_Project\\ctest\\x64\\Release\\ctest.dll", CallingConvention = CallingConvention.Cdecl)]
		static private extern IntPtr CreateTestClass();

		[DllImport("D:\\C_Project\\ctest\\x64\\Release\\ctest.dll", CallingConvention = CallingConvention.Cdecl)]
		static private extern void SetBool(IntPtr obj, bool data);

		[DllImport("D:\\C_Project\\ctest\\x64\\Release\\ctest.dll", CallingConvention = CallingConvention.Cdecl)]
		static private extern void SetInt(IntPtr obj, int data);

		[DllImport("D:\\C_Project\\ctest\\x64\\Release\\ctest.dll", CallingConvention = CallingConvention.Cdecl)]
		static private extern bool GetBool(IntPtr obj);

		[DllImport("D:\\C_Project\\ctest\\x64\\Release\\ctest.dll", CallingConvention = CallingConvention.Cdecl)]
		static private extern int GetInt(IntPtr obj);

		[DllImport("D:\\C_Project\\ctest\\x64\\Release\\ctest.dll", CallingConvention = CallingConvention.Cdecl)]
		static private extern void DeleteTestClass(IntPtr obj);

		private IntPtr obj;

		public TestClass()
		{
			obj = CreateTestClass();
		}

		~TestClass()
		{
			DeleteTestClass(obj);
		}

		public void SetBool(bool data) { SetBool(obj, data); }
		public void SetInt(int data) { SetInt(obj, data); }

		public bool GetBool() { return GetBool(obj); }
		public int GetInt() { return GetInt(obj); }

	}

	class test
	{
		static void Main(string[] args)
		{
			TestClass lib = new TestClass();

			lib.SetBool(true);
			lib.SetInt(200);

			Console.WriteLine("[GetBool] " + lib.GetBool());
			Console.WriteLine("[GetInt] " + lib.GetInt());
		}
	}
}

 

이제 우리가 아는 클래스 형태로 C++의 DLL를 사용할 수 있습니다.

 

실행 결과


마무리

 

추가로 C++ 문자열을 C#에서 쓰는게 조금 복잡해서 따로 포스팅을 하였습니다.

궁금하신 분들은 아래 링크에서 확인 할 수 있습니다!

 

https://rurustory.com/1

 

[C#] 다양한 방법으로 C++ DLL String 사용하기

다양한 방법으로 C++ DLL String 사용하기 C#에서 C++로 문자열을 전달을 해줄때 자동으로 데이터를 맞춰주지만 C++에서 C#으로 문자열을 전달 할때는 수동으로 데이터를 맞춰야 합니다. 지금부터 다

rurustory.com

 

잘못된 점이 있거나 궁금한 점이 있다면 언제든지 문의해주시기 바랍니다!

728x90
반응형

'프로그래밍 > C#' 카테고리의 다른 글

[C#] 다양한 방법으로 C++ DLL String 사용하기  (0) 2024.02.21

 

클래스를 DLL로 만들고 사용하기

 

클래스를 DLL로 만들고 사용하는 방법을 알아보겠습니다.


클래스 구조 코드 준비하기

 

헤더파일(.h파일)

#include <iostream>
using std::cout;
using std::endl;

#include <string>
using std::string;

class TestClass 
{
private:
	string stringTest;
	bool boolTest;
	int intTest;

public:
	TestClass();
	~TestClass();

	void SetString(string data);
	void SetBool(bool data);
	void SetInt(int data);

	string GetString();
	bool GetBool();
	int GetInt();
};

 

cpp파일

#include "ctest.h"

TestClass::TestClass() { cout << "Init TestClass!!" << endl; }
TestClass::~TestClass() { cout << "End TestClass!!" << endl; }

void TestClass::SetString(string data) { stringTest = data; }
void TestClass::SetBool(bool data) { boolTest = data; }
void TestClass::SetInt(int data) { intTest = data; }

string TestClass::GetString() {	return stringTest; }
bool TestClass::GetBool() { return boolTest; }
int TestClass::GetInt() { return intTest; }

 

위와 같이 DLL로 만들 클래스 구조의 코드를 준비합니다.


DLL Wrapper(래퍼) 선언부 만들기

 

헤더파일(.h파일)

#include "ctest.h"
#define DLL __declspec(dllexport)

extern "C" {
	DLL TestClass* CreateTestClass();

	DLL void SetString(TestClass* obj, string data);
	DLL void SetBool(TestClass* obj, bool data);
	DLL void SetInt(TestClass* obj, int data);

	DLL string GetString(TestClass* obj);
	DLL bool GetBool(TestClass* obj);
	DLL int GetInt(TestClass* obj);

	DLL void DeleteTestClass(TestClass* obj);
}

 

코드 설명

일반적인 DLL 만드는 방법과 동일하게 __declspec(dllexport) 명령어를 이용하여 DLL 라이브러리에서 호출가능한 함수로 만들어줍니다.

그리고 DLL에서 네임 맹글링된 함수를 가져오기 어렵기 때문에 extern "C" 명령어를 이용하여 네임 맹글링을 제거합니다.

이제부터 네임 맹글링을 하지않기 때문에 클래스를 사용하려면 클래스를 동적 할당하여 각 함수마다 동적 할당된 클래스의 주소를 알려주어야 합니다.

마지막으로 동적 할당하여 객체를 생성하였기 때문에 꼭 동적 할당 해제하는 함수도 만들어줘야 합니다.


DLL Wrapper(래퍼) 구현부 만들기

 

cpp파일

#include "ctestDLL.h"

TestClass* CreateTestClass() { return new TestClass(); }

void SetString(TestClass* obj, string data) { obj->SetString(data); }
void SetBool(TestClass* obj, bool data){ obj->SetBool(data); }
void SetInt(TestClass* obj, int data){ obj->SetInt(data); }

string GetString(TestClass* obj) { return obj->GetString(); }
bool GetBool(TestClass* obj){ return obj->GetBool(); }
int GetInt(TestClass* obj){ return obj->GetInt(); }

void DeleteTestClass(TestClass* obj) 
{
	if (obj) {
		delete obj;
		obj = nullptr;
	}
}

 

코드 설명

new를 이용하여 클래스를 동적할당하는 함수를 구현합니다.

각 함수에서는 동적할당한 클래스의 주소를 받고 동적할당한 클래스에서 함수를 다시 실행시켜야 합니다.

delete를 이용하여 꼭 동적할당해제를 해줘야합니다.


DLL 파일로 만들기

 

빌드 설정

 

빌드하기전에 [프로젝트 속성 > 구성 속성 > 일반] 에서 구성 형식을 동적 라이브러리(.dll)인지 꼭 확인해야합니다.

 

빌드 결과

빌드가 완료 되면 위와같이 dll파일과 lib파일이 생깁니다.

 

여기서 dll빌드를 하였는데 lib파일이 생기는건 암시적 링킹(Implicit linking)을 하였기 때문입니다. 좀 더 상세한 내용은 아래 포스팅을 통해 확인 할 수 있습니다.

 

https://rurustory.com/8

 

[C++] 정적 라이브러리(Static Link Library)와 동적 라이브러리(Dynamic Link Library)

정적 라이브러리(Static Link Library)와 동적 라이브러리(Dynamic Link Library) 개발을 할 때 자주쓰는 함수가 많아 중복을 최소화 하기위해 또는 협업을 효율적으로 하기위해 등등 각종 여러가지 이유로

rurustory.com


DLL 사용하기

 

이제 DLL을 만들었으니 새로운 프로젝트를 만들어 테스트를 해봅시다.

 

빌드 설정

 

1. [프로젝트 속성 > C/C++ > 일반] 에서 추가 포함 디렉터리에 dll의 헤더(.h 파일)가 있는 폴더경로를 설정해줍니다.

 

 

2. [프로젝트 속성 > 링커 > 일반] 에서 추가 라이브러리 디렉터리에 dll파일과 lib파일이 있는 폴더경로를 설정해줍니다.

 

 

3. [프로젝트 속성 > 링커 > 입력]에서 추가 종속성에 lib파일 이름을 입력해줍니다. 

 

Main cpp파일

#include "ctestDLL.h"

void main()
{
	TestClass* obj = CreateTestClass();

	SetString(obj, "Ruru TestString");
	SetBool(obj, true);
	SetInt(obj, 200);

	cout << "[GetString] " << GetString(obj) << endl;
	cout << "[GetBool] " << GetBool(obj) << endl;
	cout << "[GetInt] " << GetInt(obj) << endl;
    
	DeleteTestClass(obj);
}

 

빌드 결과

 

이제 빌드를 하면 정상적으로 빌드가 되는것을 확인할 수 있습니다.


마무리

 

지금까지 C++ 클래스를 DLL로 만들고 사용하는 방법이였습니다.

잘못된 점이 있거나 궁금한 점이 있다면 언제든지 문의해주시기 바랍니다!

728x90
반응형

 

정적 라이브러리(Static Link Library)와 동적 라이브러리(Dynamic Link Library)

 

개발을 할 때 자주쓰는 함수가 많아 중복을 최소화 하기위해 또는 협업을 효율적으로 하기위해 등등 각종 여러가지 이유로 라이브러리를 사용하게 되는데 이번 시간에는 라이브러리가 어떤것들이 있는지 알아봅시다.


정적 라이브러리(Static Link Library)

 

정적 라이브러리는 컴파일을 하는 과정에서 애플리케이션이 필요로하는 라이브러리의 내용을 애플리케이션에다가 복사를 하여 사용하는 방법 입니다.

 

장단점

장점은 애플리케이션만 있으면 어디서든 실행을 시킬수 있다는 점이 있습니다.

단점은 애플리케이션에 라이브러리의 내용을 복사를 하였기 때문에 자체 용량이 커지며 라이브러리가 변동이 될 경우 애플리케이션을 전체 다시 빌드해야한다는 점이 있습니다.

 

정적 라이브러리의 확장자

Windows: .lib

Linux: .a


동적 라이브러리(Dynamic Link Library)

 

동적 라이브러리는 애플리케이션 실행할 떄 메모리에 위치하게 되며 라이브러리의 내용이 필요하게 되면 메모리에 있는 라이브러리를 사용하는 방법입니다.

 

장단점

장점은 정적 라이브러리를 사용하는 것보다 자체 용량이 작으며 라이브러리가 변동이 될 경우 애플리케이션 전체 빌드할 필요 없이 라이브러리만 바꾸면 된다는 점이 있습니다.

단점은 애플리케이션 실행할 때 라이브러리 파일이 필요하며 버전이 다르거나 호환이 되지 않는다면 사용하지 못하는 점이 있습니다.

 

동적 라이브러리의 확장자

Windows: .dll

Linux: .so


암시적 링킹(Implicit Linking)과 명시적 링킹(Explicit Linking)

 

윈도우에서 사용하는 동적 라이브러리(DLL) 에서는 애플리케이션에서 라이브러리를 호출하는 방법에 따라 암시적 링킹과 명시적 링킹으로 나누어집니다.

 

암시적 링킹(Implicit linking)

애플리케이션 자체에 DLL의 함수 정보를 포함시키는 방법이며 빌드 시 동적 라이브러리인 .dll 파일과 DLL이 제공하고자 하는 함수 정보가 포함되어 있는 .lib 파일이 생성됩니다.

이때 생성되는 .lib 파일은 정적 라이브러리로 생성한 .lib 파일과 확장자가 똑같아 같은 파일로 혼돈할 수 있지만 암시적 링킹으로 인해 생성된 .lib은 오로지 DLL의 제공 함수만 있기때문에 주의하시기 바랍니다.

 

명시적 링킹(Explicit linking)

애플리케이션이 실행할 때 .dll 파일의 존재만 확인을 하며 호출 할 함수의 주소를 얻어 함수를 실행시키는 방법 입니다.

 

 

암시적 링킹의 경우 컴파일러가 자동으로 해주는 경우가 많으며 사용하기 쉽기 때문에 편의성을 중시한다면 암시적 링킹을 사용하는것이 좋고 성능을 중시한다면 명시적 링킹을 사용하는것이 좋습니다.


마무리

 

지금까지 라이브러리 종류에 대해 알아보았습니다.

잘못된 점이 있거나 궁금한 점이 있다면 언제든지 문의해주시기 바랍니다!

 

728x90
반응형

 

다양한 방법으로 C++ DLL String 사용하기

 

C#에서 C++로 문자열을 전달을 해줄때 자동으로 데이터를 맞춰주지만 C++에서 C#으로 문자열을 전달 할때는 수동으로 데이터를 맞춰야 합니다.
지금부터 다양한 방법으로 C++문자열을 C#에서 사용하는 방법을알아봅시다.


방법 1. PtrToStringAnsi 함수를 사용하여 Marshalling 하기

 

C++

const char* stringTest() 
{
	string testStr = "Ruru Test C++ String !!!";
	return testStr.c_str();
}

C#

using System;
using System.Runtime.InteropServices;
class Program
{
    [DllImport("StringTest.dll", CallingConvention = CallingConvention.Cdecl)]
    static private extern IntPtr stringTest();

    static void Main(string[] args)
    {
    	IntPtr stringPtr = stringTest();
        string testStr = Marshal.PtrToStringAnsi(stringPtr);
        Console.WriteLine(testStr);
        Console.ReadKey();
        //초기화
        Marshal.FreeHGlobal(stringPtr);
    }
}

 

C++의 const char*를 IntPtr 형태로 받아 PtrToStringAnsi 함수를 사용하여 string으로 변환하는 방법입니다.

Marshal 클래스를 사용하기 위해서는 System.Runtime.InteropServices를 추가해야 사용할 수 있습니다.

메모리 누수 방지를 위해 문자열 사용이 끝났으면 꼭 FreeHGlobal 함수를 사용하여 초기화 해주어야합니다.


방법 2. PtrToStringUni 함수를 사용하여 Marshalling 하기

 

C++

const wchar_t* stringTest() 
{
	string testStr = "Ruru Test C++ String !!!";
	wstring testUni(testStr.begin(), testStr.end());
	return testUni.c_str();
}

 

C#

using System;
using System.Runtime.InteropServices;
class Program
{
    [DllImport("StringTest.dll", CallingConvention = CallingConvention.Cdecl)]
    static private extern IntPtr stringTest();

    static void Main(string[] args)
    {
    	IntPtr stringPtr = stringTest();
        string testStr = Marshal.PtrToStringUni(stringPtr);
        Console.WriteLine(testStr);
        //초기화
        Marshal.FreeHGlobal(stringPtr);
    }
}

 

C++의 const wchar_t*를 IntPtr 형태로 받아 PtrToStringUni 함수를 사용하여 string으로 변환하는 방법입니다.

Marshal 클래스를 사용하기 위해서는 System.Runtime.InteropServices를 추가해야 사용할 수 있습니다.

메모리 누수 방지를 위해 문자열 사용이 끝났으면 꼭 FreeHGlobal 함수를 사용하여 초기화 해주어야합니다.


방법 3. ICustomMarshaler를 사용하여 커스텀 Marshalling하기

 

C++

const char* stringTest() 
{
	string testStr = "Ruru Test C++ String !!!";
	return testStr.c_str();
}

 

C#

using System;
using System.Runtime.InteropServices;
internal class StringCustom : ICustomMarshaler
{
    #region ICustomMarshaler Members

    public void CleanUpManagedData(object ManagedObj) { }

    public void CleanUpNativeData(IntPtr pNativeData) { }

    public int GetNativeDataSize()
        => -1;

    public IntPtr MarshalManagedToNative(object ManagedObj)
    {
        throw new NotSupportedException();
    }

    public object MarshalNativeToManaged(IntPtr pNativeData)
        => Marshal.PtrToStringAnsi(pNativeData);

    #endregion

    public static ICustomMarshaler GetInstance(string cookie)
    {
        if (cookie == null)
        {
            throw new ArgumentNullException(nameof(cookie));
        }

        var result = new StringCustom();

        return result;
    }
}

class Program
{
    [DllImport("StringTest.dll", CallingConvention = CallingConvention.Cdecl)]
    [return: MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(StringCustom))]
    static private extern string stringTest();

    static void Main(string[] args)
    {
        string testStr = stringTest();
        Console.WriteLine(testStr);
        Console.ReadKey();
    }
}

 

사용자 커스텀 클래스인 ICustomMarshaler 를 사용하여 string으로 리턴받는 방법입니다.

속도가 느려 퍼포먼스가 필요한 코드라면 비추천하는 방법입니다.


방법 4. StringBuilder 함수를 사용하여 C++에서 직접 복사 하기

 

C++

int stringTest(char* buffer) 
{
	string testStr = "Ruru Test C++ String !!!";
	strcpy(buffer, testStr.c_str());
	return testStr.size();
}

 

C#

using System;
using System.Text;
class Program
{
    [DllImport("StringTest.dll", CallingConvention = CallingConvention.Cdecl)]
    static private extern int stringTest(StringBuilder buffer);

    static void Main(string[] args)
    {
        StringBuilder testBuffer = new StringBuilder(100);
        stringTest(testBuffer);
        Console.WriteLine(testBuffer.ToString());
        Console.ReadKey();
    }
}

 

StringBuilder를 이용하여 C++에 buffer를 전달하고 C++에서 문자열을 복사하여 사용하는 방법입니다.

 

StringBuilder를 사용하기 위해서는 System.Text 를 추가하여야 사용할 수 있습니다.


마무리

 

지금까지 다양한 방법으로 C++문자열을 C#에서 사용하는 방법을 알아보았습니다.

잘못된 점이 있거나 궁금한 점이 있다면 언제든지 문의해주시기 바랍니다!

 

728x90
반응형

'프로그래밍 > C#' 카테고리의 다른 글

[C#] 클래스 구조의 C++ DLL 사용하기  (0) 2024.04.25

+ Recent posts