Created
May 1, 2013 05:07
-
-
Save jacking75/5493837 to your computer and use it in GitHub Desktop.
C#과 C++ 연동
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
관리되지 않는 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