|
|
## P/Invoke 의 이해 |
|
|
## **P/Invoke의 이해**
|
|
|
---
|
|
|
|
|
|
### **P/Invoke**
|
|
|
**P/Invoke(Platform Invocation)**는 **Managed Code**에서 DLL로 구현된 **Unmanaged Code**를 사용할 수 있도록 기능을 제공한다.
|
|
|
|
|
|
> System.Runtime.InteropServices
|
|
|
|
|
|
```
|
|
|
[DllImport("example.dll", EntryPoint = "example_printInfo", CallingConvention = CallingConvention.Cdecl)]
|
|
|
static extern void printInfo();
|
|
|
```
|
|
|
* **DllImport()**에 DLL 파일명을 지정 후 `static`과 `extern` 키워드를 사용하여 Method를 정의한다.
|
|
|
* **EntryPoint** : Method를 지정하면 다른 이름으로 정의할 수 있다.
|
|
|
* **CallingConvention** : Method를 호출하는 데 필요한 규칙을 지정한다. (CallingConvention.Cdecl : 매개변수 사용을 허용)
|
|
|
|
|
|
---
|
|
|
|
|
|
```
|
|
|
// example.dll 내용
|
|
|
|
|
|
#include <stdio.h>
|
|
|
|
|
|
extern "C"
|
|
|
{
|
|
|
__declspec(dllexport) void printInfo()
|
|
|
{
|
|
|
printf("P/Invoke Example\n");
|
|
|
}
|
|
|
|
|
|
__declspec(dllexport) int computeArea(int width, int height)
|
|
|
{
|
|
|
return width * height;
|
|
|
}
|
|
|
}
|
|
|
```
|
|
|
```
|
|
|
// C#
|
|
|
|
|
|
[DllImport("example.dll", EntryPoint = "printInfo")] // 사용자 정의 DLL
|
|
|
static extern void exampleInfo();
|
|
|
|
|
|
[DllImport("example.dll", CallingConvention = CallingConvention.Cdecl)]
|
|
|
static extern int computeArea(int width, int height);
|
|
|
|
|
|
[DllImport("user32.dll", CallingConvention = CallingConvention.Cdecl)] // WinAPI 관련 DLL
|
|
|
static extern int wsprintf([Out] StringBuilder lpOut, string lpFmt, int x);
|
|
|
|
|
|
static void Main()
|
|
|
{
|
|
|
exampleInfo(); // example.dll
|
|
|
|
|
|
int area = computeArea(3, 5); // example.dll
|
|
|
|
|
|
StringBuilder buffer = new StringBuilder();
|
|
|
wsprintf(buffer, "Area : %d", area); // user32.dll
|
|
|
|
|
|
Console.WriteLine(buffer.ToString());
|
|
|
}
|
|
|
```
|
|
|
```
|
|
|
출력 결과 :
|
|
|
P/Invoke Example
|
|
|
Area : 15
|
|
|
```
|
|
|
|
|
|
### **Marshaling**
|
|
|
**Marshaling**은 한 언어로 작성된 프로그램의 표현방식을 다른 언어에 적합하게 다른 형식으로 변환하는 과정을 말한다.
|
|
|
|
|
|
### **Marshal Method**
|
|
|
| **멤버** | **설명** |
|
|
|
| -------- | -------- |
|
|
|
| Marshal.AllocHGlobal() | Unmanaged 메모리에 지정된 크기의 공간을 할당 |
|
|
|
| Marshal.FreeHGlobal() | Unmanaged 메모리에 할당된 공간을 해제 |
|
|
|
| Marshal.ReadInt32() | Unmanaged 메모리에서 32bit 부호 있는 정수를 읽음 |
|
|
|
| Marshal.ReadByte() | Unmanaged 메모리에서 byte 형식의 변수를 읽음 |
|
|
|
| Marshal.Copy() | Unmanaged 메모리의 Point를 Managed 형식의 배열에 또는 그 반대로 값을 복사 |
|
|
|
| Marshal.SizeOf() | Unmanaged 형식의 변수 크기를 byte 단위로 반환 |
|
|
|
|
|
|
#### **예시**
|
|
|
|
|
|
```
|
|
|
int intSize = 4;
|
|
|
IntPtr unmanagedArray = Marshal.AllocHGlobal(5 * intSize);
|
|
|
|
|
|
int[] managedArray = { 1, 2, 3, 4, 5 };
|
|
|
|
|
|
Marshal.Copy(managedArray, 0, unmanagedArray, managedArray.Length); // managedArray -> unmanagedArray
|
|
|
|
|
|
for (int i = 0; i < 5; i++)
|
|
|
{
|
|
|
int result = Marshal.ReadInt32(unmanagedArray, i * intSize);
|
|
|
|
|
|
Console.WriteLine(result);
|
|
|
}
|
|
|
|
|
|
int ptrSize = Marshal.SizeOf(unmanagedArray); // 4
|
|
|
|
|
|
int[] managedArrayCopy = new int[managedArray.Length];
|
|
|
Marshal.Copy(unmanagedArray, managedArrayCopy, 0, 5 * ptrSize); // unmanagedArray -> managedArrayCopy
|
|
|
|
|
|
Marshal.FreeHGlobal(unmanagedArray);
|
|
|
```
|
|
|
|
|
|
## **예제 분석**
|
|
|
### **U8Conv.StringFromUtf8Ptr()**
|
|
|
```
|
|
|
public static string StringFromUtf8Ptr(IntPtr ptr) // ptr is null-terminated
|
|
|
{
|
|
|
if (ptr == IntPtr.Zero)
|
|
|
return "";
|
|
|
|
|
|
int len = 0;
|
|
|
while (Marshal.ReadByte(ptr, len) != 0) len++;
|
|
|
if (len == 0)
|
|
|
return "";
|
|
|
|
|
|
byte[] array = new byte[len];
|
|
|
Marshal.Copy(ptr, array, 0, len);
|
|
|
|
|
|
return Encoding.UTF8.GetString(array);
|
|
|
}
|
|
|
|
|
|
```
|
|
|
* `IntPtr`은 Point를 나타내는 형식이다. 매개변수를 `IntPtr` 형식인 ptr로 받는다.
|
|
|
* ptr을 `byte` 단위로 읽어서 0이 되기 전까지 len을 증가시켜 문자열의 길이를 구한다.
|
|
|
* Managed byte 배열에 Unmanaged Point 변수에 저장된 값을 지정한 길이만큼 복사한다.
|
|
|
|
|
|
### **U8Conv.Utf8BytesFromString()**
|
|
|
```
|
|
|
public static byte[] Utf8BytesFromString(string s)
|
|
|
{
|
|
|
var cnt = Encoding.UTF8.GetMaxByteCount(s.Length);
|
|
|
byte[] buf = new byte[cnt + 1];
|
|
|
Encoding.UTF8.GetBytes(s, 0, s.Length, buf, 0);
|
|
|
return buf;
|
|
|
}
|
|
|
```
|
|
|
* Unmanaged 문자열에는 배열의 끝 요소에 0('\0')이 저장되어야 하므로 cnt보다 1 증가한 크기의 byte 배열인 buf를 생성한다.
|
|
|
* string 형식의 변수 s를 byte로 Encoding하여 buf에 저장한 후 반환한다.
|
|
|
|
|
|
### **Kai.DisplayStr()**
|
|
|
```
|
|
|
if (bufsize == 0)
|
|
|
bufsize = Kai.OneRowSize;
|
|
|
|
|
|
IntPtr buf = IntPtr.Zero;
|
|
|
```
|
|
|
* bufsize가 0이면 Kzi.OneRowSize(1024)로 초기화한다.
|
|
|
* buf를 IntPtr.Zero의 값인 0으로 초기화한다.
|
|
|
|
|
|
---
|
|
|
|
|
|
```
|
|
|
try
|
|
|
{
|
|
|
buf = Marshal.AllocHGlobal(bufsize + 1);
|
|
|
kai_display_str(id, buf, bufsize);
|
|
|
return U8Conv.StringFromUtf8Ptr(buf);
|
|
|
}
|
|
|
```
|
|
|
* Unmanaged 메모리에 bufsize+1 크기만큼의 buf를 할당한다.
|
|
|
* 해당 kai ID(id)의 데이터를 string으로 buffer(buf)에 표시한다.
|
|
|
* U8Conv.StringFromUtf8Ptr()에 매개변수로 buf를 넘긴 후 string 형식으로 반환한다.
|
|
|
|
|
|
---
|
|
|
|
|
|
```
|
|
|
finally
|
|
|
{
|
|
|
Marshal.FreeHGlobal(buf);
|
|
|
}
|
|
|
```
|
|
|
* return 반환 후 Unmanaged 메모리에 할당된 buf를 해제한다.
|
|
|
|
|
|
### **Kai.CellByName()**
|
|
|
```
|
|
|
[DllImport("kai_d.dll", EntryPoint = "kai_cell_byname", CallingConvention = CallingConvention.Cdecl)]
|
|
|
private static extern IntPtr kai_cell_byname(int id, byte[] col_name, int row);
|
|
|
```
|
|
|
* 'kai_d.dll'에 포함된 kai_cell_byname()를 사용하기 위해 DllImport로 정의한 것이다.
|
|
|
|
|
|
---
|
|
|
|
|
|
```
|
|
|
public static string CellByName(int id, string col_name, int row)
|
|
|
{
|
|
|
var col = U8Conv.Utf8BytesFromString(col_name);
|
|
|
var ptr = kai_cell_byname(id, col, row);
|
|
|
return U8Conv.StringFromUtf8Ptr(ptr);
|
|
|
}
|
|
|
```
|
|
|
* string 형식의 col_name을 U8Conv.Utf8BytesFromString()의 매개변수로 넘긴 후 byte 배열로 반환하여 col에 저장한다.
|
|
|
* kai_cell_byname()를 사용하여 해당 Kai ID(id)에서 byte 배열(col)의 row 번째 Cell 내용을 반환하여 ptr에 저장한다.
|
|
|
* ptr은 Unmanaged Point이므로 IntPtr 형식으로 저장되어 있을 것이다. 이를 U8Conv.StringFromUtf8Ptr()의 매개변수로 넘긴 후 string 형식으로 반환한다.
|
|
|
|
|
|
### **Kai.row_howmany()**
|
|
|
```
|
|
|
[DllImport("kai_d.dll", EntryPoint = "kai_row_howmany", CallingConvention = CallingConvention.Cdecl)]
|
|
|
public static extern int row_howmany(int id, out int rows);
|
|
|
```
|
|
|
* 'kai_d.dll'에 포함된 kai_row_howmany()를 row_howmany()로 사용하기 위해 EntryPoint 속성을 지정하여 DllImport로 정의한 것이다.
|
|
|
|
|
|
---
|
|
|
|
|
|
```
|
|
|
// kai_d.dll
|
|
|
|
|
|
int kai_row_howmany(int id, int *howmany) { /* 내용 */ }
|
|
|
```
|
|
|
* kai_row_howmany()은 해당 Kai ID(id)에서 howmany의 row 수를 세어 반환하는 Method이다.
|
|
|
* kai_row_howmany()의 선언 부분에는 매개변수 howmamy를 Point로 받아온다. 이를 C#에서 사용하기 위해 정의할 때 `out` 키워드를 사용하여 매개변수를 Call By Reference로 넘겨준다.
|
|
|
|
|
|
### **Metic.init(), Metic.Init()**
|
|
|
```
|
|
|
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
|
|
public delegate void Connection(metic_status status, int sv_info_kai);
|
|
|
|
|
|
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
|
|
public delegate void LoginRsp(int success, int info_kai);
|
|
|
```
|
|
|
* Unmanaged Function Pointer를 사용하기 위해 Managed Code의 Delegate로 정의한 것이다.
|
|
|
|
|
|
---
|
|
|
|
|
|
```
|
|
|
[DllImport("metic_d.dll", EntryPoint = "metic_init", CallingConvention = CallingConvention.Cdecl)]
|
|
|
public static extern int init(byte[] server_ip, ushort server_port, int init_conn_try_time, Connection conn_cb, LoginRsp login_rsp_cb);
|
|
|
```
|
|
|
* 'metic_d.dll'에 포함된 metic_init()을 init()으로 사용하기 위해 EntryPoint 속성을 지정하여 정의한 것이다.
|
|
|
|
|
|
---
|
|
|
|
|
|
```
|
|
|
public static int Init(string serverIP, ushort serverPort, int connTryTime, Connection connChange, LoginRsp loginRsp)
|
|
|
{
|
|
|
var server_ip = U8Conv.Utf8BytesFromString(serverIP);
|
|
|
return init(server_ip, serverPort, connTryTime, connChange, loginRsp);
|
|
|
}
|
|
|
```
|
|
|
* metic을 초기화하는 데 필요한 정보를 매개변수로 받아서 init()을 호출시킨다.
|
|
|
|
|
|
---
|
|
|
|
|
|
```
|
|
|
// metic_d.dll
|
|
|
|
|
|
void conn_cb(enum metic_status status, int sv_info_kai) { /* 내용 */ }
|
|
|
void login_rsp_cb(int success, int info_kai) { /* 내용 */ }
|
|
|
|
|
|
int metic_init(char *server_ip, unsigned short server_port, int init_conn_try_time,
|
|
|
void (*conn_cb)(enum metic_status status, int sv_info_kai),
|
|
|
void (*login_rsp_cb)(int success, int info_kai)) { /* 내용 */ }
|
|
|
```
|
|
|
* metic_init() 매개변수로 받은 정보를 이용해 metic을 초기화하는 Method이다. 정상적으로 연결되면 1을 반환하고, 실패하면 0을 반환한다.
|
|
|
* 매개변수에서 char 형식의 Point로 선언된 server_ip는 C#에서 string 형식으로 대체할 수 있다. 또한, unsigned short 형식의 server_port도 ushourt로 대체하여 사용한다.
|
|
|
* Function Pointr로 선언된 conn_cb와 login_rsp_cb는 Delegate로 선언된 Connection과 LoginRsp로 대체하여 사용한다. 이와 같은 형식의 Method를 매개변수로 받아서 사용할 수 있다. |
|
|
\ No newline at end of file |