# Five Go Interop — PRG에서 Go 패키지 직접 사용 Five의 핵심 차별점: **PRG 코드에서 Go의 전체 패키지 생태계를 직접 사용**. ## 1. IMPORT — Go 패키지 가져오기 ```prg IMPORT "strings" // Go 표준 라이브러리 IMPORT "database/sql" // 데이터베이스 IMPORT "net/http" // HTTP 서버/클라이언트 IMPORT "encoding/json" // JSON IMPORT _ "modernc.org/sqlite" // blank import (드라이버 등록용) IMPORT myhttp "net/http" // 별칭 import ``` PRG 파일 최상단에 선언. gengo가 Go import로 직접 변환. ## 2. 패키지 함수 호출 — `pkg.Func()` ```prg IMPORT "strings" IMPORT "strconv" IMPORT "fmt" PROCEDURE Main() LOCAL cResult, nVal, cFormatted cResult := strings.ToUpper("hello five!") // → "HELLO FIVE!" cResult := strings.ReplaceAll("a-b-c", "-", "_") // → "a_b_c" nVal := strconv.Atoi("42") // → 42 cFormatted := fmt.Sprintf("Name: %s, Age: %d", "Charles", 30) IF strings.HasPrefix(cResult, "HELLO") ? "starts with HELLO" ENDIF RETURN ``` ### 동작 원리 ``` PRG: strings.ToUpper("hello") ↓ gengo Go: hbrt.GoCallFunc(strings.ToUpper, _arg0) ``` - gengo가 IMPORT된 패키지 이름을 인식 - `pkg.Func(args)` → `hbrt.GoCallFunc()` reflect 호출로 변환 - 반환값은 자동으로 Harbour Value로 변환 ### 자동 타입 변환 | Go 타입 | → Harbour 타입 | |---------|---------------| | `string` | String | | `int`, `int64` | Numeric (Integer/Long) | | `float64` | Numeric (Double) | | `bool` | Logical | | `[]string`, `[]int` 등 | Array | | `map[string]interface{}` | Hash | | `error` (nil) | NIL | | `error` (non-nil) | String (에러 메시지) | | `*sql.DB` 등 포인터 | Go Object (Value로 래핑) | ## 3. Go 객체 메서드 호출 — `obj:Method()` Go 함수가 반환한 객체(포인터)는 Harbour의 `:` 문법으로 메서드 호출: ```prg IMPORT "database/sql" IMPORT _ "modernc.org/sqlite" PROCEDURE Main() LOCAL db, rows db := sql.Open("sqlite", ":memory:") // *sql.DB 반환 db:Exec("CREATE TABLE test (id INTEGER)") // *sql.DB.Exec() 호출 db:Exec("INSERT INTO test VALUES (1)") rows := db:Query("SELECT * FROM test") // *sql.Rows 반환 DO WHILE rows:Next() // *sql.Rows.Next() ? rows:Column(1) // 컬럼 값 읽기 ENDDO rows:Close() // *sql.Rows.Close() db:Close() // *sql.DB.Close() RETURN ``` ### 동작 원리 ``` PRG: db:Exec("CREATE TABLE ...") ↓ gengo Go: if hbrt.IsGoObject(_obj) { hbrt.GoCall(_obj, "Exec", _args...) // reflect 호출 } else { t.Send("Exec", 1) // Harbour 객체 호출 } ``` - 런타임에 Go 객체 vs Harbour 객체 자동 판별 - Go 객체: `reflect.MethodByName()` 으로 호출 - Harbour 객체: 기존 `Send()` 메커니즘 ## 4. 여러 Go 객체 동시 사용 ```prg IMPORT "database/sql" IMPORT _ "modernc.org/sqlite" PROCEDURE Main() LOCAL dbSource, dbTarget, aRows, i // 두 데이터베이스 동시 오픈 dbSource := sql.Open("sqlite", "source.db") dbTarget := sql.Open("sqlite", "target.db") // Source에서 읽어서 Target에 쓰기 aRows := SqlScan(dbSource, "SELECT * FROM products") FOR i := 1 TO Len(aRows) dbTarget:Exec("INSERT INTO inventory VALUES (...)") NEXT dbSource:Close() dbTarget:Close() RETURN // PRG 함수에서 Go 객체를 파라미터로 받아 사용 FUNCTION SqlScan(db, cSQL) LOCAL rows, cols, aResult, aRow, i, nCols aResult := {} rows := db:Query(cSQL) // Go *sql.Rows 반환 cols := rows:Columns() // 컬럼 이름 배열 nCols := Len(cols) DO WHILE rows:Next() aRow := {=>} FOR i := 1 TO nCols aRow[cols[i]] := rows:Column(i) NEXT AAdd(aResult, aRow) ENDDO rows:Close() RETURN aResult ``` ## 5. 배열 반환 처리 Go 함수가 슬라이스를 반환하면 자동으로 Harbour 배열로 변환: ```prg IMPORT "strings" PROCEDURE Main() LOCAL aParts, i aParts := strings.Split("one,two,three", ",") ? Len(aParts) // 3 ? aParts[1] // "one" ? aParts[2] // "two" ? aParts[3] // "three" FOR i := 1 TO Len(aParts) ? " [" + Str(i, 1) + "]", aParts[i] NEXT RETURN ``` ## 6. #pragma BEGINDUMP — 고급 사용 (선택) 복잡한 Go 로직이 필요한 경우에만 사용: ```prg PROCEDURE Main() ? MyGoFunc("hello") RETURN #pragma BEGINDUMP import "five/hbrt" func init() { hbrt.HB_FUNC("MYGOFUNC", func(ctx *hbrt.HBContext) { // 복잡한 Go 로직 s := ctx.ParC(1) ctx.RetC(strings.ToUpper(s) + "!!!") }) } #pragma ENDDUMP ``` ### HB_FUNC API (Harbour C API 호환) | Harbour C | Five Go | 설명 | |-----------|---------|------| | `HB_FUNC(NAME)` | `hbrt.HB_FUNC("NAME", fn)` | 함수 등록 | | `hb_pcount()` | `ctx.PCount()` | 파라미터 수 | | `hb_parc(n)` | `ctx.ParC(n)` | 문자열 파라미터 | | `hb_parni(n)` | `ctx.ParNI(n)` | 정수 파라미터 | | `hb_parnl(n)` | `ctx.ParNL(n)` | Long 파라미터 | | `hb_parnd(n)` | `ctx.ParND(n)` | Double 파라미터 | | `hb_parl(n)` | `ctx.ParL(n)` | 논리값 | | `hb_pards(n)` | `ctx.ParDS(n)` | 날짜 (YYYYMMDD) | | `hb_pardl(n)` | `ctx.ParDL(n)` | 날짜 (Julian) | | `HB_ISCHAR(n)` | `ctx.IsChar(n)` | 타입 체크 | | `HB_ISNUM(n)` | `ctx.IsNum(n)` | 타입 체크 | | `HB_ISLOG(n)` | `ctx.IsLog(n)` | 타입 체크 | | `HB_ISARRAY(n)` | `ctx.IsArray(n)` | 타입 체크 | | `HB_ISNIL(n)` | `ctx.IsNil(n)` | 타입 체크 | | `hb_retc(s)` | `ctx.RetC(s)` | 문자열 반환 | | `hb_retni(n)` | `ctx.RetNI(n)` | 정수 반환 | | `hb_retnl(n)` | `ctx.RetNL(n)` | Long 반환 | | `hb_retnd(d)` | `ctx.RetND(d)` | Double 반환 | | `hb_retl(b)` | `ctx.RetL(b)` | 논리값 반환 | | `hb_retds(s)` | `ctx.RetDS(s)` | 날짜 반환 | | `hb_storc(s,n)` | `ctx.StorC(s,n)` | By-ref 저장 | | `hb_storni(v,n)` | `ctx.StorNI(v,n)` | By-ref 저장 | | `hb_arrayNew()` | `ctx.ArrayNew(n)` | 배열 생성 | | `hb_arrayGet()` | `ctx.ArrayGet(v,i)` | 배열 읽기 | | `hb_arraySet()` | `ctx.ArraySet(v,i,x)` | 배열 쓰기 | | `hb_hashNew()` | `ctx.HashNew()` | 해시 생성 | ### Five 확장 API (Harbour에 없는 Go 전용) | API | 설명 | |-----|------| | `ctx.ParDate(n)` | `time.Time` 반환 | | `ctx.ParArray(n)` | `[]Value` 반환 | | `ctx.ParHash(n)` | `*HbHash` 반환 | | `ctx.RetArray(items)` | 배열 반환 | | `ctx.RetHash(h)` | 해시 반환 | | `ctx.RetVal(v)` | 임의 Value 반환 | | `hbrt.WrapGo(obj)` | Go 객체 → Value | | `hbrt.UnwrapGo(v)` | Value → Go 객체 | | `hbrt.GoCall(v, method, args...)` | reflect 메서드 호출 | ## 7. 사용 가능한 Go 패키지 예시 | 패키지 | PRG 사용 예 | |--------|-------------| | `strings` | `strings.ToUpper()`, `strings.Split()`, `strings.Contains()` | | `strconv` | `strconv.Atoi()`, `strconv.FormatFloat()` | | `fmt` | `fmt.Sprintf()` | | `database/sql` | `sql.Open()` → `db:Exec()`, `db:Query()` | | `net/http` | HTTP 서버, REST API | | `encoding/json` | JSON encode/decode | | `os` | `os.ReadFile()`, `os.Stat()` | | `path/filepath` | `filepath.Join()`, `filepath.Glob()` | | `time` | `time.Now()`, `time.Since()` | | `crypto/sha256` | 해시 함수 | | `regexp` | 정규식 | | `sort` | 정렬 | | 외부 패키지 | `modernc.org/sqlite`, `github.com/...` 등 | ## 8. 핵심 원칙 1. **IMPORT만으로 사용** — `#pragma BEGINDUMP` 불필요 2. **PRG 코드 100%** — Go 코드 0줄로 Go 기능 사용 3. **자동 타입 변환** — string/int/bool/array/hash 양방향 4. **Go 객체 투명 전달** — LOCAL 변수에 저장, `:` 로 메서드 호출 5. **Harbour 호환** — 기존 xBase 문법 그대로, Go는 백엔드