Skip to content

Instantly share code, notes, and snippets.

@jacking75
Created May 1, 2013 05:07
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save jacking75/5493837 to your computer and use it in GitHub Desktop.
Save jacking75/5493837 to your computer and use it in GitHub Desktop.
C#과 C++ 연동
관리되지 않는 DLL 함수 사용
ms-help://MS.MSDNQTR.v90.ko/dv_fxinterop/html/eca7606e-ebfb-4f47-b8d9-289903fdc045.htm
C++/CLI
개발 도구 및 언어 -> Visual Studio 설명서 -> Visual C++ -> 시작 -> Visual C++ 작업 절차
ms-help://MS.MSDNQTR.v90.ko/dv_vcedit/html/53ddbc6f-e123-4dc7-aa38-674b92e7cf62.htm
꼭 봐야 될 것
http://sj21.wo.to/tt/483
http://sj21.wo.to/tt/484
http://blogs.msdn.com/junfeng/archive/2006/05/20/599434.aspx
How to: Marshal Structures Using C++ Interop How to: Marshal Embedded Pointers Using C++ Interop
#include <msclr\marshal.h>
using namespace System;
using namespace msclr::interop;
int main(int argc, char** argv)
{
const char* x = "Mixing C++ and C#";
String^ y;
y = marshal_as<String^>(x);
return 0;
}
Overview of Marshaling in C++ http://msdn.microsoft.com/en-us/library/bb384865.aspx
C++에서 C#의 함수를 콜백으로 받아서 처리
typedef void (__stdcall *PROGRESS)(int);
class CalcCPP
{
int Add(PROGRESS callBack, int a, int b)
{
int result = 0;
for(int i = 0; i < 100; ++i)
{
c = (a + b);
callBack(i);
}
return c;
}
}
C#에서 C++의 함수를 함수 콜백으로 사용 C++/CLR
using namespace System;
using namespace System.Runtime.InteropServices;
namespace Test
{
public delegate void PROGRESS_CSHARP(int);
public ref class CalcWrapper
{
private:
CalcCPP* m_pCpp;
public:
CalcWrapper()
{m_pCpp = new CalcCPP()}
virtual ~CalcWrapper()
{if(m_pCpp){delete m_pCpp; m_pCpp = 0;}}
int Add(PROGRESS_CSHARP^ callBackFromCSharp,
int a, int b)
{
// GC를 할당한 뒤
GCHandle gch =
GCHandle::Alloc(callBackFromCSharp);
// delegate를 함수 포인터로 만듭니다.
IntPtr ip =
Marshal::GetFunctionPointerForDelegate(
callBackFromCSharp
);
// 그리고 형변환해서 대입
PROGRESS callBack =
static_cast<PROGRESS>(ip.ToPointer());
// 사용!
int result = m_pCpp->Add(callBack, a, b);
// 할당했던 GC를 비우면서 해제합니다
GC::Collect();
gch.Free();
return result;
}
}
}
C#에서 사용할 때는
class TestClass
{
static void Progress(int pos)
{
System.Console.WriteLine(
"Currently : " + pos.ToString()
);
}
static void Main(string[] args)
{
Test.CalcWrapper cw = new Test.CalcWrapper();
int result = cw.Add(Progress, 1, 2);
System.Console.WriteLine(
"Result is " + result.ToString()
);
}
}
C/C++ 구조체와 함수를 C# 에서 사용하기 http://hado.hadostudio.com/blog/category/DotNet 기존의 C/C++ 의 구조체와 함수들을 C# 의 클래스로 캡슐화하는 작업을 설명드립니다.
먼저, C/C++ 에서 DLL 프로젝트를 생성합니다. (프로젝트 이름을 'BND.Native' 라고 하면 출력물은 'BND.Native.dll' 이 됩니다.)
1단계: C/C++ 구조체 정의 (MD5.h)
typedef struct mD5Context
{
UINT state[4]; /* state (ABCD) */
UINT count[2]; /* number of bits, modulo 2^64 (lsb first) */
BYTE buffer[64]; /* input buffer */
}
MD5Context;
구조체 내부에 고정 크기의 배열이 정의되어 있습니다.
2단계: C/C++ 함수 정의 (MD5.cpp)
void MD5Init(MD5Context * context)
{
...
}
void MD5Update(MD5Context * context, BYTE * input, UINT input_size)
{
...
}
void MD5Final(MD5Context * context, BYTE * output_digest)
{
...
}
C/C++ 언어답게(?) 포인터를 마구 사용하고 있습니다.
3단계: C/C++ 함수 선언 (MD5.h)
extern "C" __declspec(dllexport) void MD5Init(MD5Context * context);
extern "C" __declspec(dllexport) void MD5Update(MD5Context * context, BYTE * input, UINT input_size);
extern "C" __declspec(dllexport) void MD5Final(MD5Context * context, BYTE * output_digest);
함수들을 DLL 외부로 노출합니다.
이를 위해서 #include <Windows.h> 라인을 추가해야 합니다.
이상의 3단계를 작업한 후 컴파일하여 C/C++ DLL 을 생성합니다.
다음으로 C# 프로젝트를 생성합니다.
C# 프로그램에서 C/C++ DLL 을 참조하기 위해 [C# 프로젝트의 속성] - [빌드 이벤트] - [빌드 후 이벤트 명령줄] 에 다음을 추가합니다.
COPY /Y "$(SolutionDir)BND.Native\$(ConfigurationName)\BND.Native.dll" "$(TargetDir)BND.Native.dll"
C/C++ DLL 파일을 C# 프로젝트의 출력 디렉토리로 복사해 오는 것입니다.
이를 위해서 프로젝트 '종속성'에 'BND.Native' 프로젝트를 추가해야 합니다.
4단계: 구조체 캡슐화
public class MD5Calculator
{
// C/C++ 의 구조체와 동일한 형태가 되도록 정의합니다.
public struct MD5Context
{
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] public UInt32[] state;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)] public UInt32[] count;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 64)] public byte[] buffer;
}
// 클래스 내부에 구조체 인스턴스를 캡슐화합니다.
private MD5Context mContext = new MD5Context();
// 구조체를 초기화하는 코드를 생성자 내부에 작성합니다.
public MD5Calculator()
{
/* 따로 new 하지 않아도 이미 공간이 확보되어 있음.
mContext.state = new UInt32[4];
mContext.count = new UInt32[2];
mContext.buffer = new byte[64];
*/
}
C/C++ 에서 정의한 구조체와 동일한 구조체를 정의하고, 그 인스턴스를 정의하고, 초기화 코드를 작성하였습니다. 이 작업들을 편의상 하나의 클래스(MD5Calculator) 내에 캡슐화하였습니다.
5단계: 함수 참조 선언
// C언어 함수들
[DllImport("BND.Native.dll")] extern public static void MD5Init(ref MD5Context ctx);
[DllImport("BND.Native.dll")] extern public static void MD5Update(ref MD5Context ctx, byte[] input, int input_size);
[DllImport("BND.Native.dll")] extern public static void MD5Final(ref MD5Context ctx, byte[] output_digest);
각종 포인터 파라미터가 위와 같이 매핑됩니다.
6단계: 함수 캡슐화
public void Initial()
{
MD5Init(ref mContext);
}
public void Update(byte[] input, int input_size)
{
MD5Update(ref mContext, input, input_size);
}
public byte[] Final()
{
byte[] output_digest = new byte[16];
MD5Final(ref mContext, output_digest);
return output_digest;
}
구조체를 캡슐화 했듯이, 함수들도 캡슐화해 줍니다.
C#에서 C++ 함수로 포인터 배열 넘기기 혹시 저와 같은 고민을 하시는 분들이 보시고 조금이라도 도움이 되기를 바라며 1주일 동안 고생하며 스스로 찾아낸 답들을 올려보겠습니다. << C++ 멤버 메쏘드 >>
void CalOutputs(float* ivec);
float* GetOutput(void);
void GetOutput(float* ovec);
void TeachOnce(float* ivec, float* ovec, float Rate, float momentum);
위의 함수들은 예전에 작성된 C++ 클래스의 멤버 메쏘드입니다. 모두 포인터 배열을 넘기고 받기 때문에 C#에서는 그대로 사용하기 어려운 면이 있어 C# wrapper class를 만들고 다음과 같이 메쏘드를 wrapping 했습니다.
배열 넘기기
public void CalOutputs(float[] ivec)
{
unsafe
{
fixed(float* _ivec = ivec)
{
this.bpNet.CalOutputs(_ivec);
}
}
}
배열 리턴 값 받기
특히 이 부분이 어려웠는데, 배열을 바로 리턴 받을 방법은 없고 그렇다고 마셜링을 한다든가 하면 퍼포먼스에 문제가 있을 거 같아서 원본 C++ 클래스에는 float* GetOutput(void); 으로 되어 있던 메쏘드를 void GetOutput(float* ovec); 로 추가하여 wrapping 했습니다. 만일 소스코드를 바로 다룰 수가 없는 DLL 상태라든가 하면 DLL 과 C# 사이에 이 wrapping 해주는 메쏘드를 포함하는 C++ wrapper 클래스를 추가해주면 되겠죠.
public float[] GetOutput()
{
float[] result = new float[LENGTH];
unsafe
{
fixed(float* _result = result)
{
this.bpNet.GetOutput(_result);
}
}
return result;
}
같은 타입의 배열을 두 개 이상 넘길 때
public void TeachOnce(float[] ivec, float[] ovec, float Rate, float momentum)
{
unsafe
{
fixed(float* _ivec = ivec, _ovec = ovec)
{
this.bpNet.TeachOnce(_ivec, _ovec, Rate, momentum);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment