[BY TIMOTHY BOGDALA]

calling c code from go

Published: April 11, 2015

This article covers how to compile Assimp, the Open Asset Import Library, for MinGW on Windows and then how to use that library from within code written in Go.

There’s a project on github by andrebq that wraps some basic Assimp functionality. But if you want more things, like animations, then you’ll still need to do some work.

I decided to take this opportunity to write a custom importer, which will pull information from Assimp, put it into a Go structure, and then write it out using gob (a binary encoding). The following snippet below is an abbreviated minimal-working-version.

Requirements

Assimp might not compile without the DirectX SDK installed. If there’s an error of S1023 at the end it’s because of a newer VC10 runtime dll. For this tutorial, that error can be ignored.

If there were other dependencies, I missed them. I already have a functioning OpenGL development platform working on Windows with Go.

Building Zlib

Zlib is a dependency for Assimp, so you can compile your own version of it using these steps:

  1. Download the zlib-1.2.8.zip source file from the website.
  2. Unzip into a temporary directory.
  3. Open a MinGW shell and cd into that directory.
  4. Compile and install!
make -f win32/Makefile.gcc BINARY_PATH=/c/MinGW/msys/1.0/bin INCLUDE_PATH=/c/MinGW/msys/1.0/include LIBRARY_PATH=/c/MinGW/msys/1.0/lib install

Building Assimp

These steps build Assimp as a DLL and not build tools and tests. I ran into problems when trying to build a static version, plus linking the static version really slowed down the builds.

  1. Download the assimp-3.1.zip file from the website.
  2. Unzip into a temporary directory.
  3. Open a MinGW shell and cd to that directory.
  4. Create a cmake build directory and cd to that.
mkdir build; cd build
  1. Build it!
cmake -G "MSYS Makefiles" -DCMAKE_INSTALL_PREFIX=/c/MinGW/msys/1.0 -ASSIMP_BUILD_STATIC_LIB=FALSE -DBUILD_SHARED_LIBS=TRUE -DASSIMP_BUILD_ASSIMP_TOOLS=FALSE -DASSIMP_BUILD_SAMPLES=FALSE -DASSIMP_BUILD_TESTS=FALSE -DCMAKE_BUILD_TYPE=RELEASE ..
make
make install

Building the Assimp Test in Go

Now, we’re ready for the fun part. The Go source code Here’s the source file in Go to do some basic querying of a model from Assimp:

package main

/*
#cgo CPPFLAGS: -I/MinGW/msys/1.0/include -std=c99
#cgo LDFLAGS: -L/MinGW/msys/1.0/lib -lassimp -lz -lstdc++

#include <stdio.h>
#include <stdlib.h>

#include <assimp/cimport.h>
#include <assimp/scene.h>
#include <assimp/mesh.h>
#include <assimp/cimport.h>
#include <assimp/matrix4x4.h>
#include <assimp/postprocess.h>

struct aiMesh* mesh_at(struct aiScene* s, unsigned int index)
{
    return s->mMeshes[index];
}

*/
import "C"
import (
    "flag"
    "fmt"
    "unsafe"
)

func main() {
    ///////////////////////////////////////////////////////////
    // process command line flags
    modelFilePtr := flag.String("mf", "model.obj", "model file")
    flag.Parse()
    modelFile := *modelFilePtr

    ///////////////////////////////////////////////////////////
    // attempt to load the file
    fmt.Printf("\n\nLoading: %s\n", modelFile)
    cModelFile := C.CString(modelFile)

    cScene := C.aiImportFile(cModelFile,
        C.aiProcess_JoinIdenticalVertices|
            C.aiProcess_Triangulate|
            C.aiProcess_GenSmoothNormals|
            C.aiProcess_CalcTangentSpace|
            C.aiProcess_FindInvalidData|
            C.aiProcess_LimitBoneWeights|
            C.aiProcess_ImproveCacheLocality|
            C.aiProcess_FixInfacingNormals|
            C.aiProcess_OptimizeMeshes|
            C.aiProcess_ValidateDataStructure)

    // make sure that we got a scene back
    if uintptr(unsafe.Pointer(cScene)) == 0 {
        fmt.Printf("Unable to load %s.\n", modelFile)
        return
    }

    fmt.Printf("Model file is loaded.\n")

    ///////////////////////////////////////////////////////////
    // write out some information about the model file
    fmt.Printf("\tMesh count: %d\n", cScene.mNumMeshes)
    fmt.Printf("\tTexture count: %d\n", cScene.mNumTextures)
    fmt.Printf("\tAnimations count: %d\n", cScene.mNumAnimations)

    ///////////////////////////////////////////////////////////
    // loop through each mesh
    for i:=uint(0); i<uint(cScene.mNumMeshes); i++ {
        cMesh := C.mesh_at(cScene, C.uint(i))

        ///////////////////////////////////////////////////////////
        // write out some information about the mesh
        fmt.Printf("Mesh index: %d\n", i)
        fmt.Printf("\tFace count: %d\n", cMesh.mNumFaces)
        fmt.Printf("\tBone count: %d\n", cMesh.mNumBones)
        fmt.Printf("\tUV component count: %d\n", cMesh.mNumUVComponents[0])
        fmt.Printf("\tMaterial index: %d\n", cMesh.mMaterialIndex)
        if cMesh.mTangents != nil {
          fmt.Printf("\tHas tangents: true\n")
        } else {
          fmt.Printf("\tHas tangents: false\n")
        }
    }
}

For an introduction into cgo and how Go interoperates with C, check out the following:

Basically, the magic happens in the comment above the import "C" line.

Each line with #cgo specifies compiler flags. In this case, I needed to explicitly set where to search for includes and libraries. I also control what libraries to link with.

The C code that follows that essentially gets embedded into the Go program and can be accessed from the C package (which is special). An example of calling a C function in Go is the mesh_at() function that is defined in C and called in Go via C.mesh_at(). The function itself was needed because I couldn’t figure out how to index aiMesh** types in Go.

The sample also shows that all of the code that was #included can be accessed from the C package (e.g. calling C.aiImportFile()).

Running this will copy the c++ library to where the program can find it (which I’m still not sure why I need to do) and then run the code. This snippit assumes you saved the above code in a file called _assimpimporter.go.

cp /c/MinGW/bin/libstdc++-6.dll .
go run assimp_importer.go -mf test.obj

You should see some output like:

Loading: c:/Users/timothy/Desktop/test.obj
Model file is loaded.
        Mesh count: 1
        Texture count: 0
        Animations count: 0
Mesh index: 0
        Face count: 246
        Bone count: 0
        UV component count: 2
        Material index: 0
        Has tangents: true
<- OLDER ENTRY .|. NEWER ENTRY ->