How to write a wrapper?

Started by Midimaster, April 01, 2021, 14:59:29

Previous topic - Next topic

col

In c/cpp you cannot use a type unless the compiler has seen it first (hence the arcane header system)
This should work:


typedef enum
{
    ma_device_type_playback = 1,
    ma_device_type_capture  = 2,
    ma_device_type_duplex   = 3,
    ma_device_type_loopback = 4
} ma_device_type;

void SendEnun(ma_device_type deviceType) { // Typo here - should be 'SendEnum'?
}


Btw you can use an Int on the BMax side for an enum value.
https://github.com/davecamp

"When you observe the world through social media, you lose your faith in it."

Midimaster

 ;D I'm so stupid! Thank you. I did not see the typo.

and you are right: when I set the definition at the beginning it works.

Also in the "MidiAudio.h" the definition is on line 3181 and the function on 4773

corrected code:
typedef enum
{
    ma_device_type_playback = 1,
    ma_device_type_capture  = 2,
    ma_device_type_duplex   = 3,
    ma_device_type_loopback = 4
} ma_device_type;

void SendEnum(ma_device_type deviceType) {
}



This means, that all my additional C-glue-functions should be below line 65.000? The "MiniAudio.h" is extrem big, because there are so many comments in it. I didn't notice it at first. So I thought the main() loop is in line 43. But now I see, this is only a comment, how a main loop could look like.

By the way... two questions:
1.
How do I tag a code box here in the forum for "C". It work with "=BlitzMax", but if I use "=C" the box appears with a title "Unknowm language". Where can I read which languages are accepted?

2.
Inside the "MiniAudio.h" inside comments the author uses this:
Code (BlitzMax) Select
...
but you could allocate it on the heap if that suits your situation better.

    ```c
    void data_callback(ma_device* pDevice, void* pOutput, const void* pInput, ma_uint32 frameCount)
    {
     ....
    }
    ```
In the....

I talk about the lines #4 and #9... Is this something special? A directive for the compiler?
...crossing the alps with my bike at the moment

col

#17
If the enum and function are defined in the header then #include'ing the header should let you use them and there's no need to define them again. Technically speaking after you have #include'd the header all of your c code will be seen after the last line of the header. The preprocessor will paste the contents of the #include file into your c file during compilation at the #include line.

When it comes to using single source header files there are usually some kind of #define(s) to set something to expose the implementation separate from the definition. Is there anything in the documentation that says anything related to this?

I'm not sure on question 1.
Answer to question 2 is related to html viewers. For eg Github uses the 3 tick approach to mark sections of text as code. I'm not sure what the name of that style is called - I know its some kind of html markdown but I don't know the name.
https://github.com/davecamp

"When you observe the world through social media, you lose your faith in it."

Midimaster

I'm struggling with your code in your post#4

Quote from: col on April 02, 2021, 06:26:21
...
The following is an example of create a c object and 'wrapping' it in a BMax object....

C code


// put into MiniAudio.h
struct MiniAudio {
   int Value;
};

// Put into MiniAudio_Glue.c
struct MiniAudio *MiniAudio_Create() {
   struct MiniAudio *pMiniAudio;
   pMiniAudio = (struct MiniAudio*)malloc(sizeof(struct MiniAudio));
   pMiniAudio->Value = 0;
   return pMiniAudio;
}


I was able to understand and intergrate this into my learn.c:

learn.c
#include "learn.h"

struct ma_device_config *TestStart(int x) {
struct ma_device_config *loc;
int length = sizeof(struct ma_device_config);
loc= (struct ma_device_config*) malloc(length);
return loc;
}


learn.h
...
struct ma_device_config
{
    ma_device_type deviceType;
    int sampleRate;
};


Now I can allocate a RAM in BlitzMax for use as ma_device_config. That works without errors

But I would need to interact with a given function in "learn.h" which returns not the pointer but the STRUCT itsself:

learn.c
#include "learn.h"

