라이프로그


c#에서 c++ dll의 wstring(wchar_t*)를 함수로 받아오는 방법 프로그래밍

본 내용은 조금 복잡하며, 나는 시간을 많이 들여서 글을 쓸 처지가 안돼서, 글을 읽게 되면 제법 성의 없게 느껴질 수 있음을 미리 경고한다.


==============================



c++쪽

DLL_EX_IN const wchar_t* fun(...)

참고! 여기서 DLL_EX_IN이란
#define DLL_EX_IN  __declspec(dllexport)

DLL_EX_IN은 좀 더 정확하게 하자면 사실 더 긺.
#ifdef _WIN32
#ifdef DLL_EXPORT
#define DLL_EX_IN  __declspec(dllexport)
#else
#ifdef DLL_IMPORT
#define DLL_EX_IN __declspec( dllimport )
#else
#define DLL_EX_IN
#endif
#endif
#else
#define DLL_EX_IN
#endif


아무튼 const wchar_t*로 리턴한다는 의미.

여기서 중요한 것은 wchar_t*의 메모리를 누가 어떻게 컨트롤 하냐다.
내가 권장하는 방법은 c에서 직접 컨트롤 하는 것.
예를 들어 전역 변수로 wstring을 하나 만들어서

wstring buf;

DLL_EX_IN const wchar_t* fun(...)
{
    return buf.c_str();
}

이런식으로 하는 방법이 있다.

만약 fun을 c#에서 멀티스레드로 동시 호출을 하게 된다면 이것은 문제가 될 것이다.
그런 경우에는 스레드마다 buf같은 변수가 존재해야하고, 그걸 일일이 버그없이 다뤄주려면 fun함수에 스레드번호에 해당하는 인자가 있어야 할 것.

