coda
Junior Member
Posts: 74
|
Post by coda on Sept 4, 2023 5:09:37 GMT -5
I am writing my own DLL files in another language to assist some LB software that I am creating.
I have managed to successfully pass and receive both LB number and string data types back and forth between LB and my DLL but I still have a few questions regarding passing parameters to DLL functions and receiving returns:
Firstly, I notice that numeric variables seem to be passed by value but strings, by reference.
My questions are as follows:
1) Is it possible to do this the opposite way around also? Ie. Pass numbers by reference and strings by value? The help file seems to hint that at least stings can be passed by value but then does not seem to explain how this is done other than to mention that you should leave them without null termination.
I have checked the list of data types in the LB help file and there does not appear to be a ‘string’ data type. As such, I don’t see how I can also pass strings by value as indicated in the help file. If I leave my string without null termination, what do I declare it as? There doesn’t seem to be an appropriate data type. If I use the ‘ptr’ data type provided, surely that can’t be correct and there are no other data types for strings. Perhaps I am wrong about this but it seems like very odd syntax to use ‘as ptr’ when you are NOT passing a pointer.
With regard to the inverse, for passing pointers to numeric variables, I have tried code such as:
CALLDLL #MyDLL, "PointerTest", N as ptr, result as void
...but it just appears to crash the LB program.
2) The LB help manual seems also to indicate that LB can receive back pointers to strings which would be very useful for functions that take numerical inputs but return strings. The only way I can currently achieve this is by first creating a dummy string in LB and passing a pointer to it along with the numeric value to my DLL so it can directly modify the LB-created string. When I try to do it differently by passing back a pointer to a string, LB doesn’t like my syntax and crashes my program. I have tried both of the following:
CALLDLL #MyDLL, "StringPointerTest", N as long, result as ptr
CALLDLL #MyDLL, "StringPointerTest", N as long, result$ as ptr
...but both crash.
I figure the problem must be my syntax because it must be possible to receive pointers from DLLs as, if LB CAN’T receive pointers back then what on earth would the ‘Winstring()’ function be for?
3) Is it possible to pass pointers to arrays from LB to DLLs? This would be INCREDIBLY useful.
4) Given some of the above, is the list of data types that LB can pass that is included in the help file complete? There seem to be quite a few data types that compiled languages like c and others might be using that are not on the list.
Thanks for any information you can provide.
|
|
|
Post by Walt Decker on Sept 4, 2023 12:08:45 GMT -5
In LB you can never pass a string to an API or 2nd party dll by value. It must always be by reference, e. g. ' CALLDLL #SOMEDLL, MyString$ AS PTR, RetVal AS VOID CALLDLL #SOMEDLL, MyString$ AS STRUCT, RetVal AS VOID '
In both cases you are passing a pointer, i. e. reference, to the string. The defined data types in LB are: [CHAR] applies to structs only USHORT unsigned integer 16 bit(WORD) SHORT signed integer 16 bit ULONG unsigned integer 32 bit(DWORD) LONG signed integer 32 bit DOUBLE double precision float You can pass a number to a dll by reference, however, it must be in a structure that matches a corresponding structure in the dll. You can receive a string back from a dll in two ways: 1) by passing a pointer to a string that is sized appropriately to receive a string or 2) returning the address(pointer) to a global or static string in the dll. In the later case you use the LB WINSTRING() function to retrieve the string. ' CALLDLL #SOMEDLL, StrAddr AS ULONG
MyString$ = WINSTRING(StrAddr)
'
LB does not have pointers per se therefore it is not possible to send a pointer to an array. You can not send an element of an array unless the contents of the element is first placed in an intermediate variable.
Hope that helps.
|
|
|
Post by Brandon Parker on Sept 8, 2023 19:38:20 GMT -5
In LB you can never pass a string to an API or 2nd party dll by value. It must always be by reference, While this is true for how Liberty BASIC passes strings behind the scenes, this is not true for how the process is presented to the Liberty BASIC programmer. LB obfuscates the passing of strings to provide "passing by value" and "passing by reference" ... If you want to pass by value, you send a simple LB string as a PTR type. LB makes a copy of that string and actually sends the reference pointing to the copy to the API call. If you want to pass by reference, you send an LB string with chr$(0) appended to the end of the string (i.e. myString$ = myString$ + chr$(0)) should be placed just prior to an API call where myString$ needs to be sent by reference. When the string has a chr$(0) as the last character, LB assumes "pass by reference" and will send the reference to the actual myString$ variable held in memory. If the API call manipulates the string, it manipulates the programmer's actual string. See below for the excerpt from the LB Help File... {:0) Brandon Parker
|
|
|
Post by Chris Iverson on Sept 8, 2023 22:36:00 GMT -5
Uhh, are we sure that's still accurate?
Because I have literally never done that for my strings in LB, and I have never had a problem with my string buffers being written by called DLL functions. I didn't even realize it was a documented requirement.
Just check any of my LB examples for my LBNet project on GitHub.
Or heck, with the very example that you quoted, modified to remove the explicit null character:
'getpstr.bas - Get information using the GetProfileString API call open "kernel32.dll" for dll as #kernel 'Notice that no ASCII zero characters are added to these strings 'because the program will not need to read any results out of the call 'to GetProfileString. appName$ = "windows" keyName$ = "device" default$ = "" 'add an ASCII zero so Liberty BASIC will not pass a copy of result$ 'into the API call, but the actual contents of result$ result$ = space$(50) size = 50 '49 spaces plus 1 ASCII zero character calldll #kernel, "GetProfileStringA",_ appName$ as ptr,_ keyName$ as ptr,_ default$ as ptr,_ result$ as ptr,_ size as long,_ result as long close #kernel 'display the retrieved information up to but not including the 'terminating ASCII zero print trim$(result$) wait
|
|
|
Post by Brandon Parker on Sept 9, 2023 17:26:43 GMT -5
I am not sure I could state whether it is still accurate, but I am just going off of the manual and previous experience with LB 4.5.x and some very "legacy" C DLLs...
I know I had issues many many years ago getting the DLL functions to work properly until I found that part of the help file and started using the added chr$(0) to all the strings that were to be manipulated by the DLL functions.
I could be misinterpreting it, or maybe it is no longer required/implemented in that way.
Either way, whatever works...works...
{:0)
Brandon Parker
|
|
coda
Junior Member
Posts: 74
|
Post by coda on Oct 20, 2023 5:15:26 GMT -5
Thankyou so much for your input, everyone! Sorry it has taken me so long to reply but my internet availability has been sporadic at best. I think the confusing part is that in order to pass a string by value, you still have to use as ptr. But I think what you guys have said has cleared up the issue for me. Thankyou once again!
|
|
|
Post by Chris Iverson on Oct 20, 2023 14:17:46 GMT -5
If you're trying to understand how all this works, then I feel I should clarify that strings are never passed by value.
LB copying string memory around was done to simplify things for the LB user, but from the point of view of the DLL being called, strings are always passed by reference. The DLL will always get a pointer to the string, and will have to dereference that pointer to get the actual string value.
|
|
coda
Junior Member
Posts: 74
|
Post by coda on Oct 23, 2023 22:39:09 GMT -5
Thanks Chris. Yes, I mostly understood that... the part that confused me was that the LB syntax involved a pointer while LB and its help file seemed to be talking about strings in a more abstract way rather than in terms of their actual representation in memory which led me to think that the syntax used by LB for 'by value' would not involve a pointer (because it had abstracted away the actuality of strings for the LB programmer) whereas 'by reference' it would make reference to a string. It seems that is not the case and the actuality of a pointer is involved in the syntax for both.
|
|
|
Post by pierre on Oct 24, 2023 17:34:02 GMT -5
LB Help file : Passing strings into API/DLL calls. --------------------------------------------------
After all I have read here on the subject, I remain very puzzled.
Here is what I understand from the LB helpfile:
When we send a character string into an API call
(1) LB adds a null character (probably after checking if there was already one, or not ? ) and then sends only a copy of the complete string to the DLL. ====> BY VALUE. ====> There is no 'Returned Value' sent back to LB.
(2) LB checks for a null terminator: if the chr$(0) is present, then LB sends into the API the address of the place where the string resides in memory. ====> BY REFERENCE. ===> LB recieves in return the so-called "Returned Value" (the modified value of the character string).
Therefore, Brandon's observation is IMHO completely justified : when we want to pass a character string by reference we have to add a chr$(0). This is always true according to number (2) hereabove.
But is it really - always - true? And is it not just the example given in the LB help file that illustrates the confusion around all this?
Chris showed us that at least in this example, it is not necessary to add the null terminator. Even if we don't add the chr$(0), the modified value of the Result$ variable comes back from the DLL.
So, what are we doing ? We are looking for information about the default printer. First, we must know where to find (in the Windows ini file) the values of the variables appName$ and KeyName$. Then we have to send into the API call a memory buffer capable to hold the number of characters required by the awaited "Returned Value" ... plus 1 place for the "null terminator" that the DLL sends back to us. This is done through the variable Result$ which we fill with a certain number of space characters. but how many ? we don't know in advance..... so we can only guess... Bare in mind that it is just a question of reserving enough space for the answer, not a question of explicitly send a null terminator into the API. Here, the returned string length is 44. So, we should send a string variable "Return$" (as a ptr) of at least 44 + 1 = 45 spaces. That's all. Of course, the place that will be occupied by the null terminator has been provided for, but it is not necessary to send it explicitly as a chr$(0). The example gives us a guess of 49, that seems to be enough, so adding a hypothetical null terminator doesn't change anything. In this case, it is not required neither to make the convoluted left$(Result$ .... etc) detour to find the answer.. -As Chris showed us, just a trim$(Result$) is sufficient. If our guess was less than 45, then we would always have an answer, but the string would be truncated. So, it works, despite of the given example that seems to have been made to prove explanation (2).
At last, what do we have ?
- Reading the LB help file number (1) , if we don't add explicitly a null terminator, then LB adds one and sends a copy of the string into the API call, and we should not have any answer (?), but here - in the example edited by Chris - we do have an answer.....So explanation number (1) seems to be untrue....
- Brandon told us that he always adds a chr$(0) when passing a string by reference and it always works, according to explanation (2).
- Chris told us that he never had problems when passing string references into API calls, apparently not specially observing the text of the LB help file.
- And, one thing more: Both Walt and Chris told us that in LB, strings are always passed by reference into an API call.....So, if strings are never passed by value, what is the meaning of explanation number (1) hereabove?
Where is the truth?
Either the text of the help file is erroneous or at least confusing and should be edited, or the given example is not appropriated, or - what of course is quite possible - I am completely misunderstanding all this.
Perhaps Carl could give us some clarification ?
|
|
|
Post by Carl Gundel on Oct 25, 2023 16:21:20 GMT -5
LB Help file : Passing strings into API/DLL calls. -------------------------------------------------- After all I have read here on the subject, I remain very puzzled. <snipped> Perhaps Carl could give us some clarification ? Before I dig into this, is there some code that you've written that isn't working that we can have a look at? If you already posted something I apologize.
|
|
|
Post by pierre on Oct 25, 2023 18:56:30 GMT -5
Alas, for the moment no code written by myself.
AS far as API calls are concerned, I am not very experimented yet. Just trying to understand and making sense of it all.
You should read this thread in its entirety to see the problem.
In a nutshell:
The help file "Passing strings to API/DLL calls" shows two ways of passing strings: (1) by value - (2) by reference
a) Now, Walt and Chris are telling us that in LB strings are always passed into API calls by reference and not by value. If this is true, then the help file should perhaps be amended (?).
b) See also Brandon's observations and the modified example file that Chris provided to show that adding explicitly the null terminator is not necessary, at least in this example.
I know your time is precious, so I don't want to waste it, but some clarity about this subject would be very much appreciated
|
|