How to get started with ... everything?.

LiveCode Builder is a language for extending LiveCode's capabilities, creating new object types as Widgets, and libraries that access lower-level APIs in OSes, applications, and DLLs.

Moderators: LCMark, LCfraser

SparkOut
Posts: 2834
Joined: Sun Sep 23, 2007 4:58 pm

How to get started with ... everything?.

Post by SparkOut » Tue Jan 09, 2018 9:38 pm

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?
Last edited by SparkOut on Thu Jan 09, 2020 11:26 am, edited 1 time in total.

MaxV
Posts: 1579
Joined: Tue May 28, 2013 2:20 pm
Location: Italy
Contact:

Re: How to get started with ... everything?

Post by MaxV » Wed Jan 10, 2018 5:04 pm

Livecode Wiki: http://livecode.wikia.com
My blog: https://livecode-blogger.blogspot.com
To post code use this: http://tinyurl.com/ogp6d5w

jameshale
VIP Livecode Opensource Backer
VIP Livecode Opensource Backer
Posts: 474
Joined: Thu Sep 04, 2008 6:23 am
Location: Melbourne Australia

Re: How to get started with ... everything?

Post by jameshale » Thu Jan 11, 2018 1:11 am

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.


SparkOut
Posts: 2834
Joined: Sun Sep 23, 2007 4:58 pm

Re: How to get started with ... everything?

Post by SparkOut » Thu Jan 11, 2018 10:24 pm

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

MaxV
Posts: 1579
Joined: Tue May 28, 2013 2:20 pm
Location: Italy
Contact:

Re: How to get started with ... everything?

Post by MaxV » Fri Jan 12, 2018 4:30 pm

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

LCMark
Livecode Staff Member
Livecode Staff Member
Posts: 1206
Joined: Thu Apr 11, 2013 11:27 am

Re: How to get started with ... everything?

Post by LCMark » Fri Jan 12, 2018 7:23 pm

@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.

SparkOut
Posts: 2834
Joined: Sun Sep 23, 2007 4:58 pm

Re: How to get started with ... everything?

Post by SparkOut » Mon Jan 15, 2018 12:04 am

@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.)

LCMark
Livecode Staff Member
Livecode Staff Member
Posts: 1206
Joined: Thu Apr 11, 2013 11:27 am

Re: How to get started with ... everything?

Post by LCMark » Tue Jan 16, 2018 2:11 pm

@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).

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 '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.

SparkOut
Posts: 2834
Joined: Sun Sep 23, 2007 4:58 pm

Re: How to get started with ... everything?

Post by SparkOut » Tue Jan 16, 2018 9:30 pm

@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 .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
This 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.

LCMark
Livecode Staff Member
Livecode Staff Member
Posts: 1206
Joined: Thu Apr 11, 2013 11:27 am

Re: How to get started with ... everything?

Post by LCMark » Wed Jan 17, 2018 12:05 pm

@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:

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);
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:

Code: Select all

FUNCTION int __stdcall ReadAllDigital();
So, the function returns 'int', and take not arguments. This translates to an LCB handler decl as:

Code: Select all

private __safe foreign handler _ReadAllDigital() returns CInt binds to "c:K8055D>ReadAllDigital!stdcall"
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:

Code: Select all

private __safe foreign handler _ReadBackAnalogOut(out rBuffer as CInt) returns nothing binds to "c:K8055D>ReadBackAnalogOut!stdcall" -- THIS IS NOT CORRECT!
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:

Code: Select all

private foreign type CIntArray2 binds to "MCAggregateTypeInfo:EE"
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:

Code: Select all

private __safe foreign handler _ReadBackAnalogOut(our rBuffer as CIntArray2) returns nothing binds to "c:K8055D>ReadBackAnalogOut!stdcall"
With this definition we can then define a suitable handler which we can call from LCS:

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
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.

AndyP
Posts: 614
Joined: Wed Aug 27, 2008 12:57 pm
Location: Seeheim, Germany (ex UK)
Contact:

Re: How to get started with ... everything?

Post by AndyP » Wed Jan 17, 2018 1:16 pm

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

jameshale
VIP Livecode Opensource Backer
VIP Livecode Opensource Backer
Posts: 474
Joined: Thu Sep 04, 2008 6:23 am
Location: Melbourne Australia

Re: How to get started with ... everything?

Post by jameshale » Wed Jan 17, 2018 2:48 pm

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.

SparkOut
Posts: 2834
Joined: Sun Sep 23, 2007 4:58 pm

Re: How to get started with ... everything?

Post by SparkOut » Wed Jan 17, 2018 4:08 pm

@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:

Code: Select all

Parsing error: syntax error
   return tIntList[1] formatted as string & "," tIntList[2] formatted as string
Do you have an insight into the problem here?
Thanks again!

livecodeali
Livecode Staff Member
Livecode Staff Member
Posts: 192
Joined: Thu Apr 18, 2013 2:48 pm

Re: How to get started with ... everything?

Post by livecodeali » Wed Jan 17, 2018 4:31 pm

Hopefully it's just the missing ampersand:

Code: Select all

return tIntList[1] formatted as string & "," & tIntList[2] formatted as string

Post Reply

Return to “LiveCode Builder”