struct ma_device_config *TestStart_2(int x) {
struct ma_device_config *loc;
int length = sizeof(struct ma_device_config);
loc= StartDevice(1);
return loc;
}


learn.h
typedef enum
{
    ma_device_type_playback = 1,
    ma_device_type_capture  = 2,
    ma_device_type_duplex   = 3,
    ma_device_type_loopback = 4
} ma_device_type;

struct ma_device_config
{
    ma_device_type deviceType;
    int sampleRate;
};


// example for how MiniAudio uses the STRUCT
struct ma_device_config StartDevice(ma_device_type deviceType) {
struct ma_device_config config;
return config;
}


And this does not work. The error message is of course:
C:/BasicNG/FreeAudio/learnb.c: In function 'Test2Start':
C:/BasicNG/FreeAudio/learnb.c:14:5: error: incompatible types when assigning to type 'struct ma_device_config *' from type 'struct ma_device_config'
  loc= StartDevice(1);
     ^


But how to change to pointer?


By the way....
the syntax of the code line...
loc= (struct ma_device_config*) malloc(length);
... is very myteriouse for me. I understand that this allocates the amout of RAM at the adress of loc. But why these brackets and why this asterix?
...crossing the alps with my bike at the moment

Henri

#19
Quote... is very myteriouse for me. I understand that this allocates the amout of RAM at the adress of loc. But why these brackets and why this asterix?

Brackets are for typecasting. Malloc returns a void pointer (base pointer) which then needs to be typecast-ed to appropriate pointer type. Asterisk (*) just means that this variable is a pointer (or in this case casting to a pointer).

Syntax of Malloc:

       ptr = (cast_type *) malloc (byte_size);


One thing to note when reading C as opposed to Blitzmax, is that in C type definition comes first (int * myIntPointer) and in Blitzmax it is the opposite (myIntPointer:Int Ptr)

-Henri
- Got 01100011 problems, but the bit ain't 00000001

col

#20
At the risk of repeating myself ;D

QuoteDid I mention that you should really learn the language of what you are wrapping - c/cpp
Quoteyou will also need to understand what pointers are - especially from the c/cpp side

Maybe google and learn:
C variable declaration and definition - there is a very distinct difference between the two.
C structs.
C pointers to structs.
C dereference pointers.

As Henri says - malloc returns a pointer of type 'void*' and I cast it to 'struct MiniAudio*'.

I guess in that simple example you don't 'have' to cast it to 'struct MiniAudio*' as the value is simply being returned to BMax where BMax will keep the value stored as a Byte Ptr anyway. Without the cast would mean that the function will need to return a void*. void* is a very opaque datatype and it's impossible to know what the pointer really points to unless you read where the pointer was created - a hard lesson made easy is to always cast a void* returned from malloc to the intended datatype - in this case 'struct MiniAudio*'.

So it is done there as good practice so that the code is crystal clear as to its intent and to aid readability. Casts like this in c/cpp don't actually generate any extra cpu instructions, especially in release builds so it's purely for the coders benefit in this case. In fact in cpp it would be highly discouraged to cast in this way as there are special keywords for type casting - again to aid intent and readability.  It's also good to learn the 'correct' way because what if you wanted to pass the instance to another MiniAudio function from within that function - it MiniAudio function will expect the correct argument type so that cast in that simple example is purely to get you learn good habits  8)

BTW cpp syntax is much nicer and cleaner than c - maybe have a go at converting my little example to cpp to see it you like it more?
https://github.com/davecamp

"When you observe the world through social media, you lose your faith in it."

col

QuoteNow I can allocate a RAM in BlitzMax for use as ma_device_config. That works without errors

How do you know if it really does work or if it just happens to work without crashing?
This is a very real possibility when it comes to interoperability with 2 languages, especially when there is zero marshalling for the datatypes between the 2 - as is the case between BMax and c/cpp.

Don't let put you off though! Quite the contrary - It just means that you have make sure 100% that without any doubt whatsoever that what you expecting to happen is really happening. You have to check pointer values, check member values from pointers, all to make sure that the code is working correctly - You have to verify this and you'll get used to the 'printf' and 'Print' statements in doing so ;)
https://github.com/davecamp

