How to get started with ... everything?.
How to get started with ... everything?.
For the life of me, I cannot figure out how to get to set up LCB to do anything useful. I have played with the pink circle widget demo and although that "works" it renders a cropped image. But that isn't helping me learn what to do. I can copy the code and get it to do that, but how do I determine what code I need to do the things I want to do? There just don't seem to be any handles on it. I would love to get more involved, but I don't have an entry point.
My target will only be Windows (10).
What I am ultimately hoping is that LCB will mean that I can leverage other low level functions, and make calls to third party dlls?
For instance, if I have a prepackaged 3rd party (Windows standard) dll is there a way I can use LCB to be the glue to access the functions in a called dll and receive the returned value?
That's the long term goal. But for the life of me, I would love just to get started on the road to learning the way there.
Are there any better intros than the pink circle sample?
My target will only be Windows (10).
What I am ultimately hoping is that LCB will mean that I can leverage other low level functions, and make calls to third party dlls?
For instance, if I have a prepackaged 3rd party (Windows standard) dll is there a way I can use LCB to be the glue to access the functions in a called dll and receive the returned value?
That's the long term goal. But for the life of me, I would love just to get started on the road to learning the way there.
Are there any better intros than the pink circle sample?
Last edited by SparkOut on Thu Jan 09, 2020 11:26 am, edited 1 time in total.
Re: How to get started with ... everything?
Livecode Wiki: http://livecode.wikia.com
My blog: https://livecode-blogger.blogspot.com
To post code use this: http://tinyurl.com/ogp6d5w
My blog: https://livecode-blogger.blogspot.com
To post code use this: http://tinyurl.com/ogp6d5w
-
- VIP Livecode Opensource Backer
- Posts: 474
- Joined: Thu Sep 04, 2008 6:23 am
- Location: Melbourne Australia
Re: How to get started with ... everything?
You could also log in to your account at LiveCode.com and look down the left hand list at the “Learning” category. I have a “Widget Course” listed there. It is not too bad, although there are a couple of ‘bugs’. Read the comments for each lesson.
-
- Posts: 627
- Joined: Wed Apr 24, 2013 4:53 pm
- Contact:
Re: How to get started with ... everything?
Thanks to everyone for helping - there's a lot more info out there than I thought.
Special thanks to Bernd who contacted me off forum and gave me invaluable help to get me started.
I know I'm trying to run before walking, but my ultimate goal is to access functions in a 3rd party dll on Windows. I have compiled a C++ console file which uses LoadLibrary and then GetProcAddress to access the functions which works by using LCS to poll the state by getting the value returned to the shell console, having set the hideConsoleWindows to true.
I would like to translate this to LCB if possible.
I have used syntax borrowed from n.allen in this thread http://forums.livecode.com/viewtopic.php?f=93&t=25463 which (after correcting the dll path) works to load the library, but fails to return anything, and reports an error "unknown language". Any ideas what that could be about? In the meantime I will carry on practicing with more sensible tasks.
Thanks again
Special thanks to Bernd who contacted me off forum and gave me invaluable help to get me started.
I know I'm trying to run before walking, but my ultimate goal is to access functions in a 3rd party dll on Windows. I have compiled a C++ console file which uses LoadLibrary and then GetProcAddress to access the functions which works by using LCS to poll the state by getting the value returned to the shell console, having set the hideConsoleWindows to true.
I would like to translate this to LCB if possible.
I have used syntax borrowed from n.allen in this thread http://forums.livecode.com/viewtopic.php?f=93&t=25463 which (after correcting the dll path) works to load the library, but fails to return anything, and reports an error "unknown language". Any ideas what that could be about? In the meantime I will carry on practicing with more sensible tasks.
Thanks again
Re: How to get started with ... everything?
If you just want to work with DLL just read this: http://livecode.wikia.com/wiki/Working_with_DLL
Livecode Wiki: http://livecode.wikia.com
My blog: https://livecode-blogger.blogspot.com
To post code use this: http://tinyurl.com/ogp6d5w
My blog: https://livecode-blogger.blogspot.com
To post code use this: http://tinyurl.com/ogp6d5w
Re: How to get started with ... everything?
@SparkOut: Can you share the C code you are trying to convert to LCB? I might be able to help out
@MaxV: That explains how to build an external - which is still a viable way to extend LC. However, the advantage of doing it in LCB is that you get greater integration with the engine, and you don't have to touch any C/C++ IDEs (like visual studio) - assuming you have the DLL already compiled and waiting to be used.
@MaxV: That explains how to build an external - which is still a viable way to extend LC. However, the advantage of doing it in LCB is that you get greater integration with the engine, and you don't have to touch any C/C++ IDEs (like visual studio) - assuming you have the DLL already compiled and waiting to be used.
Re: How to get started with ... everything?
@LCMark sure, where/how would you like it?
I am trying to access the K8055D.dll provided with Velleman VM110N USB experimentation board. They have a software kit here: https://www.velleman.eu/downloads/files ... ev1206.zip with example sample source and compiled files for Delphi, VB6, C++ etc (looks like it's old stuff, TBH). I used the DevC source and compiled a console exe to call from LC with shell to have a way of getting access to the dll and returning results to LC. I can do the job I need to do with it. I would just love to be able to get there without needing 3rd party IDEs. (Mind you, my familiarity with LCB is even less than some of these other samples, but this is how we learn.)
I am trying to access the K8055D.dll provided with Velleman VM110N USB experimentation board. They have a software kit here: https://www.velleman.eu/downloads/files ... ev1206.zip with example sample source and compiled files for Delphi, VB6, C++ etc (looks like it's old stuff, TBH). I used the DevC source and compiled a console exe to call from LC with shell to have a way of getting access to the dll and returning results to LC. I can do the job I need to do with it. I would just love to be able to get there without needing 3rd party IDEs. (Mind you, my familiarity with LCB is even less than some of these other samples, but this is how we learn.)
Re: How to get started with ... everything?
@SparkOut: The velleman API is really simple - so equally simple to bind to from LCB:
Here is an example lcb file with bindings to some of the DLLs functions (the rest are all very similar!), and some example LCB public handlers which wrap them (they are what is called from LCS).
Create a folder 'velleman' and put the above in as 'velleman.lcb'.
Create a folder code in 'velleman'.
Create a folder 'x86-win32' in 'code'.
Then copy the dll into the x86-win32 folder.
The above sets up the lcb extension source structure. If you then fire up the Extension Builder and point it at the velleman LCB file and click test extension, the vellemanOpenDevice() public handlers etc should be available to call like normal LCS handlers (e.g. answer vellemanOpenDevice(1) - gives me -1 here, as I don't have the board!).
Hope this helps!
P.S. I used '__safe' on the foreign handlers in the above because calling them doesn't require anything passed which can be considered unsafe - they all take ints, return ints, or have out parameters as ints. If any of the calls had required memory blocks to be passed around, or similar things it would not have been appropriate.
Here is an example lcb file with bindings to some of the DLLs functions (the rest are all very similar!), and some example LCB public handlers which wrap them (they are what is called from LCS).
Code: Select all
library velleman
use com.livecode.foreign
private __safe foreign handler _OpenDevice(in pCardAddress as CInt) returns CInt binds to "c:K8055D>OpenDevice!stdcall"
private __safe foreign handler _CloseDevice() returns nothing binds to "c:K8055D>CloseDevice!stdcall"
private __safe foreign handler _ReadAnalogChannel(in pChannel as CInt) returns CInt binds to "c:K8055D>ReadAnalogChannel!stdcall"
private __safe foreign handler _ReadAllAnalog(out rData1 as CInt, out rData2 as CInt) returns nothing binds to "c:K8055D>ReadAllAnalog!stdcall"
public handler vellemanOpenDevice(in pCardAddress as Integer) returns Integer
return _OpenDevice(pCardAddress)
end handler
public handler vellemanCloseDevice() returns nothing
_CloseDevice()
end handler
public handler vellemanReadAllAnalog() returns String
variable tData1 as CInt
variable tData2 as CInt
_ReadAllAnalog(tData1, tData2)
return tData1 formatted as string & "," & tData2 formatted as string
end handler
end library
Create a folder code in 'velleman'.
Create a folder 'x86-win32' in 'code'.
Then copy the dll into the x86-win32 folder.
The above sets up the lcb extension source structure. If you then fire up the Extension Builder and point it at the velleman LCB file and click test extension, the vellemanOpenDevice() public handlers etc should be available to call like normal LCS handlers (e.g. answer vellemanOpenDevice(1) - gives me -1 here, as I don't have the board!).
Hope this helps!
P.S. I used '__safe' on the foreign handlers in the above because calling them doesn't require anything passed which can be considered unsafe - they all take ints, return ints, or have out parameters as ints. If any of the calls had required memory blocks to be passed around, or similar things it would not have been appropriate.
Re: How to get started with ... everything?
@LCMark
This is brilliant, thank you so much. "Simple" is a relative term, obviously! I thought it should be OK in principle but the syntax to declare the handler binding using !stdcall and the folder structure isn't intutive to me. Nevertheless, I am making progress with everyone's kind help.
I have added the following declaration and handler to the .lcbThis is actually working to test, and I am getting digital reads from the device. One thing that baffles me is that it refused to work when the foreign handler declaration said "returns nothing" rather than CInt. It makes sense to me that it should require a declaration of "returns CInt", but the _ReadAllAnalog declaration specifically says "returns nothing", although there are "out" variables declared. I am really vague about this syntax, and would love to know what parts of the documentation to study to get more understanding of this.
This is brilliant, thank you so much. "Simple" is a relative term, obviously! I thought it should be OK in principle but the syntax to declare the handler binding using !stdcall and the folder structure isn't intutive to me. Nevertheless, I am making progress with everyone's kind help.
I have added the following declaration and handler to the .lcb
Code: Select all
private __safe foreign handler _ReadAllDigital(out rData as CInt) returns CInt binds to "c:K8055D>ReadAllDigital!stdcall"
public handler velReadAllDigital() returns Integer
variable tData as CInt
return _ReadAllDigital(tData)
end handler
Re: How to get started with ... everything?
@SparkOut: I perhaps should have added some comments to the above
In terms of the folder structure, then the extension builder requires each extension have its own folder (velleman) in this instance. Then (in dp-11) it requires that a *single* LCB file be present in that folder (velleman.lcb) [ in the next build we've extended this so you can have multiple LCB files, but they have to be related - more on this when when the next build comes out! ]. Then, as we are wanting to include native code blobs (the velleman dll in this case) we need a code folder. As native code blobs are platform/arch specific, you then need a folder for each of those combinations which you want to support. In this case the DLL is very much Windows only, so you only need a 'code/x86-win32' folder - in here you place the DLLs for x86-win32. The 'leaf' names of the DLLs in that folder is what you use in the binding string (K8055D in this case, as the DLL is called K8055D.DLL).
When you bind to C handlers, you need to tell LCB what the 'signature' of the C handler is, as defined in C. The signature of a handler consists of the types of the parameters and the return value type.
In the DLL zip, there is a header file for BCB - which has the needed information in it:
Each of these lines defines the signature of the C handler, so we have to turn them into LCB foreign handler definitions.
The first bit to notice is that all of them have __stdcall before the name. For various reasons of history, Windows has many different ways a C function can be called - typically you'll find two though __stdcall and __cdecl. The latter is the default for C applications, and the default for LCB, the former (__stdcall) convention is the one which is generally used for DLLs (particularly Win APIs) - however it needs to be explicitly stated.
In LCB, the calling convention is specified by appending !<convention> to the binding string - this is why all the bindings in this case have !stdcall at the end.
After establishing that, the next thing to do is to translate the signature as written in C, to LCB. If we take the example you mention - ReadAllDigital - then you can see in the above it is declared in C as:
So, the function returns 'int', and take not arguments. This translates to an LCB handler decl as:
So the reason this handler looks different from ReadAllAnalog, is because the C functions are quite different.
The rest of the functions are all pretty much the same - they either return void, int or bool, and take no, 1 or two int arguments. The type to use in LCB for 'bool' in C, is 'CBool' (and as we have seen 'nothing' maps to void).
The final bit of 'difference' in the API is the use of 'int *' in ReadBackAnalogOut and ReadAllAnalog. This is a little more subtle, as C APIs declared as above don't actually specify *what* this means - this requires sifting through the docs generally.
The case of ReadAllAnalog is as specified above - it reads the two analog channels the device has, and returns the values into the two passed parameters (out basically means that the var passed to it is passed 'by reference' - for foreign handlers this is done in a way so that the foreign handler sees a reference to the foreign type which is declared).
The case of ReadBackAnalogOut would, at first glance, appear to require something like:
i.e. Following the same pattern as ReadAllAnalog - you might assume that rBuffer is a CInt out parameter. However, this is not quite the case, checking the docs (and the sample code) - the buffer parameter actually needs to be a pointer to an array of 2 CInts (i.e. int[2]). This is considered an 'aggregate' type in LCB, which we need to define:
This defines a 'foreign' type which consists of two (C) int values - each char after the : specifies the type of a field in the aggregate. The full list of codes can be found in the LCB language reference guide (https://github.com/livecode/livecode/bl ... ference.md).
WIth this type defined, we can now define the foreign handler type in a safe way:
With this definition we can then define a suitable handler which we can call from LCS:
This also illustrates how to access an aggregate - a foreign aggregate type in LCB 'looks like' a List - 1 element for each code in the aggregate definition. So tBuffer[1] gives us the first CInt, and tBuffer[2] gives us the second CInt.
Hope this helps - I think the rest of the calls in the DLL are all simple variants of the above. The only 'tricky' ones are the ReadBackAnalogOut and ReadAllAnalog - but I think those should work with the definitions above (obviously I can't actually *test* this, as the DLL won't init for me, not having the board!).
Let me know how you get on though!
P.S. The above has been edited several times - as I said I can't really test this - so it might not quite work, but hopefully it is almost correct and we can find any problems with it easily enough.
In terms of the folder structure, then the extension builder requires each extension have its own folder (velleman) in this instance. Then (in dp-11) it requires that a *single* LCB file be present in that folder (velleman.lcb) [ in the next build we've extended this so you can have multiple LCB files, but they have to be related - more on this when when the next build comes out! ]. Then, as we are wanting to include native code blobs (the velleman dll in this case) we need a code folder. As native code blobs are platform/arch specific, you then need a folder for each of those combinations which you want to support. In this case the DLL is very much Windows only, so you only need a 'code/x86-win32' folder - in here you place the DLLs for x86-win32. The 'leaf' names of the DLLs in that folder is what you use in the binding string (K8055D in this case, as the DLL is called K8055D.DLL).
When you bind to C handlers, you need to tell LCB what the 'signature' of the C handler is, as defined in C. The signature of a handler consists of the types of the parameters and the return value type.
In the DLL zip, there is a header file for BCB - which has the needed information in it:
Code: Select all
FUNCTION int __stdcall OpenDevice(int CardAddress);
FUNCTION void __stdcall CloseDevice();
FUNCTION int __stdcall ReadAnalogChannel(int Channel);
FUNCTION void __stdcall ReadAllAnalog(int *Data1, int *Data2);
FUNCTION void __stdcall OutputAnalogChannel(int Channel, int Data);
FUNCTION void __stdcall OutputAllAnalog(int Data1, int Data2);
FUNCTION void __stdcall ClearAnalogChannel(int Channel);
FUNCTION void __stdcall ClearAllAnalog();
FUNCTION void __stdcall SetAnalogChannel(int Channel);
FUNCTION void __stdcall SetAllAnalog();
FUNCTION void __stdcall WriteAllDigital(int Data);
FUNCTION void __stdcall ClearDigitalChannel(int Channel);
FUNCTION void __stdcall ClearAllDigital();
FUNCTION void __stdcall SetDigitalChannel(int Channel);
FUNCTION void __stdcall SetAllDigital();
FUNCTION bool __stdcall ReadDigitalChannel(int Channel);
FUNCTION int __stdcall ReadAllDigital();
FUNCTION int __stdcall ReadCounter(int CounterNr);
FUNCTION void __stdcall ResetCounter(int CounterNr);
FUNCTION void __stdcall SetCounterDebounceTime(int CounterNr, int DebounceTime);
FUNCTION int __stdcall Version();
FUNCTION int __stdcall SearchDevices();
FUNCTION int __stdcall SetCurrentDevice(int CardAddress);
FUNCTION int __stdcall ReadBackDigitalOut();
FUNCTION void __stdcall ReadBackAnalogOut(int *Buffer);
The first bit to notice is that all of them have __stdcall before the name. For various reasons of history, Windows has many different ways a C function can be called - typically you'll find two though __stdcall and __cdecl. The latter is the default for C applications, and the default for LCB, the former (__stdcall) convention is the one which is generally used for DLLs (particularly Win APIs) - however it needs to be explicitly stated.
In LCB, the calling convention is specified by appending !<convention> to the binding string - this is why all the bindings in this case have !stdcall at the end.
After establishing that, the next thing to do is to translate the signature as written in C, to LCB. If we take the example you mention - ReadAllDigital - then you can see in the above it is declared in C as:
Code: Select all
FUNCTION int __stdcall ReadAllDigital();
Code: Select all
private __safe foreign handler _ReadAllDigital() returns CInt binds to "c:K8055D>ReadAllDigital!stdcall"
The rest of the functions are all pretty much the same - they either return void, int or bool, and take no, 1 or two int arguments. The type to use in LCB for 'bool' in C, is 'CBool' (and as we have seen 'nothing' maps to void).
The final bit of 'difference' in the API is the use of 'int *' in ReadBackAnalogOut and ReadAllAnalog. This is a little more subtle, as C APIs declared as above don't actually specify *what* this means - this requires sifting through the docs generally.
The case of ReadAllAnalog is as specified above - it reads the two analog channels the device has, and returns the values into the two passed parameters (out basically means that the var passed to it is passed 'by reference' - for foreign handlers this is done in a way so that the foreign handler sees a reference to the foreign type which is declared).
The case of ReadBackAnalogOut would, at first glance, appear to require something like:
Code: Select all
private __safe foreign handler _ReadBackAnalogOut(out rBuffer as CInt) returns nothing binds to "c:K8055D>ReadBackAnalogOut!stdcall" -- THIS IS NOT CORRECT!
Code: Select all
private foreign type CIntArray2 binds to "MCAggregateTypeInfo:EE"
WIth this type defined, we can now define the foreign handler type in a safe way:
Code: Select all
private __safe foreign handler _ReadBackAnalogOut(our rBuffer as CIntArray2) returns nothing binds to "c:K8055D>ReadBackAnalogOut!stdcall"
Code: Select all
public handler valReadBackAnalogOut() returns String
variable tBuffer as CIntArray2
_ReadBackAnalogOut(tBuffer)
/* We have the buffer as a foreign aggregate, but we must convert to a List to access */
variable tIntList as List
put tBuffer into tIntList
return tIntList[1] formatted as string & "," tIntList[2] formatted as string
end handler
Hope this helps - I think the rest of the calls in the DLL are all simple variants of the above. The only 'tricky' ones are the ReadBackAnalogOut and ReadAllAnalog - but I think those should work with the definitions above (obviously I can't actually *test* this, as the DLL won't init for me, not having the board!).
Let me know how you get on though!
P.S. The above has been edited several times - as I said I can't really test this - so it might not quite work, but hopefully it is almost correct and we can find any problems with it easily enough.
Re: How to get started with ... everything?
Once this has all been tested, it would be great to add it as a LC lesson.
Andy Piddock
https://livecode1001.blogspot.com Built with LiveCode
https://github.com/AndyPiddock/TinyIDE Mini IDE alternative
https://github.com/AndyPiddock/Seth Editor color theming
http://livecodeshare.runrev.com/stack/897/ LiveCode-Multi-Search
https://livecode1001.blogspot.com Built with LiveCode
https://github.com/AndyPiddock/TinyIDE Mini IDE alternative
https://github.com/AndyPiddock/Seth Editor color theming
http://livecodeshare.runrev.com/stack/897/ LiveCode-Multi-Search
-
- VIP Livecode Opensource Backer
- Posts: 474
- Joined: Thu Sep 04, 2008 6:23 am
- Location: Melbourne Australia
Re: How to get started with ... everything?
More than a lesson.
It could be expanded to be a generic overview of how to write an extension to call foreign code and what structure the widget needs to have.
For example if we were calling a Mac binary, what code folder would be required?
When calling parts of the Mac or iOS framework we do not actually need a code folder I guess.
When calling any binary how do we signal to lc that this or that code binary is required.
In any case I understood more about calling on a foreign code block in Mark’s reply than I have in all the other posts/blogs I have read.
Thank you.
It could be expanded to be a generic overview of how to write an extension to call foreign code and what structure the widget needs to have.
For example if we were calling a Mac binary, what code folder would be required?
When calling parts of the Mac or iOS framework we do not actually need a code folder I guess.
When calling any binary how do we signal to lc that this or that code binary is required.
In any case I understood more about calling on a foreign code block in Mark’s reply than I have in all the other posts/blogs I have read.
Thank you.
Re: How to get started with ... everything?
@LCMark
This is fantastic, thank you again!
I have been able to make some extra functions and can get and set each digital input and output, and and clear and set all digital inputs and outputs. I can open and close devices, search devices and parse the version. Your ReadAllAnalog function seems to work fine. I am getting on with building a full library for this board and so very pleased with the results, and happy that I am beginning to learn (just about) and very grateful for the support.
Having correct "our" to "out" in the declaration for _ReadBackAnalogOut I get the following error though, and it won't compile:Do you have an insight into the problem here?
Thanks again!
This is fantastic, thank you again!
I have been able to make some extra functions and can get and set each digital input and output, and and clear and set all digital inputs and outputs. I can open and close devices, search devices and parse the version. Your ReadAllAnalog function seems to work fine. I am getting on with building a full library for this board and so very pleased with the results, and happy that I am beginning to learn (just about) and very grateful for the support.
Having correct "our" to "out" in the declaration for _ReadBackAnalogOut I get the following error though, and it won't compile:
Code: Select all
Parsing error: syntax error
return tIntList[1] formatted as string & "," tIntList[2] formatted as string
Thanks again!
-
- Livecode Staff Member
- Posts: 192
- Joined: Thu Apr 18, 2013 2:48 pm
Re: How to get started with ... everything?
Hopefully it's just the missing ampersand:
Code: Select all
return tIntList[1] formatted as string & "," & tIntList[2] formatted as string