staca04
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.InteropServ ices
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
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.InteropServ
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
Stupid counter-question. What happens if you don't use ByVal?
change Byval to ByRef in :
ByVal s As String
ByVal s As String
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.
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
Regards
Friedrich
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
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
Oh fine I found this:
Default Marshaling for Strings
Both the System..::.String and System.Text..::.StringBuil der 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
Default Marshaling for Strings
Both the System..::.String and System.Text..::.StringBuil
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
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).
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).
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?
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.
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.
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:
' 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
Correction the line below:
WinHLLAPI(funcName, sbData, Convert.ToUInt16(sbData.Le ngth), 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.Ca pacity), ReturnCode)
WinHLLAPI(funcName, sbData, Convert.ToUInt16(sbData.Le
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.Ca
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.InteropServ ices
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.Ca pacity), ReturnCode)
End Sub
End Module
The System.AccessViolationExce ption occurs on the Convert.ToUnit16(sbData.Ca pacity).
I do not follow how you came to suggeting to use Unit16 and StringBuilder.
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.InteropServ
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,
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(
WinHLLAPI(funcName, sbData, Convert.ToUInt16(sbData.Ca
End Sub
End Module
The System.AccessViolationExce
I do not follow how you came to suggeting to use Unit16 and StringBuilder.
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.
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
ASKER CERTIFIED SOLUTION
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
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.InteropServ
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
@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.
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.
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.
I will give it a try and update you all later.
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.GetB ytes(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.GetS tring(buff er)
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.
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.
' 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.
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.
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
needed for this simple starting case.
Public buffer(100) As Byte
<< 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.
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
SOLUTION
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
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.
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.
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.
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