extern "C"와 네임 맹글링

 

오픈소스같은 곳에 보면 extern "C"라는 문법이 종종 보이는데 처음에 보면 이게 뭐하는거지 C로 컴파일 해준다는건가? 아니면 C문법으로 바꾸어 쓰라는건가? 라고 의문이 생길겁니다. 이번엔 extern "C"가 뭐하는 녀석인지 알아봅시다. 


네임 맹글링

 

extern "C"를 알아보기 전에 네임 맹글링에 대해서 먼저 알아봅시다.

 

컴파일러들은 컴파일을 할때 다른곳에서 접근할수 있도록 심볼을 만드는 작업을 합니다. 
여기서 함수를 심볼로 만들때 심볼이름을 함수이름 그대로 생성하는 C컴파일러와 달리 C++컴파일러들은 각각 정해진 규칙에 따라 심볼이름을 변경하여 생성합니다. 
이러한 작업을 네임 맹글링이라고 하며 C와 달리 C++에서는 함수 오버로딩 같은 다형성을 지원할 수 있는 이유이기도 합니다. 

 

그러면 이제 실제로 네임 맹글링이 되고있는지 확인해봅시다.

 

test_c.c

/* C 코드 */
#include <stdio.h> 

void test()
{
	printf("extern c test!!\n");
}

int main()
{
	test();
	return 0;
}

 

test_cpp.cpp

/* C++ 코드 */
#include <iostream>
using std::cout;
using std::endl;

void test()
{
	cout << "extern c test!!" << endl;
}

int main()
{
	test();
	return 0;
}

 

컴파일

# C 컴파일 #
gcc -c test_c.c
gcc -o test_c test_c.o

# C++ 컴파일 #
g++ -c test_cpp.cpp
g++ -o test_cpp test_cpp.o

 

결과

 

readelf 명령어를 사용하여 확인해보면 실제로 C로 컴파일한 파일은 test 라는 이름으로 심볼이 생성되었고 C++로 컴파일한 파일은 _Z4testv 라는 이름으로 심볼이 생성된걸 확인할 수 있습니다.


extern "C"

 

자 그러면 이미 눈치채신 분들도 있을겁니다. 

extern "C"는 네임 맹글링을 사용하지 않는다. 라는 의미로 extern "C"를 선언하면 그 안에 모든 코드들은 네임 맹글링을 하지 않습니다.

 

그럼 실제로 네임 맹글링 작업을 안하는지 알아봅시다.

 

test_cpp_extern_c.cpp

/* C++ 코드 */
#include <iostream>
using std::cout;
using std::endl;

extern "C" 
{
	void test()
	{
		cout << "extern c test!!" << endl;
	}
}

int main()
{
	test();
	return 0;
}

 

컴파일

# C++ extern "C" 컴파일 # 
g++ -c test_cpp_extern_c.cpp
g++ -o test_cpp_extern_c.o

 

결과

 

extern "C"를 적용안한 파일에서는 _Z4testv 로 심볼이 생성되었고 extern "C"를 적용한 파일에는 C코드와 마찬가지로 test라는 이름으로 심볼이 생성된걸 확인할 수 있습니다.


마무리

 

지금까지 extern "C"와 네임 맹글링에 대해서 알아보았습니다.

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

728x90
반응형

 

C++ 클래스 기본적인 사용법

객체지향 언어인 C++의 클래스 사용법에 대해 알아봅시다.


접근 제어 지시자

 

public 어디서든 접근이 가능한 접근 제어 지시자이며 내부, 외부코드에서 모두 접근이 가능합니다.
private 선언한 클래스 내부에서만 접근이 가능한 접근 제어 지시자이며 외부에서 함부로 사용을 못하게 할때 사용합니다.
protected 기본적으로 private 역할을 하며 추가적으로 상속 받은 클래스에서도 접근이 가능한 접근 제어 지시자입니다.

 

 

기본적인 클래스 모양

 

class 클래스 이름
{
접근 제어 지시자:
    멤버변수
    멤버함수
};

클래스 선언부와 구현부

 

클래스 선언부: 클래스의 모양을 정의하는 곳이며 보통 헤더파일(.h파일)에 많이 정의합니다.

class TestClass
{
private:
	string stringTest;
    
public:
	void SetString(string data);
};

 

클래스 구현부: 클래스 선언부에서 정의한 함수들을 실제로 구현하는 곳입니다.

void TestClass::SetString(string data)
{
	cout << "[SetString] " << data << endl;
	stringTest = data;
}

생성자와 소멸자

 

생성자: 클래스 객체를 생성할때 자동으로 호출되는 함수 입니다. 사용자가 따로 코드작성을 하지 않으면 컴파일러가 알아

 

생성자 선언부

class TestClass 
{
public:
	TestClass();
};

 

생성자 구현부

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

 

소멸자: 생성자와 반대로 클래스 객체가 소멸할때 자동으로 호출되는 함수 입니다. 생성자와 마찬가지로 사용자가 따로  코드작성을 하지 않으면 컴파일러가 알아서 아무런기능이 없는 함수로 만들어 실행합니다.

 

소멸자 선언부

class TestClass 
{
public:
	~TestClass();
};

 

소멸자 구현부

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

클래스 객체 생성 및 사용

 

이제 앞에서 생성한 클래스로 객체를 만들어 사용하는 방법을 알아보겠습니다.

void main() 
{
	/* TestClass 클래스를 obj 이름으로 객체 생성 */
	TestClass obj = TestClass();

	/* 객체 함수 호출 */
	obj.SetString("Ruru TestString");
}

전체 예제 코드

 

헤더파일(.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) 
{
	cout << "[SetString] " << data << endl;
	stringTest = data;
}
void TestClass::SetBool(bool data) 
{
	cout << "[SetBool] " << data << endl;
	boolTest = data;
}
void TestClass::SetInt(int data) 
{
	cout << "[SetInt] " << data << endl;
	intTest = data;
}

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

void main() 
{
	/* TestClass 클래스를 obj 이름으로 객체 생성 */
	TestClass obj = TestClass();

	/* 객체 함수 호출 */
	obj.SetString("Ruru TestString");
	obj.SetBool(true);
	obj.SetInt(200);
    
	cout << "[GetString] " << obj.GetString() << endl;
	cout << "[GetBool] " << obj.GetBool() << endl;
	cout << "[GetInt] " << obj.GetInt() << endl;
}

 

마무리

 

이상 C++ 클래스 기본적인 사용법에 대해 알아보았습니다.

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

 

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