bmx-ng / bmk

The enhanced BlitzMax build program.
zlib License
28 stars 13 forks source link

Are extern types working just yet? #20

Closed davecamp closed 8 years ago

davecamp commented 8 years ago

Not sure if the bmk or bcc :/ Rather that show you my own code its easier to use the example in the docs under 'Interfacing with C and other languages' with ( a fixed version of ) the c++ example on that help page to see any errors.

woollybah commented 8 years ago

The short answer is no. The long reason is that C does not interface directly with C++. With x86 asm, you have a fixed target - you know that x method is so many bytes from the start of the class because it is always in the same place - so you can cheat a bit, and it all just works. The asm is so low-level that you can feed the stack in whatever way you need it. With C, it's no so easy to hack things around like that.

So, for brl DX7/9, I added glue code to handle the interface between Max and DX. It works well, but obviously not so transparently as creating some extern types which magically work in legacy BlitzMax.

However, if you can come up with a clean, multi-arch way to do this, I can implement it in bcc. :-)

Supporting C structs via extern types should be doable, to some extent, but whether to support them as stack or heap variables is another matter - I would choose stack, as then you could implement lightweight vec types, for example.

davecamp commented 8 years ago

Yeah the original does some unlawful tricks with the assembly. It looks as though c structs are almost there already but the field variable names don't tie up so you end up with compilation errors.

I'll have a think over the weekend about a, hopefully ;-) , clean approach and get back to you.

davecamp commented 8 years ago

Getting back to this :-)

In the meantime, how difficult would it be to implement extern'ed bmax interfaces?

woollybah commented 8 years ago

I'll need a bit more information to go on than that. Usage example, perhaps, possible syntax, etc.

davecamp commented 8 years ago

Sorry, I didn't think the request through properly. Please bear with me as I'm not always so good at explaining stuff

Example usage would look like

Extern
Interface MyTest
    Method GetData:Byte Ptr()
    Method SetData(Data:Byte Ptr)
    Method PrintTest(Data$z)
EndInterface

Function CreateInstance:MyTest()
EndExtern

' create an instance via a c function
Local inst:MyTest = CreateInstance()
inst.PrintTest()

Existing c file would already contain something along the line of

struct MyTest{
    void*(*GetData)(MyTest* This);
    int(*SetData)(MyTest* This, void* Data);
    int(*PrintTest)(MyTest* This, char* Data);
};

MyTest* CreateInstance(){
    MyTest* pInstance = NULL;
    FactoryFunctionToCreateMyTestInstance(&pInstance);
    return pInstance;
}

The c file could already contain functions such as

*void MyTest_GetData(MyTest* This);

if it would make it easier to implement rather than have bcc output it? I guess it would be along the lines of what you mentioned earlier about extern structs however with function pointers, and blindly allow them to called, all without munging the interface name or its methods? I know its never so straight forward to implement this kind of thing so forgive me if I sound like I'm expecting it to be easy as I'm not.

woollybah commented 8 years ago

Seems fine. I can probably have it generate code like

MyTest * bb_inst = CreateInstance();
bb_inst->PrintTest();

The struct would also need to be generated based on the bmx code in the extern, so you would need to be careful with regards to Strict/SuperStrict usage, and return types - SuperStrict will assume void return type if you don't specify anything, and therefore the generated struct for the SetData return type would be void and not int as in your example. Which is fine if you understand the consequences of declaring your bmx code with Strict or SuperStrict.

Not entirely sure about the use of "Interface", as that implies some form of polymorphism. Since we already have Interface and multiple inheritance, were you intending this version of Interface to have similar qualities? :-)

davecamp commented 8 years ago

My thoughts are to allow using c++ objects but only through an interface mechanism via a virtual table. It's possible to create a c to c++ bridge by using that interface mechanism. I know its not complete c++ object/method access but it's respectable food for thought :-)

Here is a complete working example of accessing a c++ interface object ( pure abstract class ) thats using a vtable to access the c++ object instance methods. I've tried to keep it as small as possible for the sake of this post. I hope you can understand what I have mind to be achieved. The cpp file is for the sake of creating a real cpp object - normally you wouldn't see that code ( except for the create function, which would finally be in the c file - it would be in a lib/dll or maybe even a file ). If you can get bcc to generate the c file then that means that its automatically creating the 'glue' code required for it to work and that would be so awesome! If it turns out to be a pain in the back side then simply allowing the external interface syntax ( which will generate a call to the IMyInterface_Add function in the c file ) would work as we can write all of the c code manually.