예)
DLL_EX_IN const wchar_t * cma_analyze(int th_num,...
{
    return aCma[th_num]->jna_buffer.c_str();
}

자, 이렇게 c쪽에서 wstring의 메모리를 직접 컨트롤 하겠다면 c#에서 이를 받아서 읽는 방법이 있다.



[DllImport(UTaggerDllName, CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr cma_analyze(int th_num, [MarshalAs(UnmanagedType.LPWStr)]string sents, int max_can, int trans_lang);
이렇게 읽는다.

그런데 반환값이 IntPtr이다. 이걸 string으로 바꾸려면
Marshal.PtrToStringUni(cma_analyze(threadNum, sents, max_cand, trans_lang));
이런식으로 하면 된다.

여기서 [Marsh... sents 부분은 c#의 string을 c로 보내는 것이다. c쪽에서는

DLL_EX_IN const wchar_t * cma_analyze(int th_num, const wchar_t *line, int max_cand, int trans_lang)

이렇게 받는다.



=========================방법2. CoTaskMemAlloc 이용==================

이번엔 c#에서 메모리를 다루는 방법인데, 보기에 메모리 누수가 날 것 같이 생겼다. 그런데 안난다고 하는듯...
(실제로 엄격하게 테스트해보진 않음. 영어로 설명된것만 많아서 이해하기 힘들다.)

우선 c++쪽에서는 함수 선언쪽은 다 똑같은데

DLL_EX_IN const wchar_t* cma(...)
{
    ....
    wchar_t *buf = (wchar_t*)CoTaskMemAlloc((wcs.size() + 1) * sizeof(wchar_t));
    wcscpy(buf, wcs.c_str());
    return buf;
}
이런식으로 포인터에다가 CoTaskMemAlloc로 메모리를 할당해주고 거기에 데이터를 넣어주게된다.

c#쪽에서는 

[DllImport(UTaggerDllName, CallingConvention = CallingConvention.Cdecl)]
[return: MarshalAs(UnmanagedType.LPWStr)]
public static extern string cma( ... );

이렇게 하면 cma를 호출하면서 바로 c#용 string으로 반환값을 읽을 수 있다.

==========================================================================

양쪽 다 장단점이 있는데

방법1은 IntPtr을 이용하는 것이고, 2번은 MarshalAs(또는 CoTaskMemAlloc)를 다룬다고 말할 수 있다.

아무튼 IntPtr을 더 권장한다.
이유는 CoTaskMemAlloc가 리눅스에 없기 때문이며, 또한 CoTaskmemAlloc를 안쓰게 되면 그 함수는 자바의 JNA와 파이썬에도 사용할 수 있기 때문이다.
c++로 만든 라이브러리가 반드시 c#에서만 쓰인다고 보장된다면(도대체 그런 보장이 있을런지...) 모를까...
아니 그런 보장이 있다 하더라도 금방 사라질 수 있다. 어떻게 변할지 알 수 없으니 1번을 권장한다.

멀티스레드가 되면 조금 복잡해지게 된다지만 스레드 개수만큼의 길이를 가진 배열을 만들어서 변수들을 저장해주면 될 것.


c++에서 백터와 포인터와 참조를 다룰 때 자동 재할당을 주의해야한다 프로그래밍

예제

void tagger::RuleFinish(vector<Word_Freq> *pVecWF)

//이런 함수가 있고 보다시피 pVecWF라는 변수를 가져오는데 이를 다루기 쉽게 백터래퍼런스로 바꾸어 사용한다
vector<Word_Freq> &vecWF = *pVecWF;

//여기까진 문제가 없다. 혹여나 pVecWF의 내부 백터용량이 다 차서 재할당이 일어나도 문제는 없다.


vector<UMorp> &vecMor = *(*pVecWF)[a].pVecMorp;

//이렇게 되면 a번째의 요소인 vecMor은 pVecWF가 재할당이 일어나면 큰 문제가 발생한다. 메모리 침범을 일으킨다. 경험상 윈도우에서는 쉽게 일어나지 않지만 리눅스에서는 자주 일어난다.
//pVecWF에 insert를 추가로 하게 되면 이런 일이 발생할 수 있다. 그 때는 vecMor에 바로 접근이 안되고
// *(*pVecWF)[a].pVecMorp 를 통째로 다시 써서 접근해야 한다.
//재할당이 일어나는 것을 완벽하진 않지만 어느정도 막는 방법이 있다.

이하는 주석 명령어 //를 없이 서술함.

pVecWF에 미리 할당을 충분히 해두는 것이다. 말 그대로 "충분" 하다면 재할당이 일어나지 않으니 이론상 완벽하긴 하다.

다른 방법은  레퍼런스를 쓰지 않고 일일이 vecWF[a].pVecMorp 같은 형태로 접근하는 방법이 있다.
이게 너무 지치고 수고스럽다면, 레퍼런스가 아니라 포인터를 사용하고, 재할당이 일어날 것 같으면 포인터를 갱신해주는 방법이 있다.
레퍼런스는 그 근본값의 주소가 변경되어도 소용이 없기 때문...

gdb로 리눅스 c++ 디버깅 팁 기록 프로그래밍

gdb에 앞서 컴파일을 할 때 
-O2 빼고 -g 넣으면 됨. -g3 는 더욱 자세한 디버깅인듯.

gdb 명령어. 구글링으로 쉽게 알 수 없는 팁들을 기록.
gdb UTaggerC // UTaggerC로 디버깅 시작.
break abort // abort 될 때 멈춰줌. 이걸 해야 제대로 멈춘 줄을 알 수 있다.
또는 break exit
또는 break _exit

abort 는 메모리 침범(내가 막 지어 부르는 용어)으로 긴급정지할 때 발생하는 이름이다. 여기에다가 브레이크포인트를 설치하게 된다. 그래서 break abort를 하면 어디쯤에서 멈추는지 소스코드 상의 위치를 알 수 있다.

위 방법으로 안멈추면, 구글링 키워드 
gdb break before terminated

버그에 따라 멈추는 지점이 랜덤할 수도 있는데, 그건 이 방법으로 해결하기가 쉽지 않다.
그러나 경험상 그 문제의 지점은 그리 멀리 떨어져 있지 않다.

스택 살피기
bt

그러면 전체 스택을 보여준다. 거기서 #1 #2 같은걸 볼 수 있는데 스택 안에서의 프레임 번호라고 한다.
특정 프레임으로 이동
frame <번호>
예) frame 3

그리고 그 프레임에서 변수들을 보고 싶으면

로컬변수 보기
info locals
지역변수 보기
info args
특정변수 보기
p <변수병>
예) p i

프레임을 한 단계 올리거나 내리기.
up 또는 down


c utf8 iconv 에서 변환 불가 처리 방법 프로그래밍

- utf8에서 한 글자가 몇 바이트 차지하는지, 그 글자의 끝은 어디인지 아는 방법

1
2
3
4
5
while ( (*in_ptr & 0xc0) == 0x80)
{
in_ptr++;
in_bytes--;
}

utf8의 포맷을 이해하면 위 코드를 다양하게 응용할 수 있다.
스트링의 끝에 도달해서 널문자를 만나도 적절하게 while 중단을 해줄 것.



- utf8을 cp949로 바꾸면서 utf8에 없는 것을 ?로 바꾸는 함수
(글자 하나당 하나의 ?로 바꾼다. utf8이 글자는 1개여도 가변길이로 여러 바이트를 써도 1개의 물음표로 처리한다. )

 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
	string utf8_cp949(string str) const
{
string con_res;
con_res.resize(str.size()*2);//넉넉하게
char *out_ptr = (char*)con_res.c_str(); //입력 포인터

#if _LIBICONV_VERSION >= 0x010F
const char *in_ptr = (const char*)str.c_str();
#else
char *in_ptr = (char*)str.c_str();
#endif

size_t out_bytes = con_res.size();
size_t out_bytes_ori = out_bytes;//iconv가 out_bytes값을 수정하는데, 컨버팅한 것 만큼 줄인다. 얼마나 컨버팅했는지 알려면 out_bytes의 변화값을 알아야한다. 그러기 위해서 ori에 저장
size_t in_bytes = (str.size()) * sizeof(char);
//디버깅 팁. iconv가 인자를 어떻게 바꾸는지 알아야한다.
/*
in_ptr = 대상은 안바꾸지만 ptr 증가할 수 있다. 다 컨버팅하면 문자열 마지막까지 이동함.
in_bytes = 컨버팅하고 남은 길이로 바뀜. 컨버팅할 수록 줄어들며, 다 바꾸면 0이 됨.
out_ptr = 컨버팅하면서 ptr 증가. 항상 마지막을 가리킴.
out_bytes = 사용하고 남은 길이만큼으로 바꿈. 100이였는데 80을 사용하면 20이 남음. 그러면 100에서 20으로 바뀜.
*/
size_t result = -1;
size_t converted;

while (result == -1)//유니코드에는 있으나 cp949에는 없는 코드를 물음표?로 바꾸면서 진행한다.
{
result = iconv(icv_utf8_cp949, &in_ptr, &in_bytes, &out_ptr, &out_bytes);
converted = out_bytes_ori - out_bytes;
if (result == -1)//중간에 cp949에 없는 문자를 만난 경우를 처리한다.
{
*out_ptr = '?';

out_ptr++;
out_bytes--;
in_ptr ++ ;
in_bytes -- ;

while ( (*in_ptr & 0xc0) == 0x80)
{
in_ptr++;
in_bytes--;
}
}
}

if (result == -1)
{
perror("iconv");
}
else
{
//cout << "iconv converted : " << converted << endl;
con_res.resize(converted);
//cout << "iconv : " << con_res << endl;
return con_res;
}

return "";
}




- wchar_t의 wstring을 cp949의 string으로 바꾸는 함수
마찬가지로 변환불가는 ?로 처리한다.



 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
	string stringW2A(const wstring &wstr)const
{
// wstring -> string
//string str;
//stringW2A(wstr, str);
//return str;

//방법4 - iconv 사용
string con_res;
con_res.resize(wstr.size() * 2 + 2);//cp949는 사실 *2만 해줘도 충분하지만 넉넉하게 +2 추가.
char *out_ptr = (char*)con_res.c_str(); //입력 포인터

#if _LIBICONV_VERSION >= 0x010F
const char *in_ptr = (const char*)wstr.c_str();
#else
char *in_ptr = (char*)wstr.c_str();
#endif

size_t out_bytes = con_res.size();
size_t out_bytes_ori = out_bytes;//iconv가 out_bytes값을 수정하는데, 컨버팅한 것 만큼 줄인다. 얼마나 컨버팅했는지 알려면 out_bytes의 변화값을 알아야한다. 그러기 위해서 ori에 저장
size_t in_bytes = (wstr.size()) * sizeof(wchar_t);
//디버깅 팁. iconv가 인자를 어떻게 바꾸는지 알아야한다.
/*
in_ptr = 대상은 안바꾸지만 ptr 증가할 수 있다. 다 컨버팅하면 문자열 마지막까지 이동함.
in_bytes = 컨버팅하고 남은 길이로 바뀜. 컨버팅할 수록 줄어들며, 다 바꾸면 0이 됨.
out_ptr = 컨버팅하면서 ptr 증가. 항상 마지막을 가리킴.
out_bytes = 사용하고 남은 길이만큼으로 바꿈. 100이였는데 80을 사용하면 20이 남음. 그러면 100에서 20으로 바뀜.
*/
size_t result = -1;
size_t converted;

while (result == -1)//유니코드에는 있으나 cp949에는 없는 코드를 물음표?로 바꾸면서 진행한다.
{
result = iconv(icv, &in_ptr, &in_bytes, &out_ptr, &out_bytes);
converted = out_bytes_ori - out_bytes;
if (result == -1)//중간에 cp949에 없는 문자를 만난 경우를 처리한다.
{
*out_ptr = '?';
out_ptr++;
out_bytes--;
in_ptr += sizeof(wchar_t);
in_bytes -= sizeof(wchar_t);
}
}

if (result == -1)
{
perror("iconv");
}
else
{
//cout << "iconv converted : " << converted << endl;
con_res.resize(converted);
//cout << "iconv : " << con_res << endl;
return con_res;
}

return "";
}



위 함수들은 icv_utf8_cp949 나 icv가 선언, 해제하는 코드가 없다.

선언과 해제

icv = iconv_open("CP949//TRANSLIT", "WCHAR_T//TRANSLIT");//코드 컨버터 wchar_t(유니코드)를 cp949 char로
icv_utf8_cp949 = iconv_open("CP949//TRANSLIT", "UTF-8//TRANSLIT");


iconv_close(icv);//릴리즈. 꼭해줘야함.
iconv_close(icvA2W);//릴리즈. 꼭해줘야함.

이렇게 한다.

iconv 핸들러(icv 같은거) 들은 스레드마다 따로 있어야 한다. 멀티스레드로 작동하지 않는다.
윈도우에서는 어째서인지 멀티스레드로 사용해도 에러가 나지 않지만 리눅스에서는 바로 죽는다.

TRANSLIT 은 변환불가에 대한 적절한(?) 대체제가 있으면 그걸로 자동 대체한다는 의미다.
그런데 한자는 대체제가 없는지 ?로 해주지 않아서, 위 코드는 수동 처리한다.


이 외에 cp949를 wstring 으로 바꾸는 함수에 대해.

우선 선언고 해제
icvA2W = iconv_open("WCHAR_T//TRANSLIT", "CP949//TRANSLIT");//코드 컨버터 cp949 > wchar_t(유니코드)

iconv_close( icvA2W );

함수 나머지 소스



 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
	wstring stringA2W(const string &str)const
{
wstring wstr;
wstr.resize(str.size());
char *out_ptr = (char*)wstr.c_str(); //출력 포인터

#if _LIBICONV_VERSION >= 0x010F
const char *in_ptr = (const char*)str.c_str();
#else
char *in_ptr = (char*)str.c_str();
#endif

size_t out_bytes = wstr.size() * sizeof(wchar_t);
size_t out_bytes_ori = out_bytes;//iconv가 out_bytes값을 수정하는데, 컨버팅한 것 만큼 줄인다. 얼마나 컨버팅했는지 알려면 out_bytes의 변화값을 알아야한다. 그러기 위해서 ori에 저장
size_t in_bytes = str.size();
//디버깅 팁. iconv가 인자를 어떻게 바꾸는지 알아야한다.
/*
in_ptr = 대상은 안바꾸지만 ptr 증가할 수 있다. 다 컨버팅하면 문자열 마지막까지 이동함.
in_bytes = 컨버팅하고 남은 길이로 바뀜. 컨버팅할 수록 줄어들며, 다 바꾸면 0이 됨.
out_ptr = 컨버팅하면서 ptr 증가. 항상 마지막을 가리킴.
out_bytes = 사용하고 남은 길이만큼으로 바꿈. 100이였는데 80을 사용하면 20이 남음. 그러면 100에서 20으로 바뀜.
*/
size_t result = -1;
size_t converted;

while (result == -1)//cp949에서 유니코드로 변경중 에러가 날 경우를 처리하면서 진행.(이론상 에러는 없을 것으로 생각. 모든 cp949의 문자는 유니코드에 존재하는 것으로 안다.)
{
result = iconv(icvA2W, &in_ptr, &in_bytes, &out_ptr, &out_bytes);
converted = out_bytes_ori - out_bytes;
if (result == -1)//중간에 cp949에 없는 문자를 만난 경우를 처리한다.
{
*(wchar_t*)out_ptr = L'?';
out_ptr += sizeof(wchar_t);
out_bytes -= sizeof(wchar_t);
in_ptr++;
in_bytes--;
}
}

if (result == -1)
{
perror("iconvA2W");
}
else
{
//cout << "iconv converted : " << converted << endl;
wstr.resize(converted / sizeof(wchar_t));
//cout << "iconv : " << con_res << endl;
return wstr;
}

return L"";
}





전체 코드가 enc_cp949 라는 클래스를 기준으로 작성했었다.
코드파일 cpp h를 올리는데 이 안에는 다른 코드도 많이 있음.



사용하려면 iconv해더파일을 utf8이라는 폴더에 넣어서 써야한다.
그리고 iconv 동적라이브러리도 필요하다.


사용상의 문제는 책임지지 않음.

1 2 3 4 5 6 7 8 9 10 다음