Pro*C 프로그래밍

2023. 3. 18. 19:26프로그래밍

728x90

Pro*C 프로그램 만들기

 

예전에 이 프로그램을 만들기 위해서 한 번 시작 해 본적이 있다. 그 때는 별 생각없이 시작한 것이, 이왕 시작 했으면 뭐라도 한 번 만들어 봐야 할 것 아닌가 하는 생각까지 발전했고, 발전 한 김에 이렇게 펼쳐 보기 시작한 결과 물이다. msys2 + MinGW 를 사용하여 윈도우에서 구성 할 수 있는 Pro*C 프로그램을 작성하는 것이 목표이었고, 어느 정도는 할 수 있을 것이란 생각이 들기 시작 하였다.

 

 

GitHub - tommybee/dynamic_proc: dynamic pro*c library

dynamic pro*c library . Contribute to tommybee/dynamic_proc development by creating an account on GitHub.

github.com

 

그러면 각각의 단계 별로 기억을 더듬어 보기로 한다.

 

InstantClient 다운 받기

우선 오라클을 사용하기 위해서는 이 클라이언트1를 받아야 한다. Basic 하고 precompile 버전을 다 받아서 둘이 합쳐야 될 듯하다. 예전 문서2에서는 precompile 버전만다운 받으라고 되어있는 데, 이 basic 버전을 받아서 합쳐주고,

그 다음빌드에 필요한 라이브러리들을 다운 받아 MinGW에 맞도록 라이브러리를 변환해서 사용하는 것이 해본 결과 맞는방법 일 것으로 보인다.

이참에 오라클 사이트에 회원으로 가입해 보는 것도 좋은 방법이다. 자바도 있고 ...

 

Precompiler 다운받기3

Instant Client Basic 다운 받기4

예전에 썼던 내용 참고하기5

 

모두 다 다운 받아서 한 디렉토리에 다 복사해 놓는다. 충돌나는 파일들이없기 때문에 이렇게 해도 무방한 듯 보인다.

 

 

MinGW 환경에 맞는 라이브러리 만들기

이렇게 다운 받은 파일들은 dll 이라서 컴파일 하기 위한 gcc 환경에서 맞는 정보가 들어 있지 않다. 따라서 내 입맞에 맞도록 라이브러리를 추출해야만 하는 작업을 한다. 예전에 썼던 문서6도 뒤적거려 보면 좋을 듯 하다.

pexports -o orasql12.dll > orasql12.def
dlltool --def orasql12.def --dllname orasql12.dll --output-lib liborasql12.a.
pexports -o oci.dll > oci.def
dlltool --def oci.def --dllname oci.dll --output-lib liboci12.a
 

위의 pexports 라는 명령어가 없으면 다음 사이트7에서 다운 받는 걸로 한다.

 

이렇게 해서 준비가 끝났다. 그럼 오라클에서 제공하는 샘플을 돌려보기로한다.

환경 설정은 다음과 같이 잡아 준다.

export PATH=$PATH:/C/DEV/COMP/OracleClient32:/C/DEV/COMP/OracleClient32/sdk
 

여기서 오라클 홈은 /C/DEV/COMP/OracleClient32 라고가정한다.

 

샘플 코드 사용하기

그러고 나면 이제 잘 돌아 가는 지 여부를 확인해 보아야 한다. 맞는 것을 찾아서 돌려보니 아래 sample.pc 가 젤 맞는 것같아서 약간 커스터마이징 해서 써보기로 한다.

 

클라이언트 다운받으면 부록으로 코드가 따라온다.

 

Sample.pc

#include <stdio.h>
#include <string.h>
#include <sqlda.h>
#include <sqlcpr.h>
#define USERID " test"
#define PASSWD " test"
#define USING "DSN=127.0.0.1;CONNTYPE=1;PORT_NO=1521"
 

EXEC SQL BEGIN DECLARE SECTION;
	VARCHAR uid[30];
	VARCHAR pwd[30];
	VARCHAR conn_opt[100];
EXEC SQL END DECLARE SECTION;

EXEC SQL INCLUDE SQLCA.H;

void main()
{
   strcpy(uid.arr,USERID);
   uid.len =strlen(uid.arr);
   strcpy(pwd.arr,PASSWD);
   pwd.len = strlen(pwd.arr);
   strcpy(conn_opt.arr, USING);
   conn_opt.len = strlen(USING);

   EXEC SQL WHENEVER SQLERROR GOTO errexit;
   EXEC SQL CONNECT :uid IDENTIFIED BY :pwd USING:conn_opt;
   printf("Connected to Oracle11g using %s/%s\n", uid.arr, pwd.arr);
   EXEC SQL COMMIT WORK RELEASE;
   return;

errexit:
   printf("Connection failed");
   return;
}  /* end of main */
 

