Jul 12 2007

[C/C++]함수 포인터를 이용한 DLL과 사용 프로그램 간의 양방향 호출

분류: Lang.C_CPP 태그: ,, , Heart @ 1:35 오전

Trackback : http://dev.heartsavior.net/archives/93/trackback/

DLL을 사용한 프로그램을 구현하다 보면 단방향 호출이 상당히 불편할 때가 많다.
DLL은 dllimport/dllexport를 통해 함수 (혹은 AFX_EXT_CLASS 등으로 클래스) 를 노출시킴으로써 DLL을 사용하는 측에서 호출할 수 있지만, 역으로 DLL을 사용하는 측의 함수를 DLL에서 사용해야 할 일이 생긴다면?

이럴 때 ‘함수 포인터’를 이용하면 간단히 해결할 수 있다.

상황을 하나 설정해 보자.

DLL 내부의 동작 로직상 고유의 ID를 얻어야 하는데, 이 ID 값은 외부 프로그램에서 제공한다.
그리고 그 ID 값은 필요할 때마다 새로 생성할 수 있어야 한다.(즉, 로직 내에서 ID가 몇개 필요할지는 예측할 수 없다)

이럴 경우 ID 갯수 예측을 할 수 없기 때문에 DLL 배포함수에 전달인자로 ID 리스트를 넘기는 방법을 사용할 수가 없고, 외부 프로그램에서 ID를 생성하는 함수를 DLL 배포함수를 통해 함수 주소를 넘겨주고, DLL에서는 ID 생성이 필요할 때 마다 함수 포인터를 사용하여 외부 함수를 사용하는 방법으로 문제를 해결해야 한다.

소스 코드로 설명하자면,

// DLL 배포 함수를 모은 헤더 파트(외부 배포 및 내부 사용 겸용)

// 프로젝트에서 DLL에는 _MYDLL_을 predefine 에 꼭 넣어줄 것
#ifdef _MYDLL_
#define MY_DLL_DIST    __declspec(dllexport)
#else
#define MY_DLL_DIST    __declspec(dllimport)
#endif _MYDLL_

// ID를 얻어오는 함수를 외부 프로그램으로부터 등록받는 배포함수
void MY_DLL_DIST RegisterIDRetriveFunc( BOOL (*fpGetID)(char *pszIDBuf, int nBufLength) );

// RegisterIDRetriveFunc의 구현부

// ID를 얻어오는 함수의 포인터를 전역 변수로 저장할 것임
// 등록이 되지 않은 상태는 NULL로 표시
BOOL (*fpGetNewID)(char *pszID, int nBufLength) = NULL;

void MY_DLL_DIST RegisterIDRetriveFunc( BOOL (*fpGetID)(char *pszIDBuf, int nBufLength) )
{
// 전역 함수 포인터에 외부 함수를 등록
fpGetNewID = fpGetID;
}

// 이후에 사용할 때의 예제
// BOOL bSuccess =  (*fpGetNewID)(pszBuf, nBufLen);

// 외부 프로그램의 소스
BOOL GetID(char *pszIDBuf, int nBufLength)
{

}

// DLL 에 함수를 등록
RegisterIDRetriveFunc(&GetID);

외부 프로그램에서는 ID를 얻을 수 있는 함수의 주소를 전달인자로 하여 RegisterIDRetriveFunc()를 호출하고, DLL에서는 RegisterIDRetriveFunc의 전달인자로 넘겨받은 함수 주소를 가지고 DLL 내부에서 보관하고 있다가 필요할 때 함수 포인터 형식으로 호출하면 된다.


May 29 2007

[C/C++]DLL 외부에서 DLL 내부의 가변 크기 데이터를 가져오는 방법

분류: Lang.C_CPP 태그: ,, Heart @ 9:28 오후

Trackback : http://dev.heartsavior.net/archives/100/trackback/

DLL에서 데이터를 가져와야 하는데, 데이터가 가변적(크기가 일정하지 않음)일 경우가 있습니다.

이를 처리하는 방법은 여러 가지가 있는데, 크게 ‘DLL 외부에서 할당하고 DLL 내부에서 이를 사용하는 방법’ 과 ‘DLL 내부에서 할당하고 DLL 외부에서 이를 사용하는 방법’ 으로 나눌 수 있습니다.

전자의 경우 DLL 외부에서 충분한 사이즈의 메모리를 확보하려면 DLL 내부에 메모리를 얼마나 할당해야 하는지 요청해야 하는 번거로움이 생깁니다.(물론, 최대치를 정해버릴 수 있겠지만 가장 나쁜 방법이죠. 고정이 되어 버리기 때문에…)

후자의 경우 DLL 내부에서 사이즈의 메모리를 확보하고 DLL 외부에 메모리를 그대로 보내 주면 되기 때문에 자료를 한 번에 요청할 수 있고, DB 액세스가 필요한 경우 쿼리를 여러 번 실행할 필요가 없기 때문에 더욱 효율적일 수 있습니다.

이 때, 우리가 생각해야 하는 것 중 하나가 메모리 해제인데, DLL 내부에서 메모리를 할당하고 DLL 외부에서 이를 받아서 사용할 경우, 사용은 가능하지만 해제하게 되면 에러가 발생합니다.

이를 해결하기 위해 DLL 외부에서 DLL 내부에 메모리 해제를 요청할 수 있도록 합니다. 메모리 해제를 알려 주고 받아온 메모리 주소를 보내면 DLL 내부에서 메모리를 해제하면 됩니다.

아래는 DLL 내부의 코드와 DLL 외부의 코드입니다.

DLL 내부의 코드

// TESTDLL_API는 __declspec(dllexport)를 표현하는 define 이다.

TESTDLL_API void testMem(char ** ppData, int * nDataCount);

TESTDLL_API void testMem(char ** ppData, int * nDataCount)
{
// 사이즈를 저장할 수 있는 공간을 NULL로 할당할 때, *ppData 를 메모리 해제
if( !nDataCount )
{
delete [] *ppData;
return;
}

*ppData = new char[10];

for( int i = 0 ; i < 10 ; i++ )
{
*( *(ppData) + i) = i + ‘A’;
}

*nDataCount = 10;
}

DLL 외부의 코드

char * pData;
int nCount;

testMem(&pData, &nCount);

for( int i = 0 ; i < nCount ; i++ )
{
printf(”– %c\n”, *(pData + i));
}

testMem(&pData, NULL);

위의 두 코드에서 pData, ppData에 대해 메모리 구조(특히 포인터가 가리키는 것)를 직접 그려서 유심히 살펴보기 바랍니다.
메모리 구조만 그려볼 수 있게 되면 100% 이해하게 되는 것입니다.


Dec 22 2006

[C/C++] char *(*(**foo [][8])())[]; 를 해석할 줄 아는가?

분류: Lang.C_CPP 태그: ,, Heart @ 7:50 오전

Trackback : http://dev.heartsavior.net/archives/114/trackback/

가끔 컴퓨터학원 강사분들이나 교수님들께서 쉬어가는 식으로 책에서는 찾아보기 힘든 난해한 퀴즈를 제시한다.

본인의 경우는 컴퓨터학원에서 자바를 배울 때 클래스 내 static 블럭, static 함수, 기타 등등에 print문을 쓰고 실행 순서를 말해보자는 퀴즈를 풀었는데, 일반적인 프로그래밍을 한다면 쉽게 접하기 어려운 문제여서 꽤 감명을 받은 적이 있다.

C의 경우에는 보통 배열과 포인터, 함수 포인터를 섞어서 퀴즈를 제시하는데, 입문서만 보면서 진행한 사람이라면 풀기 상당히 힘든 문제이다. 물론 실무에서 많이 볼 일은 없지만, 이렇게 쓰는 게 오류는 아니기 때문에 알아두면 나쁠 것은 없다.(리눅스 커널에서 이런 식으로 복잡하게 엮여 있는 경우를 찾아볼 수 있다고 들었으니 쓸모도 나름대로 있다.)

이를 푸는 방법을 제시해 주는 페이지가 있어 링크를 통해 소개한다.

http://www.unixwiz.net/techtips/reading-cdecl.html

제목을 풀어내는 과정은 A hairy example에 있다.


뒷 쪽 »