"When you observe the world through social media, you lose your faith in it."

col

Quotestruct ma_device_config *' from type 'struct ma_device_config

As the compiler complains... these are different types. One is

struct ma_device_config *

and the other is

struct ma_device_config
https://github.com/davecamp

"When you observe the world through social media, you lose your faith in it."

Midimaster

Quote from: col on April 03, 2021, 16:09:11
As the compiler complains... these are different types. One is struct ma_device_config * and the other is struct ma_device_config

Yes, I know. But towards BlitzMax I need the version with the pointer, and  towards "MiniAudio.h" it has to be without.
Can I try to cast this?

something like ...
// my glue function:
struct ma_device_config *TestStart_2(int x) {
struct ma_device_config *loc;
loc= (struct ma_device_config *) StartDevice(1);
return loc;
}
// given MiniAudio.h function:
struct ma_device_config StartDevice(ma_device_type deviceType) {
struct ma_device_config config;
return config;

}
...crossing the alps with my bike at the moment

col

#24
QuoteBut I would need to interact with a given function in "learn.h" which returns not the pointer but the STRUCT itsself:

Try not to do this when wrapping - it's ok'ish (still not recommended but still...) to do it within c if you have no choice but if you can then always return a pointer if want to store it in BMax - it will make your life so much easier. Note that it can be done just that it will probably end up with tears and hair-pulling!

But say you REALLY had to return the struct then there are a couple of options -
First you need to know the size of the complete struct in bytes and allocate memory in BMax to store the data (maybe a memory bank), then you can pass a pointer of that instance into C and copy the data across. This will give what is known as a 'binary blob' in BMax because you won't really have a clean way to get at the data from within the bank.

The cleaner way to do this would be to create a Type in BMax where the Field members are an exact binary copy of the struct and its members in C, pass it in as a parameter to the c function and copy the data into it.

So let's dive in with an example of how I would manage something like this:-


struct MiniAudio {
   int IntMember;
   float FloatMember;
};


BMax Type has to be exactly the same size in bytes with the member at the same binary position with the Type as they are within the struct - our simple example is easy:-


Type MiniAudio
   Field IntMember:Int
   Field FloatMember:Float
EndType


If I now had no choice but to return a struct instance into BMax I would create the Type instance in BMax
Local MA:MiniAudio = New MiniAudio
so that we have some memory where we can put the struct from C, and instead of returning a struct from the c function I would return the struct data via a parameter - via a pointer. You need to learn pointers see ;)

Anyway to get on with the example...



Import "miniaudio.c"

Extern
Function CreateMiniAudio(Instance:Byte Ptr)
EndExtern

' The type MUST be 100% binary compatible with the c struct version
' This means not juse the size but the Fields MUST also be at the same binary position as they are in the c struct.
Type MiniAudio
   Field IntMember:Int
   Field FloatMember:Float
EndType

Local MA:MiniAudio = New MiniAudio
CreateMiniAudio(MA)

Print MA.IntMember
Print MA.FloatMember


C code


// 100% binary compatible with the Type MiniAudio in BMax
struct MiniAudio {
   int IntMember;
   float FloatMember;
};

// pMiniAudio is actually the 'Type MiniAudio' instance from BMax and is used here as an 'out' parameter.
void CreateMiniAudio(struct MiniAudio* pMiniAudio) {
   // Create an instance of the struct on the stack
   struct MiniAudio MA;
   MA.IntMember = 100;
   MA.FloatMember = -100;

   // This will copy all data members from MA to pMiniAudio.
   // The '*pMiniAudio' will 'dereference' the pointer to give us type 'MiniAudio' as opposed to 'MiniAudio*'.
   *pMiniAudio = MA;
}
https://github.com/davecamp

"When you observe the world through social media, you lose your faith in it."

Midimaster

I think you misunderstood me.

I only want to receive (and store) the pointer in blitzMax. Nothing more!

