Return string from PureBasic DLL function crashes ms-access

Just starting out? Need help? Post your questions and find answers here.
User avatar
AndrewM
User
User
Posts: 39
Joined: Wed Nov 06, 2013 3:55 am
Location: Australia

Return string from PureBasic DLL function crashes ms-access

Post by AndrewM »

If I create a DLL that returns a string to ms-access, ms-access 'stops working' and crashes. If I convert the string to a double, then the DLL works fine an ms-access does not crash. I also suspect that the DLL remains loaded after the calling script has terminated however this could be by design. Closing ms-access stops the DLL. It is a minor pest having to open and close ms-access everytime I need to recompile the DLL. No being able to pass strings is a serious limitation.

I am using ms-access 2010 32 bit on Win 7 x64. Code that can be used to test for the bug is located in a post on EXIF data extraction from photos. http://www.purebasic.fr/english/viewtop ... 2&start=15
User avatar
AndrewM
User
User
Posts: 39
Joined: Wed Nov 06, 2013 3:55 am
Location: Australia

Re: Return string from PureBasic DLL function crashes ms-acc

Post by AndrewM »

On close investigation, the issue is not a PureBasic bug, rather it is bad information in the PB help file. According to a MicroSofts MSDN web site at DLL can only return a number as a function return which explains why I can get the DLL to work with numbers. In the case of strings, the DLL can change the value of the string but cannot return the string as a function return. Any function return from a DLL that changes a string will be a number and the number can be used to indicate something like the length of the new string. When as string is passed to a DLL, it is always passed by reference and a DLL may change this string. As the string has been passed by reference, it will also be changed within ms-access or ms-excel. Just use the string variable that was passed to the DLL as it will now have a changed value. No function return value from the DLL call is required. There are some other factors, the string that is passed to the DLL must be terminated with a null character, so you have to add vbNullChar to the end of your string before passing it from Excel or Access (I have not tested if PB handles this yet). Secondly, the string that has been passed has to be large enough to handle the changed value, so you may need to add several null characters to the end of your string to make it long enough to handle any change.

In summary, calls to functions in DLLs work if a number is expected as a return value. Getting a DLL to change a string works differently. I have not tested whether I can use a DLL to change a string yet with the string being later used in ms-access. The purpose of this post was to indicate the source of the issue. The information in the Help File for PB works for DLL functions that return numbers. It does not work for functions that work with strings.

The full explanation is here http://msdn.microsoft.com/en-us/library ... e.10).aspx in an Offce Dev Centre article called "Returning Strings from DLL Functions". Here are the breadcrumbs that lead to the article. "Office development > Office desktop > Office XP > Office XP Developer > Microsoft Office XP Developer > Programming Concepts > The Windows API and Other Dynamic-Link Libraries > Calling DLL Functions > Returning Strings from DLL Functions"
User avatar
AndrewM
User
User
Posts: 39
Joined: Wed Nov 06, 2013 3:55 am
Location: Australia

Re: Return string from PureBasic DLL function crashes ms-acc

Post by AndrewM »

I would like to get string operations working, namely to pass a string from ms-access to pb, change the string in pb and then get the changed string back in ms-access. Here is some test code that I am working to identify the issue. I have not worked with pointers before so the problem lies here.

PB DLL code with 3 versions of the same procedure. Issues are described in comments.

Code: Select all

Global  MyString$
Global InputString$
  
ProcedureDLL.d StringChangerD (InputString$)  
    ;Works but returns a number and does not change string as the string was provided by Value and not as a reference.
    ;Need to change the input to a memory address - too hard for me
    success.d
    MessageRequester( "test",InputString$)
    
    MyString$ = "Hello to you too"
    If Len(InputString$) > Len(MyString$)
        InputString$=MyString$
        success = -1
    Else
        success =0
    EndIf
    ProcedureReturn success 
EndProcedure

ProcedureDLL.s StringChangerS (InputString$)  
    ;Does not work - triggers error 16 in ms-access 2010 (Expression too Complex) which means that access was expecting an expression that would evaluate to a number rather than being presented with a string.
    success.d
    MessageRequester( "test",InputString$)
    
    MyString$ = "Hello to you too"
    If Len(InputString$) > Len(MyString$)
        InputString$=MyString$
    EndIf
    ProcedureReturn MyString$ 
EndProcedure

