기본적으로 go 에서 외부 라이브러리를 동적으로 (.so .dll) 로드할 때 cgo 패키지를 사용합니다.

go 로 작성된 외부 라이브러리를 동적으로 로드 해주는 기본 패키지가 존재합니다.

plugin · pkg.go.dev

Plugin 패키지는 go 로 작성된 라이브러리를 동적으로 임포트하는 패키지 입니다.

플러그인 만들기

플러그인을 작성할 파일을 생성합니다. 플러그인은 go 의 기본 타입(string, int...) 와 함수를 전달할 수 있습니다.

$ touch plugin.go

플러그인을 작성합니다.

package main

// 사용하지 않지만, 없으면 컴파일이 진행되지 않음
func main() {}

// 값
var SomeVal = "hello"

// 함수
func SomeFunc() { println("hello") }

플러그인에서 노출할 값과 함수를 정의합니다. main 패키지에서 작성하며, main 함수는 사용하지 않으나, 미 작성 시 컴파일이 되지 않습니다.

플러그인을 컴파일 합니다.

# Linux
$ go build -buildmode=plugin -o plugin.so plugin.go

# Windows
$ go build -buildmode=plugin -o plugin.dll plugin.go

플러그인 로드하기

플러그인을 사용할 파일을 생성합니다.

$ touch loader.go
package main

import "plugin"

func main() {

	// 플러그인 열기
    plug, err := plugin.Open("plugin.so") //plugin.dll

	if err != nil {
		println(err.Error())
		return
	}

	// SomeVal 찾기
	symSomeVal, err := plug.Lookup("SomeVal")
	if err != nil {
		println(err.Error())
		return
	}
    
    // 형 변환
    // 포인터 형태로 값이 전달됨
	if SomeVal, ok := symSomeVal.(*string); !ok {
		println("SomeVal casting err")
		return
	} else {
		println(*SomeVal)
	}

	// SomeFunc 찾기
	symSomeFunc, err := plug.Lookup("SomeFunc")
	if err != nil {
		println(err.Error())
		return
	}

	// 형 변환
	if SomeFunc, ok := symSomeFunc.(func()); !ok {
		println("SomeFunc casting err")
		return
	} else {
		SomeFunc()
	}
}

컴파일

$ go build loader.go

실행

$ ./loader
hello
hello

구조체 전달

플러그인의 값은 main 패키지에서 정의됩니다.

gomain 패키지는 외부에서 패키지를 읽거나 볼 수 없습니다.

정확히는, pluginmain 패키지 안에 있는 구조체는 전달이 되나, loader 에서 pluginmain 패키지에 접근 할 수 없기에, 캐스팅에서 오류가 발생합니다.

panic: interface conversion: plugin.Symbol is *main.SomeStruct, not *loader.SomeStruct [recovered]
	panic: interface conversion: plugin.Symbol is *main.SomeStruct, not *loader.SomeStruct

loader 에서 같은 형태의 구조체를 정의해도 위 오류가 나타납니다.

기본적으로 main 패키지는 타 패키지가 간섭할 수 없습니다.

구조체 뿐만 아니라 type 키워드로 정의한 커스텀 타입은 사용할 수 없습니다.

이러한 문제를 해결하려면 구조체를 외부 패키지에 정의하여 사용하면 됩니다.

구조체 정의 패키지

package some_struct

type SomeStruct struct {
	Name: string
}

func (s *SomeStruct) Hi() {
	println("Hi " + s.Name)
}

플러그인

package main

import "some_struct"

var V = some_struct.SomeStruct{Name: "apple"}

func main() {}

로더

package main

import "plugin"
import "some_struct"

func main() {
	p, err := plugin.Open("plugin/path/plugin.so")
	if err != nil {
		return nil, err
	}

	s, err := p.Lookup("V")

	if err != nil {
		println(err.Error())
        return
	}
	if apple, ok := s.(*some_struct.SomeStruct); !ok {
    	println("casting error")
        return
    }
    
    println(apple.Name)
    apple.Hi()
}

예시

플러그인 예시

vompressor/go-plugin-example
Contribute to vompressor/go-plugin-example development by creating an account on GitHub.

리플랙션을 사용하여 플러그인을 쉽게 만들기 위한 라이브러리

GitHub - vompressor/vplug
Contribute to vompressor/vplug development by creating an account on GitHub.