BlitzMax Interacting with C

Started by Midimaster, April 16, 2021, 09:57:30

Previous topic - Next topic

Midimaster

This tutorial will teach how you can use C-functions in BlitzMax and communicate with them. In the original BlitzMax helpfile there is only a single example for that. But I want to show a lot of examples for a lot of data types and also how Callback-functions are made.

With this tutorial you are also able to use third party C-files within your BlitzMax code. This means that you can use any library you find in the C-world. At the end of the tutorial you can write wrappers for this libraries.

BlitzMax already speaks "C"

You can directly write C-functions in the BlitzMax-IDE. I suggest to always use two different files: One for BMX and one for C. Lets say: "mycode.bmx" and "mycode.c". Create both in two TABs of the IDE and save them to the same directory. The inclusion is done with one simple line in the BlitzMax-code:

mycode.bmx
Code (BlitzMax) Select
Import "mycode.c"

One of the most important functions in C is printf(), which you extensivly will use for debugging. This printf() writes messages to the console as you are used it from Print in BlitzMax. So you can control, what happens on the C-side

mycode.bmx & mycode.c
Code (BlitzMax) Select
⎯⎯⎯⎯⎯ mycode.bmx ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯

Import "mycode.c"
Test

Extern "C"
Function Test()
End Extern

⎯ ✂⎯⎯ mycode.c ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯

void Test(void) {
printf("hello World");
}


If you want to call a c-function in BlitzMax you define it in a block Extern ... End Extern. Here the function has the same name like in the C-function. Take care about UpperCase! C is case sensitive. After defining it you can use it as a normal BlitzMax-function.

On the C-side you have to take care about some C-conventions. One is this void. This means nothing comes as a parameter and nothing will be returned. And never forget the semikolon at the end of each command line.

Lets see our next sample:

Sending and Receiving Parameters

In the extern definition the function expects now a parameter. Its name is only symbolic. You can choose any name.
On the C-side the parameter has the same type, but in c-convention the type is in front of the name.
Also the c-function itselft has an INT in front, which means that the functions returns an INTEGER value.

mycode.bmx & mycode.c
Code (BlitzMax) Select
⎯⎯⎯⎯⎯ mycode.bmx ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯

Import "mycode.c"
Local A:Int=3
Local E:Int = Test(A)
Print "E=" + E

Extern "C"
Function Test:Int (b:Int)
End Extern

⎯ ✂⎯⎯ mycode.c ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯

int Test(int c) {
printf("para= %i at the beginning" , c);
int d=c+5;
return d;     
}


Have a look at the printf(). This shows you how to include values in a print command. "%i" is a placeholder and means "show it as an integer". The value follows after a comma. This command does the same like BlitzMax's Print "para= " + c + " at the beginning"

To add two parameters in to a printf() simply add more parameters and placeholders:
int d=5;
char e[] = "peter";
printf("para= %i and %s ", d , e);

"%i" means "show it as an integer".  "%s" means "show it as a string".



Variable Types in BlitzMax and C
The variables you are used in BlitzMax also exist in C. To send/receive them is for some of them easy:

BlitzMax         C                      printf()
--------------------------------------------------
A:Int            int a                   %i
B:Short          unsigned short b        %i
C:Long           long long c             %i       
D:Byte           unsigned char d         %i or %c
E:Float          float e                 %f
F:Double         double f                %f
 


and for some of them the use is different and transportation complicate. Our first example is about STRINGS:

BlitzMax         C               printf()
--------------------------------------------------
S:String         char[] s                 %s
 


Strings are not the same in BlitzMax and C and cannot excanged very easy.

Here is a first example. This will show how to send a BlitzMax STRING to a selfmade C-Function:
mycode.bmx & mycode.c
Code (BlitzMax) Select
⎯⎯⎯⎯⎯ mycode.bmx ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
Import "mycode.c"
Local MyString$="abc"

Test( MyString.ToCString())

Extern "C"
Function Test(b:Byte Ptr)
End Extern

⎯ ✂⎯⎯ mycode.c ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯

void Test(char *e) {
char f[] = "peter";
printf("strings are  %s and %s \n", e,f);
}

}


On the BlitzMax-Side two steps are necessary. First is to convert the string to a C-conform string. There for we already have the given function ToCString(). The second step is to send the "Pointer to the string" instead of sending the string itself. A "pointer" is the adress where the string is in the RAM. Strings can been seen as a continuous sequence of bytes in the RAM, the C-string ends with a 0-byte. So sending the pointer to where this sequence beginns enables the C-function to re-build the string. On the C-Side we use also a pointer: a CHAR-ARRAY POINTER. This type of pointer points to a byte-array which can be handled as a string too. Also regular strings in C are always byte-arrays. But our CHAR ARRAY POINTER is not the same as the CHAR ARRAY. Although both are handled as strings in C, they are different!

