Avatar of staca04
staca04
Flag for United States of America asked on

string passed from vb.net express to C DLL is not updated

I am trying to convert a VB Script program to VB.NET (2008 Express). The script uses Automation COM to access HLLAPI under the covers, Jolly Giant QWS3270).  I am currently just trying to get knowledgeable with VB.NET, for a simple console application, calling WinHLLAPI DLL (Jolly Giant's QWS3270).  I have many of the WinHLLAPI functions working just fine. The problem calls are those which require a string buffer to be passed in and that will be updated by the HLLAPI DLL. In each case, the string is not updated but the return code (in pos variable)
is 0 - so WinHLLAPI thinks it did something.

I have reduced my code to a small example below. This calls a HLLAPI function that does not require connecting to a session (just keeping things simple).

I have searched and searched the internet and EE.  Most answers are from C++ perspective.
I am not writing C or C++, I am wrting VB.NET Express code, hopefully there is a VB.NET Express answer to this.  There seem to be plenty of people using VB.NET to call a C/C++ DLL
and pass in a string using ByVal and get the string updated - but I do not seem to be able to.
Hopefully, someone in EE can help.  Thank you

Option Explicit On
Imports System.Runtime.InteropServices

Module Module1

    <DllImport("C:\Program Files\QWS3270 PLUS\whllapi.dll")> _
     Sub WinHLLAPI(ByRef f As Long, ByVal s As String, ByRef l As Long, ByRef r As Long)
    End Sub

    Sub Main()
        Call QuerySystem()
        End
    End Sub

    Public Sub QuerySystem()
        Dim str As String
        Dim pos As Long
        Dim length As Long
        Dim number As Long

        ' HLLAPI function number 10: Query System
        number = 20
        ' preallocated buffer area
        str = Space(35)
        ' the length is not really needed, 35 is implied
        length = Len(str)
        ' position is not needed here
        pos = 0
        ' call hllapi function
        WinHLLAPI(number, str, length, pos)
    End Sub
Microsoft DevelopmentVisual Basic ClassicProgramming Languages-Other

Avatar of undefined
Last Comment
staca04

8/22/2022 - Mon
F. Dominicus

Stupid counter-question. What happens if you don't use ByVal?
Éric Moreau

change Byval to ByRef in :
ByVal s As String
staca04

ASKER
Using ByRef does not work. All calls fail. The string does not appear to make it in. Evidently, VB.NET and VB in general, a string structure is a BSTR.  Using ByRef will pass strings as a pointer to a pointer, and, ByVal, causes VB to pass the value of the BSTR variable, which is the pointer, not the pointer to a pointer.
Your help has saved me hundreds of hours of internet surfing.
fblack61
F. Dominicus

emoreaus comment just suggests the proper answer, That's what I asked too but. Just a bit background. the functions though it did something but it changed a copy of the original string therefor my "counter question and emoreaus comment. You want to change the "Parameter" you ca not change a Parameter which was handed over By Val...

Regards
Friedrich
F. Dominicus

Ah sorry, I did not have your comment before.

VB takes a BSTR but you can have BSTR also, I'd stil try to hand over the stuff as String, or if available in VB.NET check what encoding the string really can have.

see
http://msdn.microsoft.com/en-us/library/aa645736%28VS.71%29.aspx

for interop declarations.

Regards
Friedrich
F. Dominicus

Oh fine I found this:
Default Marshaling for Strings

Both the System..::.String and System.Text..::.StringBuilder classes have similar marshaling behavior.

Strings are marshaled as a COM-style BSTR type or as a character array terminating in a null reference (Nothing in Visual Basic). The characters within the string can be marshaled as Unicode or ANSI, or in a platform-dependent manner (Unicode on Microsoft Windows NT, Windows 2000, and Windows XP; ANSI on Windows 98 and Windows Millennium Edition (Windows Me).

This topic provides the following information on marshaling string types:


So the answer probably is then you have to use the W Version of the API call (if there is one)

   Sub WinHLLAPIW(ByRef f As Long, ByRef s As String, ByRef l As Long, ByRef r As Long)

Regards
Friedrich
⚡ FREE TRIAL OFFER
Try out a week of full access for free.
Find out why thousands trust the EE community with their toughest problems.
staca04

ASKER
If I use ByRef, I get SystemEngineException.

When I use ByVal, the string is passed in but is not updated.  ByVal works fine to pass strings IN,
 the string is not updated, in other words, after the function returns, the string contains the same
 content.  I can pass a string, for example 'Test 3270' to the SendKeys function and I will see
 'Test 3270' appear on the terminal emulatior's screen - that is proof positive ByVal is what I need to
 use.  

I tried WinHLLAPIW but got Entry Point not found exception - the HLLAPI api is a well established interface in the mainframe world - been around maybe 30 years - I had not ever seen a reference
to WinHLLAPI - but I gave it shot.

My guess still is that VB.NET (Visual Basic 2008 Express) is doing something different, maybe
 garbage collection which moves the string - totally a guess.

I do not think this is an encoding (Unicode/ASCII) problem because I am *not* getting garbage
in the string, I am getting no change in the string.

I will review the suggested msdn document for clues.

I have also emailed Jolly Giant to get their take on it.

In the end, maybe I simply can not use Visual Basic 2008 (Express or otherwise).  Maybe I will need to
try to FreeBasic, PowerBasic or REALBasic - or worse, learn C again (I used it 20 years ago).
F. Dominicus

Well I can not follow your argument, By Val is not correct because you want the API Call to change this parameter so it must be By Ref. You have nothing else which might  return some value. And you preallocatoin mean you, give it a clean string and it's supposed to be changed.... . do you have the C-header file for this library around, what parameters are there given?
staca04

ASKER
Let me say I appreciate the help very much.  
This should not be this difficult, but obviously it is, we just have to keep plugging along.
 It has to be somthing simpler - but heck if I know what it is.


the DLL can not be COM-enabled - the HLLAPI/WinHLLAPI interface is 30 years old, long before COM existed.


this is the protype statement from the HLLAPI.h file in their C sample:

VOID FAR PASCAL _stdcall WinHLLAPI(LPWORD, LPBYTE, LPWORD, LPWORD);

Here is call information from their general Help file:

The WinHLLAPI( ) call requires you to specify four parameters in every call and has the following format:

extern VOID FAR PASCAL WinHLLAPI(  )

LPWORD lpwFunction,      /* Function name */
LPBYTE lpbyString,      /* String pointer */
LPWORD lpwLength,      /* String (data) length */
LPWORD lpwReturnCode );      /* Return code */


I am reading thru the Interop stuff -  holy freaking cow - is it really this complicated/difficult ?
  All this marshalling stuff is mind-boggling to say the least.

Experts Exchange has (a) saved my job multiple times, (b) saved me hours, days, and even weeks of work, and often (c) makes me look like a superhero! This place is MAGIC!
Walt Forbes
nffvrxqgrcfqvvc

You should be using UInt16 instead of Long also in your code example you have the following:
' HLLAPI function number 10: Query System
 number = 20
You comment function name as 10 but you assign 20? That a typo?
Try something like this:
 

<DllImport("whllapi.dll", ExactSpelling:=True)> _
    Private Shared Sub WinHLLAPI(ByVal lpwFunction As UInt16, ByVal lpbyString As System.Text.StringBuilder, ByVal lpwLength As UInt16, ByRef lpwReturnCode As UInt16)
    End Sub

    Public Sub Test()

        Dim funcName As UInt16 = 20 ' function name
        Dim ReturnCode As UInt16 = 0 'return code
        Dim sbData As New System.Text.StringBuilder(35)

        WinHLLAPI(funcName, sbData, Convert.ToUInt16(sbData.Length), ReturnCode)
        Console.WriteLine(sbData.ToString)

    End Sub

Open in new window

nffvrxqgrcfqvvc

Correction the line below:
WinHLLAPI(funcName, sbData, Convert.ToUInt16(sbData.Length), ReturnCode)
Should be this instead: Assuming it wants the length of the buffer and isn't a pointer to the returned length copied to the string pointer.
WinHLLAPI(funcName, sbData, Convert.ToUInt16(sbData.Capacity), ReturnCode)
staca04

ASKER
the 20 is correct function number for HLLAPI QuerySystem, but good eyeball catching the comment.

Well, I do not understand why you want to try Unit16 and StringBuilder, but I did try it. It fails with system access violation.  This is exact code I tried::

Option Explicit On
Imports System.Runtime.InteropServices
Imports System.Text

Module Module1
    <DllImport("C:\Program Files\QWS3270 PLUS\whllapi.dll")> _
    Private Sub WinHLLAPI(ByVal lpwFunction As UInt16, ByVal lpbyString As System.Text.StringBuilder, ByVal lpwLength As UInt16, ByRef lpwReturnCode As UInt16)
    End Sub

    Sub Main()
        Call Test()
        End
    End Sub

    Public Sub Test()
        Dim funcName As UInt16 = 20 ' function name QuerySystem (does not need Connect call)
        Dim ReturnCode As UInt16 = 0 'return code
        Dim sbData As New System.Text.StringBuilder(35)

        WinHLLAPI(funcName, sbData, Convert.ToUInt16(sbData.Capacity), ReturnCode)

    End Sub
End Module

  The  System.AccessViolationException occurs on the Convert.ToUnit16(sbData.Capacity).
  I do not follow how you came to suggeting to use Unit16 and StringBuilder.
⚡ FREE TRIAL OFFER
Try out a week of full access for free.
Find out why thousands trust the EE community with their toughest problems.
nffvrxqgrcfqvvc

Do you know if lpwLength wants the size of the buffer or does it return the size of the data that was copied to the buffer, or does it do both.
I am just looking at the decleration you provided and it doesn't use 64 bit integer.
Have a look at the updated example and see if it works.
 

<DllImport("whllapi.dll", ExactSpelling:=True)> _
    Private Shared Sub WinHLLAPI(ByVal lpwFunction As UInt16, ByVal lpbyString As System.Text.StringBuilder, ByRef lpwLength As UInt16, ByRef lpwReturnCode As UInt16)
    End Sub

    Public Sub Test()

        Dim funcName As UInt16 = 20 ' function name 
        Dim ReturnCode As UInt16 = 0 'return code 
        Dim sbData As New System.Text.StringBuilder(35)
        Dim pLength As UInt16 = sbData.Capacity

        WinHLLAPI(funcName, sbData, pLength, ReturnCode)
        Console.WriteLine(sbData.ToString)

    End Sub

Open in new window

ASKER CERTIFIED SOLUTION
F. Dominicus

THIS SOLUTION ONLY AVAILABLE TO MEMBERS.
View this solution by signing up for a free trial.
Members can start a 7-Day free trial and enjoy unlimited access to the platform.
See Pricing Options
Start Free Trial
GET A PERSONALIZED SOLUTION
Ask your own question & get feedback from real experts
Find out why thousands trust the EE community with their toughest problems.
staca04

ASKER

I tried it but no data was displayed, there were no errors indicated. There is no data returned, rather,
 the parameters passed in are updated by the dll.  The return value has to be ignored, the dll does not
 intend to return, it intends to update what is passed in.


Going back to my original post, I have added code for a couple other functions, all of this works just fine except for the one call that should be getting an updated string. The calls that pass in a string (typig on the screen, searching the string) work fine. The QuerySystem call (as well as others I have not included here) passes in a string to be updated - but the string is unchanged.  Given all of this works
as expected, including the updating of the length and pos parameters that were passed in.


Option Explicit On
Imports System.Runtime.InteropServices

Module Module1

    <DllImport("C:\Program Files\QWS3270 PLUS\whllapi.dll")> _
     Sub WinHLLAPI(ByRef f As Long, ByVal s As String, ByRef l As Long, ByRef r As Long)
    End Sub

    Sub Main()
        Call QuerySystem()
        Call Connect()
        Call SendKeyStrokes()
        Call SearchScreen()
        Call Disconnect()
        End
    End Sub

    Public Sub QuerySystem()
        Dim str As String
        Dim pos As Long
        Dim length As Long
        Dim number As Long

        ' HLLAPI function number 20: Query System
        number = 20

        ' work area ????
        str = Space$(35)

        ' the length is not needed, 35 is implied
        length = Len(str)

        ' position is not needed here
        pos = 0

        ' call hllapi function
        WinHLLAPI(number, str, length, pos)

    End Sub


    '
    Private Sub Connect()

        Dim str As String
        Dim pos As Long
        Dim length As Long
        Dim number As Long

        ' HLLAPI function number 1: Connect presentation space
        ' data string is "A" by default
        ' data length is 1 by default
        ' position is not needed, default to 0
        ' init variables
        number = 1
        str = "A"
        length = 1
        pos = 0

        ' make sure we trap an error loading the dll
        On Error Resume Next
        ' call HLLAPI function
        WinHLLAPI(number, str, length, pos)
        ' check if we got an error
        If (Err.Number = 48) Then     ' 48 = runtime error loading dll or file not found
            MsgBox("Error loading WHLLAPI DLL" & vbNewLine & vbNewLine & "This program cannot continue!" _
                    , vbOKOnly + vbCritical _
                    , "Error loading DLL")
            End     ' stop the program
        End If
        ' reset the error handler
        On Error GoTo 0

    End Sub

    '
    Private Sub Disconnect()

        Dim str As String
        Dim pos As Long
        Dim length As Long
        Dim number As Long

        ' HLLAPI function number 2: Disconnect presentation space
        number = 2

        ' "A" by default in this case
        str = "A"

        ' the length defaults to 1
        length = 1

        ' position is not needed here
        pos = 0

        ' call hllapi function
        WinHLLAPI(number, str, length, pos)

    End Sub


    Public Sub SendKeyStrokes()
        Dim str As String
        Dim pos As Long
        Dim length As Long
        Dim number As Long

        ' HLLAPI function number 3: Send Key
        number = 3

        ' cursor right,cursor right,cursor left,cursor left, type string "KAC3270 Test", cursor right,cursor left
        str = "3270 Test"

        ' the length is not needed
        length = Len(str)

        ' position is not needed here
        pos = 0

        ' call hllapi function
        WinHLLAPI(number, str, length, pos)

    End Sub


    Public Sub SearchScreen()
        Dim str As String
        Dim pos As Long
        Dim length As Long
        Dim number As Long

        ' HLLAPI function number 6: Search Presentation Space
        number = 6

        ' cursor right,cursor right,cursor left,cursor left
        str = "3270 Test"

        ' the length is not needed on input - will contain position of string after call
        length = Len(str)

        ' position is not needed here
        pos = 0

        ' call hllapi function
        WinHLLAPI(number, str, length, pos)

    End Sub

End Module

nffvrxqgrcfqvvc

@fridom
It's correct the API wants a 2 byte pointer the only reason his Long worked is because it's large enough to hold the data. I haven't  advised the OP to use UInt16 for the string pointer so I don't understand why you think that is the case here.
@staca04
Do you have more documentation a description for each of the parameters would help. The decleration should be correct per your code example.
I started with Experts Exchange in 2004 and it's been a mainstay of my professional computing life since. It helped me launch a career as a programmer / Oracle data analyst
William Peck
staca04

ASKER
I have heard from Jolly Giant.  They advise C-style string and VB string are different.  I will have to use a Byte array (instead of a string) and ensure it is null terminated :  ByVal s As Byte ()  

I will give it a try and update you all later.
staca04

ASKER
A Byte array works!  

 So, we change the sub declaration to specify "As Byte()"

<DllImport("C:\Program Files\QWS3270 PLUS\whllapi.dll")> _
     Sub WinHLLAPI(ByRef f As Long, ByVal s As Byte(), ByRef l As Long, ByRef r As Long)
    End Sub

And, we change the routines to move string into a byte array and (when needed) move the byte array
  back into a string.

    Public Sub QuerySystem()
        Dim str As String
        Dim pos As Long
        Dim length As Long
        Dim number As Long

        ' HLLAPI function number 20: Query System
        number = 20

        ' work area
        str = Space(35)
        '  move string to byte array
        buffer = System.Text.ASCIIEncoding.ASCII.GetBytes(str)

        ' the length is not needed, 35 is implied, but specify anyway
        length = Len(str)

        ' position is not needed here
        pos = 0

        ' call hllapi function passing byte array instead of a string
        WinHLLAPI(number, buffer, length, pos)

        ' move byte buffer back to a string - only needed when you expect buffer was updated
        str = System.Text.ASCIIEncoding.ASCII.GetString(buffer)
    End Sub

...

It is a good day when you learn something!  Again, thanks to all for their efforts!

Now I have to figure out how to close this question.
staca04

ASKER
Ok, I forgot to show buffer being allocated, I just used a public variable, 100 bytes was more than was
 needed for this simple starting case.

    Public buffer(100) As Byte
⚡ FREE TRIAL OFFER
Try out a week of full access for free.
Find out why thousands trust the EE community with their toughest problems.
nffvrxqgrcfqvvc

<< I will have to use a Byte array (instead of a string) and ensure it is null terminated : >>
The string builder should have worked with modification but it's good to know you got it working.

<DllImport("whllapi.dll", ExactSpelling:=True)> _
    Private Shared Sub WinHLLAPI(ByVal lpwFunction As UInt16, <MarshalAs(UnmanagedType.LPStr)> ByVal lpbyString As System.Text.StringBuilder, ByRef lpwLength As UInt16, ByRef lpwReturnCode As UInt16)
    End Sub

Open in new window

SOLUTION
F. Dominicus

THIS SOLUTION ONLY AVAILABLE TO MEMBERS.
View this solution by signing up for a free trial.
Members can start a 7-Day free trial and enjoy unlimited access to the platform.
See Pricing Options
Start Free Trial
⚡ FREE TRIAL OFFER
Try out a week of full access for free.
Find out why thousands trust the EE community with their toughest problems.
nffvrxqgrcfqvvc

I have to agree with fridom his comment was also correct about using byte but just overlooked that I changed the decleration to use 2 byte integers because you are using 64 bit integer Long data type. He confused that change thinking I was refering to using that for the string pointer. I don't think this issue to simply use a Byte() worked here there was something else changed to get it to work because the decleration using the StringBuilder should have worked in the same manner.
 
staca04

ASKER
I am not fussed with whomever needs to get extra points. I still appreciate the efforts.

I tried several of the things that were suggested - those things did not work -- very possibly because I did not fully understand what was being suggested or exactly how to use it (I had never used Stringbuidler before) -- the simpliest solution was "use a byte arrary" -- I fully understood that simple answer and got it working quickly, so the byte array is the working solution I used and have fully incorporated in the project I am working on.

Experts Exchange is like having an extremely knowledgeable team sitting and waiting for your call. Couldn't do my job half as well as I do without it!
James Murphy
staca04

ASKER
when I got fridom's solution, I could "not see the forest because of the trees". Some one else stated it simpler in a 4-word answer "use a byte array". so I award the points to fridom