But that is what I'm not able to do a the moment.

The MiniAudio.h function returns a STRUCT

I want to receive a pointer in BlitzMax

So I need a glue function which converts the STRUCT into a pointer

// my glue function:
struct ma_device_config *ReturnToBlitzMax(int x) {
struct ma_device_config *loc;
loc= (struct ma_device_config *) StartDevice(1);
return loc;
}
// given MiniAudio.h function:
struct ma_device_config StartDevice(ma_device_type deviceType) {
struct ma_device_config config;
return config;
}

...crossing the alps with my bike at the moment

col

#26
Using your example you could do this...

// my glue function:
struct ma_device_config *TestStart_2(int x) {
        // we'll create a copy for BMax
        struct ma_device_config* instance;

struct ma_device_config loc;
loc= StartDevice(1);

        instance = (stuct ma_device_config*)malloc(sizeof(ma_device_config));
        *instance = loc;
return instance;
}
// given MiniAudio.h function:
struct ma_device_config StartDevice(ma_device_type deviceType) {
struct ma_device_config config;
return config;
}


There are different ways in which this could done... it's up to you really which you prefer.
You would then use a Byte Ptr in BMax in this case.
https://github.com/davecamp

"When you observe the world through social media, you lose your faith in it."

Baggey

#27
Wish i could help, but i know nothing about C and wrapper file's  :-[

I have some old code in C emulating the AY chip which is found in the ZX Spectrum. Would i be right if a wrapper can be made to work with BlitzMax. If you have the .c or .h file. This could be used in Blitzmax directly  :-X

Kudos!

Kind Regards Baggey
Running a PC that just Aint fast enough!? i7 4Ghz Quad core 32GB ram  2x1TB SSD and NVIDIA Quadro K1200 on Acer 24" . DID Technology stop! Or have we been assimulated!

Windows10, Parrot OS, Amiga mini, ZX Spectrum 48k, C64, ORIC Atmos 48K, Enterprise 128K, The SID chip. Im Misunderstood!

Midimaster

Im still struggling with these pointers...

I call a c-function from BlitzMax
Code (BlitzMax) Select
local P:Byte Ptr=  LearnPointer("abc")
Print String.FromCString(p)
Extern
Function LearnPointer:Byte Ptr(str$z)
End Extern


This works perfect:
char *LearnPointer(char *str) {
strcat(str, "?" );
return str;
}


This works too:
char *LearnPointer(char *str) {
char *mine="aaa";
return mine;
}


but this not:
char *LearnPointer(char *str) {
char *mine="aaa";
strcat(mine, "?" );
return mine;
}


What do I forget?
...crossing the alps with my bike at the moment

Henri

#29
Lets examine this line:


char *mine = "aaa";


This creates a char pointer to string literal 'aaa' that is of size 3.

Then you use:

strcat(mine, "?" );


This will add '?' at the end of memory area pointed by mine. But memory reserved for mine was 3 in size so trying to write extra character
outside of the reserved memory space creates an EAV. As a solution you could use char array that is big enough to hold both 'strings'.

In actuality, all of the examples do not work for me as they were probably intended. When a string is sent to C-code, it is out of the jurisdiction of Blitzmax and can be considered a lost cause without some safety measures. To make exchanging strings between the two more reliable you should include blitz.h in your C code. This enables you to return Blitzmax strings back that are managed by Blitzmax garbage collector.

An example:
Code (blitzmax) Select

Import "LearnPointer.c"

Local s:String =  LearnPointer("abc")
Print "In Blitzmax: Return value = " + s

Extern
Function LearnPointer:String(str$z)
End Extern



#include <stdio.h>
#include <brl.mod/blitz.mod/blitz.h>

char *LearnPointer(char *str) {
printf("In C: char * str = %s\n", str);
char mine[100]="aaa";
strcat(mine, "?" );
printf("In C: char mine [100] =  %s\n", mine);
return bbStringFromUTF8String(mine);
}



-Henri
- Got 01100011 problems, but the bit ain't 00000001