You cannot do string manipulation like you are used it in BlitzMax. You cannot write something like:
// don't do it!!!
char f[] = e;
char f[] = *e;
f[] = e[];
f = e;
...;



Returning Strings To BlitzMax

You can return Strings you sended as parameter  to the C-function. And you also can return strings, that you  defined with a pointer in C. But you cannot return "normal" Strings from a C-function:

this works:
mycode.bmx & mycode.c
Code (BlitzMax) Select
⎯⎯⎯⎯⎯ mycode.bmx ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯

Import "mycode.c"
Local MyString$="abc"

Local e:Byte Ptr = Test( MyString.ToCString())
Print "backstring=" + String.FromCString(e)
Extern "C"
Function Test:Byte Ptr(b:Byte Ptr)
End Extern

⎯ ✂⎯⎯ mycode.c ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯

char* Test(char *e) {
return e;
}




This works too:
mycode.bmx & mycode.c
Code (BlitzMax) Select
⎯⎯⎯⎯⎯ mycode.bmx ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
...same
⎯ ✂⎯⎯ mycode.c ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
char* Test(void) {
char *f = "peter";
return f;
}


This not:

mycode.bmx & mycode.c
Code (BlitzMax) Select
⎯⎯⎯⎯⎯ mycode.bmx ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
...same
⎯ ✂⎯⎯ mycode.c ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
char* Test(void) {
char f[] = "peter";
return f;
}





... to be continued






mycode.bmx & mycode.c
Code (BlitzMax) Select
⎯⎯⎯⎯⎯ mycode.bmx ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯

⎯ ✂⎯⎯ mycode.c ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯


...on the way to Egypt

Midimaster

Our next chapter shows how to return values from C to BlitzMax.

Receive one value

This is easy. A C-function allows to return one value. This is exact the same, what blitzMax offers too.

You can return INTEGER, SHORT, BYTE, LONG, FLOAT, DOUBLE and also POINTERs of this values.
On the BlitzMax-Side we can receive INTEGER, unsigned SHORT, unsigned BYTE, LONG, FLOAT, DOUBLE and only Byte Ptr

The way to do is always the same. Your function, the Extern Call and the C-function should be of the same type.

mycode.bmx & mycode.c
Code (BlitzMax) Select

⎯⎯⎯⎯⎯ mycode.bmx ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯

    Import "getback.c"
    Local A:Int = GetBack()
    Print "A=" + A
     
    Extern "C"
            Function GetBack:Int () ="test"
    End Extern

⎯ ✂⎯⎯ mycode.c ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
     
    int test() {
           int d=5;
           return d;   
}


