C# 은 강력한 기능과 클래스들을 제공하기만 Window Application 의 작성을 위해서는 C 로 작성된 Library를 가져다 사용해야 하는 경우가 많이 발생한다.
C# 은 기본적으로 포인터를 지원하지 않고, 관리되는 코드의 메모리 관리체계가 근본적으로 C와는 차이가 많기 때문에 C 와의 호환을 위해서는 특수한 기법을 사용해야 한다. 이를 위해서 제공되는 것이 C# 의 PInvoke 와 Marshalling 이다.
1. Using C DLL (PInvoke : Platform Invocation Service)
1.1 DLL 함수의 정의
public DllImportAttribute(string dllName)
using System.Runtime.InteropServices
[DllImport(“user32”)]
public static extern int MessageBox(int hWnd, String pText, String pCaption, int uType);
1.2 DllImport 옵션
필드
|
설명
|
Calling Convention
|
DLL 내의 Export 함수에 대한 Calling Convention을 지정할 수 있다.
Cdecl, Winapi, StdCall 등을 포함하는 CallingConvention Enumerator를 지원하며, 기본값은 StdCall 이다.
|
CharSet
|
문자열에 사용할 Character Set을 설정한다.
None(자동), Unicode 값을 가질 수 있다.
|
Entry Point
|
DLL 내의 함수가 호출되는 이름을 나타낸다.
이를 이용하면 함수진입점을 지정하여, 선언시 다른 이름으로 별칭을 이용할 수도 있다.
|
// Cdecl 방식의 함수 선언
[DllImport("msvcrt.dll", CharSet=CharSet.Unicode, CallingConvention=CallingConvention.Cdecl)]
public static extern int printf(String format, int i, double d);
// StdCall 방식의 함수 선언
[DllImport(“msvcrt.dll”, CharSet=CharSet.Unicode, CallingConvention=CallingConvention.StdCall)]
public static extern int printf(String format, int i, String s);
// Unicode를 사용하도록 설정
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
public static extern int MessageBox(IntPtr hWnd, String text, String caption, uint type);
[DllImport(“user32”, CharSet = CharSet.UniCode, EntryPoint = “MessageBoxW”)]
public static extern int MsgBox(int hWnd, String pText, String pCaption, int uType);
1.3 Data Type 의 변환
C (관리되지 않는 코드)
|
C# (관리되는 코드)
|
HANDLE, void* 또는 일반 pointer
|
IntPtr
|
BYTE, unsigned char
|
Byte
|
short
|
Short
|
WORD, unsigned short
|
Ushort
|
int
|
int
|
UINT, unsigned int
|
uint
|
long
|
int
|
BOOL, long
|
int
|
DWORD, unsigned long
|
uint
|
char
|
char
|
LPSTR, char*
|
string 또는 StringBuilder
|
LPCSTR, const char*
|
string 또는 StringBuilder
|
BSTR
|
string
|
float
|
float
|
double
|
double
|
HRESULT
|
int
|
VARIANT
|
object
|
C
|
UINT GetSystemDirectory(LPTSTR lpBuffer, UINT uSize);
|
C#
|
[DllImport( "Kernel32.dll)]
public static extern int GetSystemDirectory(StringBuilder sysDirBuffer, int size);
|
C
|
char* GetLastError();
|
C#
|
[DllImport("somedll.dll"]
public static extern string GetLastError();
…
string s = GetLastError();
|
C
|
int GetLastError(char* sError);
|
C#
|
[DllImport("somedll.dll"]
public static extern int GetLastError(StringBuilder error);
…
StringBuilder sb = new StringBuilder();
GetLastError(sb);
|
2. Marshalling
Struct MyStruct { int n; char* s; }void SomeFn(MyStruct st);
2.1 StructLayoutAttribute 사용
[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Ansi)] public struct MyStruct { int n; string s; }[DllImport(“somedll.dll”] public static extern void SomeFn(MyStruct st);
[StructLayout(LayoutKind.Sequential, Pack=8)] public struct MyStruct { int n; string s; }
Struct tpstart_t { char usrname[18]; char cltname[18]; char dompwd[18]; char usrpwd[18]; int flags; }
[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Ansi)] public struct tpstart_t { [MarshalAs(UnmanagedType.ByValTStr, SizeConst=18)] public string usrname; [MarshalAs(UnmanagedType.ByValTStr, SizeConst=18)] public string cltname; [MarshalAs(UnmanagedType.ByValTStr, SizeConst=18)] public string dompwd; [MarshalAs(UnmanagedType.ByValTStr, SizeConst=18)] public string usrpwd; public int flags; }
2.2 Array Marshalling
int TestArrayOfInts(int* pArray, int pSize); int TestRefArrayOfInts(int** ppArray, int* pSize); int TestMatrixOfInts(int pMatrix[][COL_DIM], int row); int TestArrayOfStrings(char** ppStrArray, int size); int TestArrayOfStructs(MYPOINT* pPointArray, int size); int TestArrayOfStructs2 (MYPERSON* pPersonArray, int size);
[DllImport( "..\\LIB\\PinvokeLib.dll" )] public static extern int TestArrayOfInts([In, Out] int[] array, int size );[DllImport( “…\LIB\PinvokeLib.dll” )] public static extern int TestRefArrayOfInts(ref IntPtr array, ref int size );
[DllImport( “…\LIB\PinvokeLib.dll” )] public static extern int TestMatrixOfInts([In, Out] int[,] pMatrix, int row );
[DllImport( “…\LIB\PinvokeLib.dll” )] public static extern int TestArrayOfStrings([In, Out] String[] stringArray, int size );
[DllImport( “…\LIB\PinvokeLib.dll” )] public static extern int TestArrayOfStructs([In, Out] MyPoint[] pointArray, int size );
[DllImport( “…\LIB\PinvokeLib.dll” )] public static extern int TestArrayOfStructs2([In, Out] MyPerson[] personArray, int size );
3. Using Pointer
// 접속을 위한 메모리 할당 : DLL 함수 호출 int nAddr = TMaxLib.tpalloc("TPSTART", "", 0);// C Style 메모리주소를 IntPtr로 변환 IntPtr pMem = new IntPtr(nAddr);
// TMax 접속을 위한 start_t struct 생성 : StructLayout으로 정의됨 TMaxLib.tpstart_t tpinfop;
tpinfop.cltname = ClientName + ‘\0’; tpinfop.usrname = UserName + ‘\0’; tpinfop.dompwd = “\0”; tpinfop.usrpwd = “\0”; tpinfop.flags = TMaxLib.TPU_DIP;
// 마샬링을 이용하여 start_t 구조체를 할당한 Buffer로 복사 Marshal.StructureToPtr(tpinfop, pMem, true);
// T-Max 접속 if(TMaxLib.tpstart(pMem.ToInt32()) == -1) { m_sLastError = “연결에 실패하였습니다.”; return false; }
// Buffer 메모리 해제 TMaxLib.tpfree(nAddr);
AllocHGlobal
|
Unmanaged Memory 영역에 특정 바이트 만큼의 메모리를 할당한다.
C 함수에 할당된 메모리주소를 파라미터로 넘겨야 하는 경우 사용할 수 있다.
|
FreeHGlobal
|
AllocHGlobal로 할당된 메모리를 해제한다.
AllocHGlobal은 Unmanaged Memory를 할당하므로 C와 마찬가지로 직접 메모리를 Delete해야 한다.
|
Copy
|
Managed Type에서 Unmanaged type의 Pointer로 데이터를 복사한다.
|
ReadByte
|
Unmanaged Memory로부터 데이터를 바이트 단위로 Read한다.
|
WriteByte
|
Unmanaged Memory에 데이터를 바이트 단위로 Write한다.
|
SizeOf
|
Unmanaged Type의 크기를 리턴한다.
|
PtrToStructure
|
Unmanaged Type의 Memory Pointer로부터 Managed Object에 복사한다.
|
StructureToPtr
|
Managed Type의 오브젝트를 Unmanaged Memory의 Pointer에 복사한다.
|
void TestCallBack(FPTR pf, int value); void TestCallBack2(FPTR2 pf2, char* value);
public delegate bool FPtr( int value ); public delegate bool FPtr2( String value );[DllImport( “…\LIB\PinvokeLib.dll” )] public static extern void TestCallBack( FPtr cb, int value );
[DllImport( “…\LIB\PinvokeLib.dll” )] public static extern void TestCallBack2( FPtr2 cb2, String value );
…
public class App { public static void Main() { FPtr cb = new FPtr( App.DoSomething ); LibWrap.TestCallBack( cb, 99 ); FPtr2 cb2 = new FPtr2( App.DoSomething2 ); LibWrap.TestCallBack2( cb2, “abc” ); }
public static bool DoSomething( int value ) { Console.WriteLine( "\nCallback called with param: {0}", value ); } public static bool DoSomething2( String value ) { Console.WriteLine( "\nCallback called with param: {0}", value ); }
}
void SetData(DataType typ, void* object)
[DllImport( "..\\LIB\\PinvokeLib.dll" )] public static extern void SetData( DataType t, [MarshalAs(UnmanagedType.AsAny)] Object o );[DllImport( “…\LIB\PinvokeLib.dll”, EntryPoint=“SetData” )] public static extern void SetData2( DataType t, ref double i );
[DllImport( “…\LIB\PinvokeLib.dll”, EntryPoint=“SetData” )] public static extern void SetData2( DataType t, String s );
4. Using MFC
4.1 C++/CLI
- 내부적으로 C API, MFC 등 Visual C++ 의 모든 기능을 사용할 수 있다.
- C++/CLI 를 이용해서 .NET 의 모든 클래스를 사용할 수 있다.
- C++/CLI 를 이용해서 .NET 에서 참조만으로 사용 가능한 클래스를 생성할 수 있다.
4.2 C++/CLI 구문
ref class Block {}; // reference class value class Vector {}; // value class interface class I {}; // interface class ref class Shape abstract {}; // abstract class ref class Shape2D sealed: Shape{}; // derived class
public ref class Form1 : System::Windows::Forms::Form { System::ComponentModel::Container^ components; System::Windows::Forms::Button^ button1; System::Data::DataSet^ myDataSet; System::Windows::Forms::DataGrid^ myDataGrid; };
Button^ button1 = gcnew Button; // managed heap int * pi1 = new int; // native heap Int32^ pi2 = gcnew Int32; // managed heap
array
public ref class Vector sealed { double _x;public: property double x { double get() { return _x; } void set( double newx ){ _x = newx; } } };
4.3 C++/CLI DLL 만들기
#include#include
namespace FTPProxySample { public ref class FtpConnection : public IDisposable { public: FtpConnection(); ~FtpConnection(); void Open(String^ serverURL, int Port, String^ ClientName, String^ UserID, String^ Pwd); void Close();private: CInternetSession* m_pSession; CFTPConnection* m_pFtpConn; }; }
namespace FTPProxySample { FtpConnection::FtpConnection() { m_pSession = NULL; }FtpConnection::~FtpConnection() { m_pFtpConn = NULL; Close(); System::GC::SupressFinalize(this); } void FtpConnection::Open(String^ ServerURL, int Port, String^ ClientName, String^ UserID, String^ Pwd) { CString serverURL(ServerURL); CString password(Pwd); CString userID(UserID); CString clientName(ClientName); this.m_pSession = new CInternetSession(clientName); // FTP 접속 try { this.m_pFtpConn = this.m_pSession->GetFtpConnection(serverURL, userID, password, Port); } catch(CInternetException* pe) { // Exception 처리 } } void FtpConnection::Close() { if (this.m_pSession != NULL) { this.m_pSession->Close(); delete this.m_pSession; this.m_pFtpConn = NULL; this.m_pSession = NULL; } }
}
4.4 Using C++/CLI DLL
private void button1_Click(object sender, EventArgs e) { using (var ftp = new FTPProxySample.FtpConnection()) { try { ftp.Open("server-url", 21, "Test", "id", "password"); MessageBox.Show("Success"); } catch (Exception ex) { MessageBox.Show(ex.Message); } } }
감사합니다. 좋은 글 잘 읽었습니다.
답글삭제근데, "* CLR 배열 "array" 키워드를 사용한다." 바로 아래의 예제 코드가 드래그를 해야 보입니다. 아무래도 색상 처리가 잘못된 것 같은데, 약간의 옥의 티를 발견해 말씀드려요~
아무튼, 좋은 글 잘 읽었습니다.
사이트 관리에서 손을 놓고 있었더니 비슷한 의견들이 많으시네요. 조만간 사이트 갱신을 한번 하겠습니다. 감사합니다. 오늘도 좋은 하루 되세요.
답글삭제정말 정리를 잘하셨습니다. 잘 보고 갑니다.
답글삭제도움이 되셨기를 바랍니다.
삭제감사합니다. 오늘도 좋은 하루 되세요.
정리 끝판왕입니다. 감사합니다 형님!
답글삭제과찬이십니다. ^^
삭제도움이 되셨기를 바랍니다.
감사합니다. 오늘도 좋은 하루되세요.