빌드 하기

자 그럼 이 Pro*C 빌드는 두가지 과정을 거치는 데, 우선 일반적으로 확장자가 pc 인 Pro*C 파일을 만들고 나서 이를 원하는 언어로 - 이 경우에는 C - 만들어 주는 역할을 하는 것이 바로 proc 라는 실행 파일이다. 아래는 그 proc 에 의해서 소스 파일이 만들어지고, 만들어진 소스 파일을 컴파일 하고 빌드 하는 일련의 과정을 보였다.

proc iname=sample.pc include=/C/DEV/COMP/OracleClient32/precomp include=/C/DEV/COMP/OracleClient32/sdk/include include=/mingw/i686-w64-mingw32/include PARSE=PARTIAL RELEASE_CURSOR=YES MODE=ANSI CODE=ANSI_C

gcc -D_DEBUG -D_REENTRANT -c -g -r -w -O -c sample.c -o sample.o
gcc -D_CRON -D_DEBUG -o conn_demo sample.o -L/C/DEV/COMP/OracleClient32 -loci12 -lorasql12
rm sample.c
 

 

 

그런데, 이렇게 테스트 해보면 또 안될 경우가 있었다. 내 경우에는 접속이 잘 안되는 현상이 발생 하였는 데, 뭐가 잘못되도 잘 못 된 것이나, 이 문제를 해결하려고 여태 들어 봤지만,

한가지 결론에 도달한 것이 있다. 공부 잘하는 사람들이 잘 알고 있다는 그 비법.

생각하지 말자는 것이다. 나중에 경험이 쌓이면 자연스럽게 알게 되거나, 아니면 영원히 모를 수 있다는 것에 개의치 말자는 것이다.

 

그래서 접속 하는 데 문제가 생긴 듯 한데 다음과 같이 tnsnames.ora 파일을만들 듯이 연결 문자열을 작성해서 넣어 보도록 하자

#include <stdio.h>
#include <string.h>
#include <sqlda.h>
#include <sqlcpr.h>
#define SERVICE "ECS"
#define USERID "Asks_test"
#define PASSWD "Asks_test"

#define
USING "xxx/xxx@(DESCRIPTION=(ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP)(HOST=localhost)(PORT=1521)))(CONNECT_DATA=(SERVER=DEDICATED)(SERVICE_NAME=XXX)))"

static void sqlerror_hard() ;

EXEC SQL BEGIN DECLARE SECTION;
	VARCHAR conn_opt[100];
EXEC SQL END DECLARE SECTION;
 

void main()
{
           size_t buf_len, msg_len;
           char err_msg[512];
          
           strcpy(conn_opt.arr, USING);
           conn_opt.len = strlen(USING);

           EXEC SQL WHENEVER SQLERROR DO sqlerror_hard();
           EXEC SQL CONNECT :conn_opt;
           printf("Connected to Oracle11g using %s/%s\n", uid.arr, pwd.arr);

           EXEC SQL COMMIT WORK RELEASE;
           return 0;
}  /* end of main */

static void sqlerror_hard()
{
	EXEC SQL WHENEVER SQLERROR CONTINUE;
	
	printf("\nCannot connect as user %s::%s\n\n", uid.arr, conn_opt.arr);
	printf("\nORACLE error detected:");
	printf("\n%s \n", sqlca.sqlerrm.sqlerrmc);

	EXEC SQL ROLLBACK WORK RELEASE;
	
	exit(1);
}
 

아래 내용은 오라클 톰 아저씨8에게 누군가 물은 내용 중에 하나다. 위의 내용 처럼 tns 문자열로 접속하니 오히려 잘 된다.

sqlplus 'scott/tiger@(DESCRIPTION=(ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP)(HOST=localhost.localdomain)(PORT=1521)))(CONNECT_DATA=(SERVER=DEDICATED)(SERVICE_NAME=ora9ir2.kyte.com)))'
 

 

실전에 쓰일만한 라이브러리 만들기

그리고 난 다음, 이제 실제 샘플을 돌려 볼 시간 인 듯… 실전에 쓰이기는 할 수 있을 까 하는 생각이 들고 있지만, 그리고 라이브러리 자체도 현재 진행 형이다.

 

다음 사이트9에서 찾았음. 읽어 보아야 하나, 읽어 보고 싶지 않아서 코드만 사용 하기로 한다. 사실 내가 집중하는 분야도 아니고, 호기심 반 재미 반으로 하고 있다.

 

test.c 파일만 간단히 연결 문자열만 다음과 같이 바꾸어서 시작해 보는 것으로 한다. 류명환 유명한 사람인가 보다...

여하튼 Select를 데이터베이스에서 해보는 것으로 시작하려 한다.