Be careful! The type definitions in C must be LOWER CASE always! This...
   Int test() {
...

would cause an error !

To enable a blitzmax user to use different names for his function call and the C-function name you can add a "name translation" in the Extern part!

In our example the BlitzMax's C-function-name seems to be GetBack(), but in reality it is test().

Now the same example for FLOAT would look like this:
mycode.bmx & mycode.c
Code (BlitzMax) Select

⎯⎯⎯⎯⎯ mycode.bmx ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯

    Import "getback.c"
    Local A:Float = GetBack()
    Print "A=" + A
     
    Extern "C"
            Function GetBack:Float () ="test"
    End Extern

⎯ ✂⎯⎯ mycode.c ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
     
    float test() {
           float d=5;
           return d;
}   



Receive more  value


This needs a pratice you may already know with VAR in BlitzMax:
Code (BlitzMax) Select
Function ReturnVar(abc Var)
abc=abc+111
End Function

This adds 111 to abc also outside the function.

To do the same in C you need to work with pointers . A Pointer is the adress of a variable. If you work with pointers inside functions C uses the original RAM where the variable is to do its job, instead of working with a copy that only contains the value of the variable.

To force this from BlitzMax we have to use pointers too:
mycode.bmx & mycode.c
Code (BlitzMax) Select

⎯⎯⎯⎯⎯ mycode.bmx ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯

Local x:Int=5 , y:Int=10
Print "X=" + x + "  Y=" + y

Local A:Int = ReturnMore( Varptr(x) , Varptr(y) )
Print "X=" + x + "  Y=" + y

Extern "C"
Function ReturnMore:Int(a:Int Ptr , B:Int Ptr) ="test"
End Extern

⎯ ✂⎯⎯ mycode.c ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
     
void test(int *c , int *d ) {
*c = *c + 10;
*d = *d + 10;
}


VarPtr is the pointer to the variable
Int Ptr is a placeholder to transfer really INTEGERs
int * is a INTEGER pointer in C-language

To calculate in C you need to add the asterix "*" to the variable names .


Receive a big RAM area with values

If you have a block of values, which you want to manipulate, send the pointer to the first value and then take care in C not to exceed the limits of the area.

In this example we transfer the data-part of a TAudioSample to C-function to reset it to zero.
mycode.bmx & mycode.c
Code (BlitzMax) Select

⎯⎯⎯⎯⎯ mycode.bmx ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯

Import "getback.c"

Local Sample:TAudioSample=CreateAudioSample(12345,44100,SF_MONO16LE)
ReturnMore (Sample.Samples, Sample.Length)


Extern "C"
Function ReturnMore:Int(a:Short Ptr, B:Int) ="test"
End Extern

⎯ ✂⎯⎯ mycode.c ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
     
void test(short *SampleRAM, int size){
for (int i=0; i<size; i++){
SampleRAM[i] = 0;
}

As Sample.Samples is already a pointer we need not to write VarPtr() an the BlitzMax-side.

On the C-side you can handle pointers like Arrays. SampleRAM[0] shows the value at the beginning of the Data-field. SampleRAM[1] shows the value 2Bytes away from of the beginning of the Data-field, because the pointer is defined SHORT.


to be continued....



...on the way to Egypt

Midimaster

Save a C-STRUCT in BlitzMax
In our last chapter we learned how to send a BlitzMAx RAM-area to C to manipulate it and afterwards to get it back.

But sometimes a third-party C-function wants to return something, which is completely new and unknown for BlitzMax. A good example for this would be a STRUCT of C, which the one C-function creates and another C-function needs this STRUCT later, but you (BlitzMax) does not know anything about it...

STRUCTs
STRUCTs are like TYPEs in BlitzMax: A combination of various variables and further nested STRUCTS.

something like this:
typedef struct
{
    ma_format formatIn;
    ma_format formatOut;
    ma_uint32 channelsIn;
    ...
    ma_dither_mode ditherMode;
    ma_channel_mix_mode channelMixMode;
    float channelWeights[MA_MAX_CHANNELS][MA_MAX_CHANNELS];
    struct
    {
        ma_resample_algorithm algorithm;
        ma_bool32 allowDynamicSampleRate;
        struct
        {
            ma_uint32 lpfOrder;
            double lpfNyquistFactor;
        } linear;
        struct
        {
            int quality;
        } speex;
    } resampling;
} data_converter;


...we have no idea what that means! But we need to save this for a call of another C-function. This is also possible with pointers.

This STRUCTs come always together with a C-function that creates the content and returns not a variable, but this STRUCT:
STRUCT data_converter anycontent(){
      STRUCT data_converter abc;
      abc.formatIn=123;
      abc. channelsIn=456;
      .....
      return abc; 
}

This is a given function anycontent(), which returns abc as  STRUCT of type data_converter.

This all look very strange to us, but we dont need to understand whats happening. We only need to store this into the BlitzMax world. Therefore we write a "wrapper". A wrapper is another C-function that prepares the given C-function for sending it to the BlitzMax-side:

struct *data_converter mywrapper(){
      struct data_converter *bbb;
      int length = sizeof(struct data_converter);
      bbb = (struct data_converter*) malloc(length);
      *bbb = anycontent();
      return bbb; 
}

struct data_converter anycontent(){
      struct data_converter abc;
      abc.formatIn=123;
      abc. channelsIn=456;
      .....
      return abc; 
}


This is a wrapper for the function anycontent(). The anycontent() returns a STRUCT of type data_converter. The wrapper returns this STRUCT as  a "pointer to the content of bbb".

you need three steps to convert abc into bbb:

first define the variable bbb as pointer of type data_converter
struct data_converter *bbb;

second step is to find out the size of STRUCTs like data_converter:
int length = sizeof(struct data_converter);

third step is to allocate memory for our bbb
bbb = (struct data_converter*) malloc(length);

last step is to fill the bbb with the given function:
*bbb = anycontent();

Now we can send this to BlitzMax.
Code (BlitzMax) Select

Content:Byte Ptr = AnyContent()

Extern "C"
Function AnyContent:Byte Ptr() = "mywrapper"
End Extern


For BlitzMax this is a area of RAM with a given size. It does not know anything about the content or the structure of this content. (With BlitzMax NG you would have the chance to could build STRUCTs also on the BlitzMax-side... but this is another chapter.)




...on the way to Egypt

zelda64bit

Is this tutorial finished ?, I say it to translate it with the translator and copy it to study it in the near future, when I know more about bliztmax.

Midimaster

no... will be continued soon with next topic "Callback-functions". This tutorial grows with my skills in learning "how to write a wrapper"...

...on the way to Egypt

Baggey

Please Continue  ;D

Watching and learning with Interest.

Kind Regards Baggey
Running a PC that just Aint fast enough!? i7 Quad core 16GB ram 1TB SSD and NVIDIA Quadro K620 . DID Technology stop! Or have we been assimulated!

ZX Spectrum 48k, C64, ORIC Atmos 48K, Enterprise 128K, The SID chip. Im Misunderstood!