그때에는 C++을 잘 몰라서 그냥 그러려니 하고 지나쳤던 것들 중 하나가 마샬링, PInvoke 등이었다. 이제와서 이걸 공부하려니 엄청 막힌다.
내가 한 삽질을 공개함으로써 여러분이 삽질 스트레스를 피할수 있기를 바란다.
1. PInvoke 왜 쓰는가?
가장 간단한 이유는 C++ 코드를 .NET 언어에서 호출하기 위해서다.
2. 어떻게 하는건가?
a. DLL을 지정한다.
b. 지정된 DLL에서 호출할 함수를 C#클래스로 래핑한다.
사실 첫번째 작업과 두번째 작업은 동시에 일어난다. 어떤 dll에 포함된 함수를 C#에서 호출하고 싶다고 해보자. MSDN에 많이 나와있는 예제이다.
user32.dll에 포함된 SetWindowText 를 호출하고 싶다고 해보자.
이 함수가 C#에서 존재하도록 만들어야 한다. C#은 정적 글로벌 함수가 없으므로 클래스 정적 함수로 선언해줘야 한다. 클래스 이름은 관계없다.
public class Win32
{
user32.dll에 포함된 SetWindowText 를 호출하고 싶다고 해보자.
이 함수가 C#에서 존재하도록 만들어야 한다. C#은 정적 글로벌 함수가 없으므로 클래스 정적 함수로 선언해줘야 한다. 클래스 이름은 관계없다.
public class Win32
{
[DllImport("User32.Dll")]
public static extern void SetWindowText(int h, String s);
}
함수가 static extern인 점을 주목해야 한다.
이제 사용자 코드에서 Win32.SetWindowText 해주기만 하면 된다.
근데 여러분이 이 글의 여기까지 읽고 또는 PInvoke가 설명된 다른 글들을 읽고 여러분의 DLL을 만들어서 이것을 하려고 한다면 내가 장담하건데, 실행이 안 된 다!
예제는 잘되는데 내가 만든건 안된다?
나의 삽질이 여기서 시작되었다.
위에서도 말했지만 C++의 뭔가를 C#에서 호출하고 싶은 것이다. C++ dll을 만드는 가장 쉬운 방법인 VS에서 win32 DLL 프로젝트를 생성할 때 export library 옵션을 주면 함수와 변수에 이상하게 생긴 매크로들이 붙어있다. 이 매크로들이 내부 함수와 변수들이 밖에서 보이도록 해 주는 것이다. 이 샘플함수의 이름은 fnXXX인데 내 경우에는 fncpp_dll이다.
나는 내 C# 콘솔 프로그램에 다음과 같이 정의했다.
class Program
{
[DllImport("cpp_dll.dll")]
[DllImport("cpp_dll.dll")]
static extern int fncpp_dll();
static void Main(string[] args)
{
Console.WriteLine(fncpp_dll());
}
}
컴파일은 잘되시만 실행하면 해당 이름이 없다고 나온다. 검색해 보니 정말로 export되었는지 Dependency walker등으로 확인해 보라고 한다. 처음에는 콧방귀를 꿨다. 내가 바보인가? 스펠링을 틀릴리가 없지않나. Dllexport확실히 된건데(샘플로 제공되는 코드이니)
한참 삽질하다가 Dep walker실행해 보니 이름이... ?fncpp_dll@@DKCK 이런식으로 되어있다. C++ 해본 사람이라면 익숙할것이다. 링크 오류났을때 자주보이는거다. 컴파일하고나면 C++ 함수의 이름은 이런식으로 mangled 된다. 그러니 아무리 DllImport 해도 소용없지. 이렇게 해보았다. [DllImport("cpp_dll.dll", EntryPoint="?fncpp_dll@DKCK")] 잘 된다.
근데 내가 어떻게 컴파일 될때마다 무작위로 깨지는 이름을 찾을 수 있겠어? 고민해 본다. 음, 그럼 C++이라서 그렇다는 거지? 그럼 C 함수로 만들어 주면 어떨까?
선언과 정의를 모두 extern "C" {}로 감싸보자.
extern "C" {
CPP_DLL_API int fncpp_dll(void);
}
이제 그냥 이름 fncpp_dll로 잘 불려진다. 그렇지만 이것도 문제다. 난 C++을 쓰고 싶은데,
원래 이 문제는 DllExport를 함으로써 발생하는 문제다. 이 문제가 없이 export하고 싶으면?
.def를 사용해야 한다.
이 파일은 어떻게 쓰는지 나도 잘 모른다. 이제부터 .def 삽질 시작이다.
static void Main(string[] args)
{
Console.WriteLine(fncpp_dll());
}
}
컴파일은 잘되시만 실행하면 해당 이름이 없다고 나온다. 검색해 보니 정말로 export되었는지 Dependency walker등으로 확인해 보라고 한다. 처음에는 콧방귀를 꿨다. 내가 바보인가? 스펠링을 틀릴리가 없지않나. Dllexport확실히 된건데(샘플로 제공되는 코드이니)
한참 삽질하다가 Dep walker실행해 보니 이름이... ?fncpp_dll@@DKCK 이런식으로 되어있다. C++ 해본 사람이라면 익숙할것이다. 링크 오류났을때 자주보이는거다. 컴파일하고나면 C++ 함수의 이름은 이런식으로 mangled 된다. 그러니 아무리 DllImport 해도 소용없지. 이렇게 해보았다. [DllImport("cpp_dll.dll", EntryPoint="?fncpp_dll@DKCK")] 잘 된다.
근데 내가 어떻게 컴파일 될때마다 무작위로 깨지는 이름을 찾을 수 있겠어? 고민해 본다. 음, 그럼 C++이라서 그렇다는 거지? 그럼 C 함수로 만들어 주면 어떨까?
선언과 정의를 모두 extern "C" {}로 감싸보자.
extern "C" {
CPP_DLL_API int fncpp_dll(void);
}
이제 그냥 이름 fncpp_dll로 잘 불려진다. 그렇지만 이것도 문제다. 난 C++을 쓰고 싶은데,
원래 이 문제는 DllExport를 함으로써 발생하는 문제다. 이 문제가 없이 export하고 싶으면?
.def를 사용해야 한다.
이 파일은 어떻게 쓰는지 나도 잘 모른다. 이제부터 .def 삽질 시작이다.