ProcedureDLL.d StringChangerR (*Ptr)  
    ;Does not work - reads gibberish - problem is likely to be that I am not passing a memory address from ms-access (how does one do this?)
    success.d
    message.s
    Rascii.s
    Runicode.s
    Rutf8.s

    Rascii= PeekS(*Ptr,-1,#PB_Ascii)
    Runicode=PeekS(*Ptr,-1,#PB_Unicode)
    Rutf8=PeekS(*Ptr,-1,#PB_UTF8)
    
    message="Ascii: "+Rascii+Chr(10)+"Unicode: "+Runicode+Chr(10)+"Utf8: "+Rutf8 ;Figure out which encoding has been used
    MessageRequester( "test",message)
    
    MyString$ = "Hello to you too"
    If Len(RefString$) > Len(MyString$)
       ;PokeS(,MyString$)
        success = -1
    Else
        success =0
    EndIf
    ProcedureReturn success 
EndProcedure
Here is the calling code which need to be places in a standard module in ms-access. It only calls the last procedure in the above code.
===VBA===

Code: Select all

Option Compare Database
Option Explicit

Public Declare Function StringChangerR Lib "D:\Temp\2013\Access\StringChanger.dll" (MyString) As Double

Public Sub ChangeMyString()

    Dim success As Integer
    Dim MyString As String
    
    MyString = "Hello World" & String$(100, vbNullChar) 'Pad string with null to make it longer than return value.
    success = StringChangerR(MyString)
    Debug.Print success
    Debug.Print MyString

End Sub
===End VBA===

I can't get this working. Need help.

Andrew M
User avatar
netmaestro
PureBasic Bullfrog
PureBasic Bullfrog
Posts: 8451
Joined: Wed Jul 06, 2005 5:42 am
Location: Fort Nelson, BC, Canada

Re: Return string from PureBasic DLL function crashes ms-acc

Post by netmaestro »

I'm not sure but access might expect a bstr. You can make one out of a string like this:

Code: Select all

Procedure StringToBStr (string$) ; By Zapman Inspired by Fr34k 
  Protected Unicode$ = Space(Len(String$)* 2 + 2) 
  Protected bstr_string.l 
  PokeS(@Unicode$, String$, -1, #PB_Unicode) 
  bstr_string = SysAllocString_(@Unicode$) 
  ProcedureReturn bstr_string 
EndProcedure 
This converts your string to a bstr and returns a pointer to it. It may help.
BERESHEIT
User avatar
AndrewM
User
User
Posts: 39
Joined: Wed Nov 06, 2013 3:55 am
Location: Australia

Re: Return string from PureBasic DLL function crashes ms-acc

Post by AndrewM »

Thanks netmaestro,
As I have to pass the variable as reference, I should be able to continue using the same variable after pb has altered it. I think the issue is that I don't know how to read the memory address provided my ms-access in pb.

I have to meditate on the code above for a while.

Cheers
acreis
Enthusiast
Enthusiast
Posts: 204
Joined: Fri Jun 01, 2012 12:20 am

Re: Return string from PureBasic DLL function crashes ms-acc

Post by acreis »

I have had success with this code:

MS ACCESS

Code: Select all


Option Compare Database
Option Explicit

Public Declare Function StringChangerR Lib "test.dll" (ByVal PointerToString As Long) As Integer

Public Sub ChangeMyString()

    Dim success As Integer
    Dim MyString As String
    
    MyString = "Hello World" & String$(100, vbNullChar) 'Pad string with null to make it longer than return value.
    success = StringChangerR(StrPtr(MyString))
    Debug.Print success
    Debug.Print MyString

End Sub

PB DLL

Code: Select all

EnableExplicit

ProcedureDLL StringChangerR (*Ptr)  
    ;Does not work - reads gibberish - problem is likely to be that I am not passing a memory address from ms-access (how does one do this?)
    Protected success,
    message.s,
    Rascii.s,
    Runicode.s,
    Rutf8.s

    Rascii= PeekS(*Ptr,-1,#PB_Ascii)
    Runicode=PeekS(*Ptr,-1,#PB_Unicode)
    Rutf8=PeekS(*Ptr,-1,#PB_UTF8)
    
    message="Ascii: "+Rascii+Chr(10)+"Unicode: "+Runicode+Chr(10)+"Utf8: "+Rutf8 ;Figure out which encoding has been used
    MessageRequester( "test",message)
    
;    Protected MyString$ = "Hello to you too"
;     If Len(RefString$) > Len(MyString$)
;        ;PokeS(,MyString$)
;         success = -1
;     Else
;         success =0
;     EndIf
    ProcedureReturn success 
EndProcedure
Give it a try

Best regards
User avatar
AndrewM
User
User
Posts: 39
Joined: Wed Nov 06, 2013 3:55 am
Location: Australia

Re: Return string from PureBasic DLL function crashes ms-acc

Post by AndrewM »

Thanks acreis,
I will give it a go. The StrPtr function is not listed in the ms-access developer reference so no one would find it from that starting point.

On another matter there is another problem with using dll's in ms-access and that is that the path to the dll is hardwritten into the declare statement

Code: Select all

e.g. Public Declare Function StringChangerR Lib "D:\Temp\2013\Access\StringChanger.dll" (MyString) As Double
This took me a while to solve as you cannot replace the path to the dll with a variable. The way to tell ms-access where to find the variable is to change the current directory to the directory with the dll. In my case the drive letter may be different to what it was before so I will have to change the drive letter too. To declare a function in ms-access with a relative path to a dll do the following. It assumes that the dll is in a lib directory which is located in the same folder as the ms-access project.

Code: Select all

===VBA===
    'Set the  dll subdirectory
        mAppDrive = Left(Application.CurrentProject.Path,1) 'Get the drive letter for the drive which hosts the ms-access project
        ChDrive mAppDrive    'Change the current drive
        mLibDrive = Application.CurrentProject.Pat & "\Lib"  'Work out where the lib directory should be (and add some code to test that it exists)
        ChDir mLibDrive  'Change the directory
        Debug.Print CurDir  'Confirm the change
===End VBA===
All the validation and error handling code has been removed for clarity. It is odd but having been working within the confines of ms-access for 20 years, I am still getting to grips with this working with files and directories stuff. I can imagine that scores of other people get stuck with this issue as it is very hard to find the answer on the net. This solutions should also work for ms-Excel. Tested in ms-access 2010.
Post Reply