Enough waffling :-) , here are the files

' main.bmx
' Uncomment/Comment the Rem-EndRem pairs to the idea

'Rem
' Working implementation in Vanilla and NG
Import "main.c"
Import "main.cpp"

Extern
Function IMyInterface_Add:Int(pInstance:Byte Ptr,value:Int)
Function CreateInstance:Byte Ptr()
EndExtern

Local inst:Byte Ptr = CreateInstance()
Print IMyInterface_Add(inst,20)
Print IMyInterface_Add(inst,20)
Print IMyInterface_Add(inst,20)
Print IMyInterface_Add(inst,20)
Print IMyInterface_Add(inst,20)
'EndRem

Rem
' NG Only implementing external interface?
' A final working version would have the factory function moved from the cpp to the c file
' and that function would return the interface instance.
Import "main.c"

Extern
Interface IMyInterface
    Method Add:Int(Value:Int)
EndInterface
Function CreateInstance:IMyInterface()
EndExtern

Local inst:IMyInterface = CreateInstance()
Print inst.Add(10)
Print inst.Add(20)
Print inst.Add(30)
Print inst.Add(40)
Print inst.Add(50)
EndRem

The bcc generated ( or manually created ) c file

// INTERFACE GLUE - possibly created by bcc? if not manually is ok
typedef struct IMyInterface IMyInterface;

typedef struct IMyInterfaceVtbl{
    int(*Add)(IMyInterface* This,int Data);
};

struct IMyInterface{
    struct IMyInterfaceVtbl* lpVtbl;
};

int IMyInterface_Add(IMyInterface* This,int Value){
    return This->lpVtbl->Add(This,Value);
}

// END OF INTERFACE GLUE

And the cpp file - as I say normally this wouldn't be a part of the build process but it's here just to show a cpp object implementation and would be in a lib/dll file somewhere away from the programmer.

// BEGIN PRIVATE IMPLEMENTATION - NORMALLY THE PROGRAMMER WOULDN'T SEE THIS
class IMyInterface{
public:
    virtual int Add(int Data) = 0;
};

class CMyInterface : public IMyInterface {
    int m_value;

public:
    CMyInterface();
    virtual ~CMyInterface();

    int Add(int value){
        m_value += value;
        return m_value;
    }
};

CMyInterface::CMyInterface():
m_value(0){
};
CMyInterface::~CMyInterface(){
}
// END PRIVATE IMPLEMENTATION

// public function - can be called from 'Max to create an instance of the object
//                   that's only accessible through the interface definition
extern"C"{
    // This is just a quick 'factory create' function for the sake of a working example
    IMyInterface* CreateInstance(){
        return new CMyInterface();
    }
}

Do you think this kind of approach of accessing cpp objects would work? There may be a deal breaker that I've not thought of just yet?

woollybah commented 8 years ago

Have you tested your bridge stuff on 64-bit?

davecamp commented 8 years ago

I have and it works in x64 in NG on a windows unit. However, I just tried it for x86 in NG and it eav'd, however it does work in vanilla, which as you know is x86 only. There must be a subtle difference there somewhere :/

davecamp commented 8 years ago

There is also your question of inheritance etc. It is possible to duplicate the binary layout of a cpp object but it think it may get too complicated for multiple inheritance and polymorphism too. Single inheritance would be straight forward as the functions would be ordered from base to derived. Again we're talking pure abstract cpp objects only.

woollybah commented 8 years ago

Okay, leave it with me.

woollybah commented 8 years ago

Oh, one other thing. Like normal extern functions, your Interface and method names will be case-sensitive in the Extern block (as we are generating code directly from them to C) When you use the code later, it doesn't matter so much as we'll be referring back to the declaration.

davecamp commented 8 years ago

Yes, I think that's to be expected in the extern block. Thanks for looking into this!

woollybah commented 8 years ago

This issue was moved to bmx-ng/bcc#145