Complete Guide to Using DLLs in OpenEuphoira
Table of Contents
TODO: ADD TOC The table of contents plugin does not seem to be working.
Why
Over the years many programs have been written that take advantage of previously written code stored in dynamically linked libraries. On Windows these libraries have a .dll extension and on Linux/Unix they often end with a .so extension. These libraries can provide functionality for your program that would otherwise take years to achieve. These features include networking, graphics, sound, GUI, and much more.
What
This is a non-trivial topic and will need to be spread out over several sections. In this section we'll build a small DLL that will start our adventure into using libraries in our Euphoria programs. In the next few sections we'll explore some real world examples.
Prerequisites
This guide has prerequisites. I believe the lists below to be complete. If any other prerequisites pop up during the evolution of this guide I'll be sure to update this section.
Knowledge
At this point it's unavoidable. Some knowledge of C is required. While expert knowledge is not required you should be familiar with C header files and C data types. Additionally, I'll assume that you understand pointers and structs as many, perhaps all, DLLs that you'll want to use will take advantage of at least one of these features, probably both.
I like using Makefiles for tasks. If you copy my examples make sure you know that line indents in a Makefile are [TAB]s not [SPACE]s
Reading
- Dynamic Linking to external code - Most of this page describes C type constants. There are only 7 functions in std/dll.e and you'll need 6 of them in this first guide.
- Machine Level Access - For this guide you'll primarily use the poke and peek functions from ste/machine.e. There are several of these and you should know what each does.
- UsingDLLs - This is a light and breezy introduction to this topic.
Tools
It is possible to use other tool chains. In fact people do it all the time. But this is the tool chain I'm using. If you prefer a different tool chain you may need to adapt the some parts of this guide to use your tools.
Additionally, I'm using Linux. I'll try hard to keep Linux-isms to a minimum as the entire concept is a near perfect match between Linux and other platforms.
How
The first step of this process is really about identifying the right library for the job. As you search for the right solution it's important to note that C is not CPP. It is possible to use CPP DLLs in OpenEuphoria but that first involves creating a C wrapper. So, if you want to skip wrapping CPP code into C wrappers, try to find the functionality you're looking for written in C in the first place.
So, What is a wrapper? "A wrapper function is a function in a computer program whose main purpose is to call a second function with little or no additional computation". In our case we are creating a DLL wrapper which I'll define as a collection of wrapper functions used to make working with DLLs practical and in some cases, possible. The goal is to translate the C header files to OpenEuphoria constructs and then use the wrapper in our programs.
Example
Full Source
guitar.h
typedef struct
{
int string_count;
char* brand;
} Guitar;
/* Initialize an new guitar struct
*
* returns a pointer to the new guitar
* */
Guitar * guitar_new();
/* Free up the guitar and release it from memory
* This function will also free up the guitar.brand memory
* */
void guitar_destroy(Guitar * guitar);
/*Sets the string_count value for the guitar
*
* Guitar * guitar The struct that needs a new string count value
* Int count the new number of string for the guitar
* */
void guitar_set_string_count(Guitar * guitar, int count);
/*Gets the string_count value for the guitar
*
* Guitar * guitar The struct holds the string count value we want
*
* returns an integer value for the number of strings.
* */
int guitar_get_string_count(Guitar * guitar);
/*Sets the brand value for the guitar.
* Frees the current brand char *
* Copies the const * brand into a new char *
* sets the guitar brand to this new copied value.
*
* Guitar * guitar The struct that needs a new brand value
* */
void guitar_set_brand(Guitar * guitar, const char * brand);
/*Gets the brand pointer for the guitar
*
* Guitar * guitar The struct holds the brand value we want
*
* returns an char * for the guitar brand.
* */
const char * guitar_get_brand(Guitar * guitar);
guitar.c
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "guitar.h"
void guitar_set_string_count(Guitar * guitar, int count)
{
guitar->string_count = count;
}
int guitar_get_string_count(Guitar * guitar)
{
return guitar->string_count;
}
void guitar_set_brand(Guitar * guitar, const char * brand)
{
if (guitar->brand != NULL)
{
free(guitar->brand);
}
char * new_brand = (char*)malloc(strlen(brand)+1);
strncpy(new_brand, brand, strlen(brand));
new_brand[strlen(brand)] = '\0';
guitar->brand = new_brand;
}
const char * guitar_get_brand(Guitar * guitar)
{
return guitar->brand;
}
void guitar_destroy(Guitar * guitar)
{
if (guitar->brand != NULL)
{
free(guitar->brand);
guitar->brand=NULL;
}
free(guitar);
}
Guitar * guitar_new()
{
Guitar * guitar;
guitar = (Guitar*)malloc(sizeof(Guitar));
memset(guitar, '\0', sizeof(Guitar));
return guitar;
}
Makefile
all:
gcc -shared -fPIC -o guitar.so guitar.c
guitar.ex
#!/usr/bin/env eui
include std/dll.e
include std/machine.e
-- Globals
atom dllid_guitar_dll,
rid_guitar_new,
rid_guitar_destroy,
rid_guitar_set_brand,
rid_guitar_get_brand,
rid_guitar_set_string_count,
rid_guitar_get_string_count
-- Wrapper functions. So using the dll seems more Euphoria like.
function guitar_new()
return c_func(rid_guitar_new, {})
end function
procedure guitar_destroy(atom guitar)
c_proc(rid_guitar_destroy, {guitar})
end procedure
procedure guitar_set_brand(atom guitar, sequence brand)
atom brand_ptr = allocate_string(brand)
c_proc(rid_guitar_set_brand, {guitar, brand_ptr} )
end procedure
function guitar_get_brand(atom guitar)
atom brand_ptr = c_func(rid_guitar_get_brand, {guitar})
sequence brand = {}
brand = peek_string(brand_ptr)
return brand
end function
procedure guitar_set_string_count(atom guitar, integer cnt)
c_proc(rid_guitar_set_string_count, {guitar, cnt} )
end procedure
function guitar_get_string_count(atom guitar)
integer cnt = c_func(rid_guitar_get_string_count, {guitar})
return cnt
end function
-- Main --
-- Make sure we can open the dll before we go any further --
dllid_guitar_dll = open_dll({"./guitar.dll", "./guitar.so"})
if dllid_guitar_dll = 0 then
puts(2, "Could not find or open dynamic lib guitar")
abort(1)
end if
-- We need an identifier that represents our c routine. This is accomplished by
-- mapping the dll function prototype (or signature if you prefer) to something
-- Euphoira will know how to handle.
rid_guitar_new = define_c_func(dllid_guitar_dll, "guitar_new", {}, C_POINTER)
rid_guitar_destroy = define_c_proc(dllid_guitar_dll, "guitar_destroy", {C_POINTER})
rid_guitar_set_brand = define_c_proc(dllid_guitar_dll, "guitar_set_brand", {C_POINTER, C_POINTER})
rid_guitar_get_brand = define_c_func(dllid_guitar_dll, "guitar_get_brand", {C_POINTER}, C_POINTER)
rid_guitar_set_string_count = define_c_proc(dllid_guitar_dll, "guitar_set_string_count", {C_POINTER, C_INT})
rid_guitar_get_string_count = define_c_func(dllid_guitar_dll, "guitar_get_string_count", {C_POINTER}, C_INT)
atom guitar = guitar_new()
guitar_set_brand(guitar, "fender")
guitar_set_string_count(guitar, 6)
printf(1, "Your guitar is a %d string %s\n", {guitar_get_string_count(guitar), guitar_get_brand(guitar)})
guitar_destroy(guitar)