/* --------------------------------------------------------------------------------
파일 이름 : test.c
개발 일자 : 2002-10-28
작성자 : 류명환
-------------------------------------------------------------------------------- */
….주석 생략
… 선언문 생략


int main (void)
{
	코드 생략

	ret =
		ora_connect_tns
		("test/ test@(DESCRIPTION=(ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP)(HOST=localhost)(PORT=1521)))(CONNECT_DATA=(SERVER=DEDICATED)(SERVICE_NAME=XXX)))");

	if(ret == SUCCESS)
	{
		printf("success!!!\n");
	}


	ret = ora_setstmt (stmt);

	ret = ora_select_open (&ncols);

	for (i = 1; i <= ncols; i ++)
	{
		ret = ora_select_fetch_name (data, i);
		printf ("%-10s ", data);
	}
	printf ("\n-------------------------------------------------------\n");

	for (i = 1; i <= ncols; i ++)
	{
		ret = ora_select_set_column (i, VARCHAR2, MAX_DATA_LEN);
	}

	while (ora_select_fetch_row () == SUCCESS)
	{

		if(ora_sqlcode () == FETCH_DONE) break;

		for (i = 1; i <= ncols; i ++)
		{
			ret = ora_select_fetch_data (data, i);

			printf ("%-10s", data);
		}
		printf ("\n");

	}
	ret = ora_disconnect (ROLLBACK);

	exit (EXIT_SUCCESS);
}

 

오류가 만나자고 할 경우

 

소스 코드 중에 select 시에

Error : -1002 -ORA-01002: fetch out of sequence

를 만나는 데 이는 ORA-00100 코드를 만났을 때다.

 

Oracle Error: ORA-00100
Error Description: No data found
Error Cause: An application made reference to unknown or inaccessible data.
Action: Handle this condition within the application or make appropriate modifications 
to the application code. NOTE: If the application uses Oracle-mode SQL 
instead of ANSI-mode SQL, ORA-01403 will be generated instead of ORA-00100.
 

그리고 중요한 점은 윈도우에서는 PATH에 현재 ORACLE_HOME 즉 instant client 경로가 잡혀져있어야 한다는 것이다. 나의 경우에는

Set PATH=C:\DEV\COMP\OracleClient32;%PATH%
 

이 정도 될 듯하다.

 

Insert 하기

그리고 마지막으로 Pro*C에서 코딩 후에 insert 하려고 할 때 한글이 깨질 때면 다음 사이트10를 참고 하면 된다.

 

일단 내 목적 데이터베이스의 문자 셑을 확인 해 보면,

select parameter, value from nls_database_parameters where parameter = 'NLS_CHARACTERSET';
 

다음과 같다.

그래서 필요한 변수가 NLS_LANG 이라는 변수 이고 이 변수에 다음과 같이 할당한다.

 export NLS_LANG=AMERICAN_AMERICA.KO16KSC5601
 

내가 경험 해본 결과, iconv를 사용해서 아무리 삽질을 해도 한글이깨져서 간단하게 구글링으로 문제를 해결 하였다.

 

 

결론은 광주광역시를 한번 보기가 참으로 힘들 구나 하는 것 이였다.

 

끝으로

 

이로써, Pro*C를 사용해서 오라클에 데이터를 밀어 넣을 준비가 끝났다....라고 생각했는 데, 뜻밖에 위의 실제 라이브러리 예제를 왠만히 고쳐선 안되는 것으로 생각이 든다. 물론 내용을 안읽고 소스만 그냥 커스터마이징 하고 있어서 더 그런 생각이 들기도 한다.

 

도로명 주소를 특정 오라클 데이터베이스에 밀어넣는 것이 가능한 것인가에 대한 부분에 대해서 테스트 중에 있는 데 약간의 삽질이 필요 한듯하다.

 

github11에 올린 소스들도 아직 미완성인 채로 있다. 완성이 될 수 있을 려면 일단 크기가 아니 볼륨이 좀 있는 데이터를 얼마나 빨리 집어 넣을 수 있을 것인가에 대한 문제가 걸려서 현재 진행 중에 있다.

 

부록1. Select 하기

int test_arg_auto_select (const char *tnsstr)
{
	int i;
	int ret;
	int ncols;
	char stmt [MAX_SQL_LEN];
	char data [MAX_DATA_LEN];
	strcpy (stmt, "SELECT * FROM TB_ADDR_MAP_TEMP_REF_JIBUN WHERE BJD_CD = :bjd_cd;");
	ret = ora_connect_tns(tnsstr);

	if(ret == SUCCESS)
	{
		printf("success!!!\n");
	}

	ret = ora_setstmt (stmt);
	ret = ora_bind_params (&ncols, "%s", "2911012200");

	printf ("\n-------------------ncols %d------------------------------------\n", ncols);
	for (i = 1; i <= ncols; i ++)
	{
		ret = ora_select_fetch_name (data, i);
		printf ("%-10s ", data);
	}
	printf ("\n-------------------------------------------------------\n");

	for (i = 1; i <= ncols; i ++)
	{
		ret = ora_select_set_column (i, VARCHAR2, MAX_DATA_LEN);
	}

	while (ora_select_fetch_row () == SUCCESS)
	{
		if(ora_sqlcode () == FETCH_DONE) break;
		
		for (i = 1; i <= ncols; i ++)
		{
			ret = ora_select_fetch_data (data, i);
			printf ("%-10s", data);
		}
		printf ("\n");
	}

	ret = ora_disconnect (ROLLBACK);
	exit (EXIT_SUCCESS);
}
 

부록2. Insert 하기

int test_arg_auto_insert (const char *tnsstr)
{
	int i;
	int ret;
	int ncols;
	int idx;
	char stmt [MAX_SQL_LEN];
	char data [MAX_DATA_LEN];
	char sql_qry [MAX_DATA_LEN];


	idx = sprintf(sql_qry, "%s", "INSERT INTO TB_ADDR_MAP_TEMP_REF_JIBUN ");
	idx += sprintf(sql_qry+idx, "%s", "( ");
	idx += sprintf(sql_qry+idx, "%s", " BJD_CD, ");
	idx += sprintf(sql_qry+idx, "%s", " SIDO_NM, ");
	idx += sprintf(sql_qry+idx, "%s", " SIGG_NM, ");
	idx += sprintf(sql_qry+idx, "%s", " EMD_NM, ");
	idx += sprintf(sql_qry+idx, "%s", " RI_NM, ");
	idx += sprintf(sql_qry+idx, "%s", " SAN_CK, ");
	idx += sprintf(sql_qry+idx, "%s", " JIBUN_MAIN, ");
	idx += sprintf(sql_qry+idx, "%s", " JIBUN_SUB, ");
	idx += sprintf(sql_qry+idx, "%s", " RD_CD, ");
	idx += sprintf(sql_qry+idx, "%s", " GRND_CK, ");
	idx += sprintf(sql_qry+idx, "%s", " BLDNO_MAIN, ");
	idx += sprintf(sql_qry+idx, "%s", " BLDNO_SUB, ");
	idx += sprintf(sql_qry+idx, "%s", " EMD_SERIAL, ");
	idx += sprintf(sql_qry+idx, "%s", " MOVE_CODE ");
	idx += sprintf(sql_qry+idx, "%s", ") VALUES ( ");
	idx += sprintf(sql_qry+idx, "%s", " :BJD_CD, ");
	idx += sprintf(sql_qry+idx, "%s", " :SIDO_NM, ");
	idx += sprintf(sql_qry+idx, "%s", " :SIGG_NM, ");
	idx += sprintf(sql_qry+idx, "%s", " :EMD_NM, ");
	idx += sprintf(sql_qry+idx, "%s", " :RI_NM, ");
	idx += sprintf(sql_qry+idx, "%s", " :SAN_CK, ");
	idx += sprintf(sql_qry+idx, "%s", " :JIBUN_MAIN, ");
	idx += sprintf(sql_qry+idx, "%s", " :JIBUN_SUB, ");
	idx += sprintf(sql_qry+idx, "%s", " :RD_CD, ");
	idx += sprintf(sql_qry+idx, "%s", " :GRND_CK, ");
	idx += sprintf(sql_qry+idx, "%s", " :BLDNO_MAIN, ");
	idx += sprintf(sql_qry+idx, "%s", " :BLDNO_SUB, ");
	idx += sprintf(sql_qry+idx, "%s", " :EMD_SERIAL, ");
	idx += sprintf(sql_qry+idx, "%s", " :MOVE_CODE ");
	idx += sprintf(sql_qry+idx, "%s", "); ");

	strcpy (stmt, sql_qry);

	ret = ora_connect_tns(tnsstr);

	if(ret == SUCCESS)
	{
		printf("success!!!\n");
	}

	ret = ora_setstmt (stmt);

	if(ret == SUCCESS)
	{
		printf("success!!!\n");
	}


	ret = ora_bind_u_params (
		"%s %s %s %s %s %s %s %s %s %s %s %s %s %s",
		"2911012200","광주광역시","동구","학동","",
		"0","81","0","291102009001","0","15","0","2967", "");

	ret = ora_execute (&ncols);

	if(ret == SUCCESS)
	{
		printf("insert success!!![%d]\n", ncols);
	}

	ret = ora_disconnect (ROLLBACK);
	exit (EXIT_SUCCESS);
}
 

참고로 나는 학동이 어딘지 모른다...

 

이상.


 

728x90