Need help calling a proprietary C DLL from Python

C

Craig

I use a proprietary dll from Software Source (vbis5032.dll). I have
successfully used it from Visual Basic 6, Fujitsu Cobol and from Perl.
I would now like to use it from Python.

The following is the C++ prototype for one of the functions:
short FAR PASCAL VmxOpen(BSTR *Filespec,
LPSHORT lpLocatorSize,
LPSHORT lpOmode,
LPHANDLE lphwmcb,
BSTR *Password);

The following is some Python code I put together:
from ctypes import *
from ctypes.util import *
libc = cdll.msvcrt
printf = libc.printf
sprintf = libc.sprintf
print "\nVB/ISAM testing:"
fName = windll.oleaut32.SysAllocStringByteLen("s:\\msdb\\dcod\x00",
13)
printf ("fName = \x22%slx22\n", fName)
pw = windll.oleaut32.SysAllocStringByteLen("XYZ\x00", 4)
printf ("pw = \x22%slx22\n", pw)
CacheSize = c_long(0)
OpenMode = c_long(3)
vHandle = c_long(0)
p_vHandle = pointer(vHandle)
X = c_long(0)
printf ("Before - X = %d, CacheSize = %d, OpenMode = %d, vHandle = %d
\n", X, CacheSize, OpenMode, vHandle)
print "Ready to call (Library = " + find_library("vbis5032") + ") ..."

X = windll.vbis5032.VmxOpen(byref(fName), byref(CacheSize),
byref(OpenMode), byref(vHandle), byref(pw))

printf ("After - X = %d, CacheSize = %d, OpenMode = %d, vHandle = %d
\n", X, CacheSize, OpenMode, vHandle)

exit(0)


The following is the output from the Python code:
VB/ISAM testing:
fName = "s:\msdb\dcodlx22
pw = "XYZlx22
Before - X = 0, CacheSize = 0, OpenMode = 3, vHandle = 0
Ready to call (Library = C:\Windows\vbis5032.dll) ...
Traceback (most recent call last):
File "C:\temp\test3.py", line 19, in <module>
X = windll.vbis5032.VmxOpen(byref(fName), byref(CacheSize),
byref(OpenMode),
byref(vHandle), byref(pw))
TypeError: byref() argument must be a ctypes instance, not 'int'


I am neither a C++ nor a Python expert, but having already used this
dll from other languages, I know this should be possible. I am sure it
is just my lack of knowledge of Python.

Can anyone assist with this?
 
S

sturlamolden

Before - X = 0, CacheSize = 0, OpenMode = 3, vHandle = 0

This binds these names to Python ints, but byref expects C types.

Also observe that CacheSize and OpenMode should be c_short.
 
C

Craig

This binds these names to Python ints, but byref expects C types.

Also observe that CacheSize and OpenMode should be c_short.

I changed CacheSize and OpenMode to c_short, and commented out that
line producing the "Before" message, and the output is the same.

Further "tinkering" revealed that it is the byref on the fName and pw
that are causing the error.

The entire problem appears to be around the production of a BSTR and
the passing of pointers (byref) to the BSTR.
 
C

Craig

I changed CacheSize and OpenMode to c_short, and commented out that
line producing the "Before" message, and the output is the same.

Further "tinkering" revealed that it is the byref on the fName and pw
that are causing the error.

The entire problem appears to be around the production of a BSTR and
the passing of pointers (byref) to the BSTR.


Can anyone shed some light on how to work with BSTR's?
 
C

Chris Mellon

Can anyone shed some light on how to work with BSTR's?

Since you didn't tell ctypes about the function signature, it assumes
it returns int and gives you a python int. Provide a function
signature (c_void_p should work for BSTR) for both functions and you
should have an easier time of it.
 
C

Craig

Can anyone shed some light on how to work with BSTR's?

Since you didn't tell ctypes about the function signature, it assumes
it returns int and gives you a python int. Provide a function
signature (c_void_p should work for BSTR) for both functions and you
should have an easier time of it.




Thanks Chris.

I changed X to a c_short also, and modified the example as suggested:
X = c_short(0)
X = windll.vbis5032.VmxOpen(byref(c_void_p(fName)), byref(CacheSize),
byref(OpenMode), byref(vHandle), byref(c_void_p(pw)))
printf ("After - X = %d, CacheSize = %d, OpenMode = %d, vHandle = %d
\n", X, CacheSize, OpenMode, vHandle)

And, this is what printed:
After - X = 4325376, CacheSize = 0, OpenMode = 3, vHandle = 17586736

The printf shows that the values of X and vHandle have changed, but I
am not sure it is working properly as X should be a MUCH smaller
number (< 256). I would have expected a 0 (VIS_OK), 13 (VIS_DOS_ERROR)
or 14(VIS_DISK_ERROR) if it had worked.

Next, I changed:
fName = windll.oleaut32.SysAllocStringByteLen("u:\\msdb\\dcod\x00",
13)
so that I knew it would not find the file, and got:
After - X = 2555917, CacheSize = 0, OpenMode = 3, vHandle = 0

The vHandle staying 0 tells me that it did not find the file, but the
value of X seems random.

Based on the original C prototype, what am I doing wrong so that the
return from the call to vbis5032 is not "usable"?
 
S

sturlamolden

The following is the C++ prototype for one of the functions:
short FAR PASCAL VmxOpen(BSTR *Filespec,
LPSHORT lpLocatorSize,
LPSHORT lpOmode,
LPHANDLE lphwmcb,
BSTR *Password);


import ctypes
import comtypes

LPBSTR = ctypes.POINTER(comtypes.BSTR)
HANDLE = ctypes.POINTER(ctypes.POINTER(ctypes.c_long))
LPHANDLE = ctypes.POINTER(HANDLE)
LPSHORT = ctypes.POINTER(ctypes.c_short)

VmxOpen = ctypes.windll.vbis5032.VmxOpen
VmxOpen.restype = c_short
VmxOpen.argtypes = [LPBSTR, LPSHORT, LPSHORT, LPHANDLE, LPBSTR]


Filespec = comtypes.BSTR('blahblahblah')
LocatorSize = ctypes.c_short(256)
Omode = ctypes.c_short(1)
hwmcb = HANDLE()
Password = comtypes.BSTR('blahblahblah')


res = WmxOpen( byref(Filespec), byref(LocatorSize),
byref(Omode), byref(hwmcb), byref(Password) )


or if that fails:

pFilespec = ctypes.pointer(Filespec)
pLocatorSize = ctypes.pointer(LocatorSize)
pOmode = ctypes.pointer(Omode)
phwmcb = ctypes.pointer(hwmcb)
pPassword = ctypes.pointer(Password)

res = WmxOpen( pFilespec, pLocatorSize, pOmode, phwmcb, pPassword )
 
C

Craig

The following is the C++ prototype for one of the functions:
short FAR PASCAL VmxOpen(BSTR *Filespec,
LPSHORT lpLocatorSize,
LPSHORT lpOmode,
LPHANDLE lphwmcb,
BSTR *Password);

import ctypes
import comtypes

LPBSTR = ctypes.POINTER(comtypes.BSTR)
HANDLE = ctypes.POINTER(ctypes.POINTER(ctypes.c_long))
LPHANDLE = ctypes.POINTER(HANDLE)
LPSHORT = ctypes.POINTER(ctypes.c_short)

VmxOpen = ctypes.windll.vbis5032.VmxOpen
VmxOpen.restype = c_short
VmxOpen.argtypes = [LPBSTR, LPSHORT, LPSHORT, LPHANDLE, LPBSTR]

Filespec = comtypes.BSTR('blahblahblah')
LocatorSize = ctypes.c_short(256)
Omode = ctypes.c_short(1)
hwmcb = HANDLE()
Password = comtypes.BSTR('blahblahblah')

res = WmxOpen( byref(Filespec), byref(LocatorSize),
byref(Omode), byref(hwmcb), byref(Password) )

or if that fails:

pFilespec = ctypes.pointer(Filespec)
pLocatorSize = ctypes.pointer(LocatorSize)
pOmode = ctypes.pointer(Omode)
phwmcb = ctypes.pointer(hwmcb)
pPassword = ctypes.pointer(Password)

res = WmxOpen( pFilespec, pLocatorSize, pOmode, phwmcb, pPassword )

sturlamolden, thanks for the input.

I had to change your example to the following to get it to run:
from ctypes import *
import comtypes

LPBSTR = POINTER(comtypes.BSTR)
HANDLE = POINTER(POINTER(c_long))
LPHANDLE = POINTER(HANDLE)
LPSHORT = POINTER(c_short)

VmxOpen = windll.vbis5032.VmxOpen
VmxOpen.restype = c_short
VmxOpen.argtypes = [LPBSTR, LPSHORT, LPSHORT, LPHANDLE, LPBSTR]

Filespec = comtypes.BSTR('s:\\msdb\\dcod')
LocatorSize = c_short(0)
Omode = c_short(3)
hwmcb = HANDLE()
Password = comtypes.BSTR('GU32ASBURYPARK60A42A20')

res = VmxOpen( byref(Filespec), byref(LocatorSize), byref(Omode),
byref(hwmcb), byref(Password) )

print "res = " + str(res)

pFilespec = pointer(Filespec)
pLocatorSize = pointer(LocatorSize)
pOmode = pointer(Omode)
phwmcb = pointer(hwmcb)
pPassword = pointer(Password)

res = VmxOpen( pFilespec, pLocatorSize, pOmode, phwmcb, pPassword )

print "res = " + str(res)


After changing the assignment for Password to the correct value, both
calls to VmxOpen returned a status of 20, which indicates an invalid
password.

I received a direct email from someone, and I came up with the
following after implementing his advice:
from ctypes import *
from ctypes.util import *
libc = cdll.msvcrt
printf = libc.printf
FileSpec = windll.oleaut32.SysAllocStringByteLen("s:\\msdb\\dcod\x00",
13)
printf ("FileSpec = \x22%s\x22\n", FileSpec)
Password = windll.oleaut32.SysAllocStringByteLen("XYZ\x00", 4)
printf ("Password = \x22%s\x22\n", Password)
LocatorSize = c_short(0)
Omode = c_short(3)
hwmcb = c_long(0)
X = c_short(0)
printf ("Before - X = %#x (%d), LocatorSize = %d, Omode = %d, hwmcb =
%d\n", X, X, LocatorSize, Omode, hwmcb)
print "Ready to call (Library = " + find_library("vbis5032") + ") ..."
X.value = windll.vbis5032.VmxOpen(byref(c_void_p(FileSpec)),
byref(LocatorSize), byref(Omode), byref(hwmcb),
byref(c_void_p(Password)))
printf ("After - X = %#x (%d), LocatorSize = %d, Omode = %d, hwmcb = %d
\n", X, X, LocatorSize, Omode, hwmcb)
windll.oleaut32.SysFreeString(FileSpec)
windll.oleaut32.SysFreeString(Password)


This returned a correct value in X of 0 (VIS_OK).

When I changed:
FileSpec = windll.oleaut32.SysAllocStringByteLen("s:\\msdb\\dcod\x00",
13)
to:
FileSpec = windll.oleaut32.SysAllocStringByteLen("u:\\msdb\\dcod\x00",
13)
I got the expected X value of 13 (VIS_DOS_ERROR).

As was pointed out to me, and not really to my surprise as I am still
very "green" at Python, the correct call was:
X.value = windll.vbis5032.VmxOpen(byref(c_void_p(FileSpec)),
byref(LocatorSize), byref(Omode), byref(hwmcb),
byref(c_void_p(Password)))
instead of:
X = windll.vbis5032.VmxOpen(byref(c_void_p(FileSpec)),
byref(LocatorSize), byref(Omode), byref(hwmcb),
byref(c_void_p(Password)))


Thank you to everyone for their help.
 
D

Dennis Lee Bieber

I received a direct email from someone, and I came up with the
following after implementing his advice:

Just for completeness... I'm the "someone"... And this is a copy of
the message in question:
Please forgive my going direct -- I do not have Usenet posting privileges from work.

From your message:

-=-=-=-=-=-=-
And, this is what printed:
After - X = 4325376, CacheSize = 0, OpenMode = 3, vHandle = 17586736

The printf shows that the values of X and vHandle have changed, but I
am not sure it is working properly as X should be a MUCH smaller
number (< 256). I would have expected a 0 (VIS_OK), 13 (VIS_DOS_ERROR)
or 14(VIS_DISK_ERROR) if it had worked.

Next, I changed:
fName = windll.oleaut32.SysAllocStringByteLen("u:\\msdb\\dcod\x00",
13)
so that I knew it would not find the file, and got:
After - X = 2555917, CacheSize = 0, OpenMode = 3, vHandle = 0

The vHandle staying 0 tells me that it did not find the file, but the
value of X seems random.
-=-=-=-=-=-=-=-

X does NOT seem random to me -- but I took the effort to render it in hexadecimal. X seems to be coming back as a 32-bit integer. Your <256 code appears to be in the second half of that integer -- observe:

Also observe that x0D => 13 decimal… your stated "VIS_DOS_ERROR" code.

I have no explanation for the leading 16-bits… But under the rules of Python, "X = …" is commonly a rebinding operation, so presetting X as a ctypes short integer may not be having any effect (I suspect those are more important for calls where the input parameters need to be mutable and of a known C format).
['__class__', '__ctype_be__', '__ctype_le__', '__ctypes_from_outparam__', '__delattr__', '__dict__', '__doc__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__nonzero__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__str__', '__weakref__', '_as_parameter_', '_b_base_', '_b_needsfree_', '_objects', '_type_', 'value']

Note that in this I have assigned to the "value" component of a c_short object. Plugging in your results values:

Gives… ta da… 0 and 13 respectively. Perhaps you should, after creating X as a c short object, be assigning to the value, not to the object…

X.value = windll. …

{Replies are being directed to my home email}
--
Wulfraed Dennis Lee Bieber KD6MOG
(e-mail address removed) (e-mail address removed)
HTTP://wlfraed.home.netcom.com/
(Bestiaria Support Staff: (e-mail address removed))
HTTP://www.bestiaria.com/
 
C

Craig

I received a direct email from someone, and I came up with the
following after implementing his advice:

Just for completeness... I'm the "someone"... And this is a copy of
the message in question:




Please forgive my going direct -- I do not have Usenet posting privileges from work.
From your message:
-=-=-=-=-=-=-
And, this is what printed:
After - X = 4325376, CacheSize = 0, OpenMode = 3, vHandle = 17586736
The printf shows that the values of X and vHandle have changed, but I
am not sure it is working properly as X should be a MUCH smaller
number (< 256). I would have expected a 0 (VIS_OK), 13 (VIS_DOS_ERROR)
or 14(VIS_DISK_ERROR) if it had worked.
Next, I changed:
fName = windll.oleaut32.SysAllocStringByteLen("u:\\msdb\\dcod\x00",
13)
so that I knew it would not find the file, and got:
After - X = 2555917, CacheSize = 0, OpenMode = 3, vHandle = 0
The vHandle staying 0 tells me that it did not find the file, but the
value of X seems random.
-=-=-=-=-=-=-=-
X does NOT seem random to me -- but I took the effort to render it in hexadecimal. X seems to be coming back as a 32-bit integer. Your <256 code appears to be in the second half of that integer -- observe:
"%8.8X" % 4325376 '00420000'
"%8.8X" % 2555917 '0027000D'

Also observe that x0D => 13 decimal... your stated "VIS_DOS_ERROR" code.

I have no explanation for the leading 16-bits... But under the rules of Python, "X = ..." is commonly a rebinding operation, so presetting X as a ctypes short integer may not be having any effect (I suspect those are more important for calls where the input parameters need to be mutable and of a known C format).
import ctypes
x = ctypes.c_short(123)
dir(x)
['__class__', '__ctype_be__', '__ctype_le__', '__ctypes_from_outparam__', '__delattr__', '__dict__', '__doc__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__nonzero__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__str__', '__weakref__', '_as_parameter_', '_b_base_', '_b_needsfree_', '_objects', '_type_', 'value']
x.value = 13
x c_short(13)

Note that in this I have assigned to the "value" component of a c_short object. Plugging in your results values:
x.value = 4325376
x c_short(0)
x.value = 2555917
x c_short(13)

Gives... ta da... 0 and 13 respectively. Perhaps you should, after creating X as a c short object, be assigning to the value, not to the object...
X.value = windll. ...
{Replies are being directed to my home email}

--
Wulfraed Dennis Lee Bieber KD6MOG
(e-mail address removed) (e-mail address removed)
HTTP://wlfraed.home.netcom.com/
(Bestiaria Support Staff: (e-mail address removed))
HTTP://www.bestiaria.com/


Sorry, I wasn't trying to exclude any credit from Dennis, I just
wasn't sure if he wanted to be listed.


As it turns out, I am running into more of these BSTR and pointer
problems.

I have moved on to the next function to convert:
short FAR PASCAL VmxInfo(LPHANDLE lpDatasetNumber, LPVSTATS lpvstats);
which uses a structure:
typedef struct tagVSTATS
{
LONG nrecords;
WORD gps_used;
WORD gps_unused;
WORD max_key_len;
LONG grp_size;
WORD init_alloc;
WORD cr_opts;
WORD free_prop_area;
BSTR format;
BSTR reserved;
} VSTATS;
typedef VSTATS FAR * LPVSTATS;

This is what I have tried:
from ctypes import *
from ctypes.util import *

libc = cdll.msvcrt
printf = libc.printf

LPBSTR = POINTER(c_void_p)
HANDLE = POINTER(POINTER(c_long))
LPHANDLE = POINTER(HANDLE)
LPSHORT = POINTER(c_short)
LPVSTATS = POINTER(c_void_p)

class VSTATS(Structure):
_fields_ = [
("nrecords", c_long),
("gps_used", c_short),
("gps_unused", c_short),
("max_key_len", c_short),
("grp_size", c_long),
("init_alloc", c_short),
("cr_opts", c_short),
("free_prop_area", c_short),
("format", c_void_p),
("reserved", c_void_p)
]
vStats = VSTATS(1, 2, 3, 4, 5, 6, 7, 8)

VmxInfo = windll.vbis5032.VmxInfo
VmxInfo.restype = c_short
VmxInfo.argtypes = [LPHANDLE, LPVSTATS]

print "\n(2) VB/ISAM testing:"
hwmcb = HANDLE()

print "\nVmxInfo test:"
res = c_short(255)
printf ("Before - res = %#x (%d), hwmcb = %d\n", res, res, hwmcb)
res = VmxInfo( byref(hwmcb), byref(c_void_p(vStats)) )
printf ("After - res = %#x (%d), hwmcb = %d\n", res, res, hwmcb)
printf ("nrecords = %d, gps_used = %d, gps_unused = %d, max_key_len =
%d\n", vStats.nrecords, vStats.gps_used, vStats.gps_unused,
vStats.max_key_len)
printf ("grp_size = %d, init_alloc = %d, cr_opts = %d, free_prop_area
= %d\n", vStats.grp_size, vStats.init_alloc, vStats.cr_opts,
vStats.free_prop_area)
printf ("format = \x22%s\x22\n", vStats.format)
printf ("reserved = \x22%s\x22\n", vStats.reserved)

This is what I get:
Traceback (most recent call last):
File "C:\temp\vbisam_test_2.py", line 99, in <module>
res = VmxInfo( byref(hwmcb), byref(c_void_p(vStats)) )
TypeError: cannot be converted to pointer


And, on the last variation of this, this function needs to receive
BSTR values passed back from the dll:
short FAR PASCAL VmxGet(LPHANDLE lpDatasetNumber, LPSHORT lpSecIndex,
LPSHORT lpOption, BSTR *SrchKey,
BSTR *SecKey, BSTR *PriKey, LPSTR
lpTypeDef);
SrchKey is provided by me, SecKey and PriKey are returned by the dll,
and TypeDef is defined by me and filled by the dll.

For this, I have added:
VmxGet = windll.vbis5032.VmxGet
VmxGet.restype = c_short
VmxGet.argtypes = [LPHANDLE, LPSHORT, LPSHORT, LPBSTR, LPBSTR, LPBSTR,
LPBSTR]
hwmcb = HANDLE()
SecIndex = c_short(0)
Option = c_short(17)
SrchKey =
windll.oleaut32.SysAllocStringByteLen("MSD19PH
\x00", 41)
SecKey =
windll.oleaut32.SysAllocStringByteLen("1234567890123456789012345678901234567890\x00",
41)
PriKey =
windll.oleaut32.SysAllocStringByteLen("1234567890123456789012345678901234567890\x00",
41)
TypeDef =
windll.oleaut32.SysAllocStringByteLen(c_char_p("X".center(129,
"\x00")), 129)
print "\nVmxGet test:"
res = c_short(255)
printf ("Before - res = %#x (%d), SecIndex = %d, Option = %d, hwmcb =
%d\n", res, res, SecIndex, Option, hwmcb)
printf ("SrchKey = \x22%s\x22\nSeckey = \x22%s\x22\nPriKey = \x22%s
\x22\nTypeDef = \x22%s\x22\n", SrchKey, SecKey, PriKey, TypeDef)
print "Ready to call (Library = " + find_library("vbis5032") + ") ..."
res = VmxGet( byref(hwmcb), byref(SecIndex), byref(Option),
byref(c_void_p(SrchKey)), byref(c_void_p(SecKey)),
byref(c_void_p(PriKey)), byref(c_void_p(TypeDef)) )
printf ("After - res = %#x (%d), SecIndex = %d, Option = %d, hwmcb = %d
\n", res, res, SecIndex, Option, hwmcb)
printf ("SrchKey = \x22%s\x22\nSeckey = \x22%s\x22\nPriKey = \x22%s
\x22\nTypeDef = \x22%s\x22\n", SrchKey, SecKey, PriKey, TypeDef)

The last message I get is:
Ready to call (Library = C:\Windows\vbis5032.dll) ...
and then I get a Windows popup box:
python.exe has stopped working
so, I know I am passing something very wrong.


Once these two problems are resolved, I am sure I can process the rest
of the C prototypes myself, as they are simply variations on these
themes.


As before, any help is appreciated.
 
D

Dennis Lee Bieber

Sorry, I wasn't trying to exclude any credit from Dennis, I just
wasn't sure if he wanted to be listed.
As responded to elsewhere -- it was more a case of minimizing the
"non-work" traffic on my work email address...

said:
This is what I have tried:
said:
LPBSTR = POINTER(c_void_p)

said:
And, on the last variation of this, this function needs to receive
BSTR values passed back from the dll:
short FAR PASCAL VmxGet(LPHANDLE lpDatasetNumber, LPSHORT lpSecIndex,
LPSHORT lpOption, BSTR *SrchKey,
BSTR *SecKey, BSTR *PriKey, LPSTR
lpTypeDef);
SrchKey is provided by me, SecKey and PriKey are returned by the dll,
and TypeDef is defined by me and filled by the dll.

For this, I have added:
VmxGet = windll.vbis5032.VmxGet
VmxGet.restype = c_short
VmxGet.argtypes = [LPHANDLE, LPSHORT, LPSHORT, LPBSTR, LPBSTR, LPBSTR,
LPBSTR]

Remember that Python strings are immutable... Also note that the
signature you give above seems to differentiate between passing a
pointer variable LPSTR (ie; lpTypeDef is just 4-bytes which will/should
contain the address pointing to the real string) and passing the address
of the first byte of a string buffer BSTR (*PriKey is the same as
&PriKey[0] -- but PriKey itself has to be a sequence of characters or an
empty buffer allocated to hold them).


From the ctypes documentation:
http://docs.python.org/lib/node453.html

"""
Assigning a new value to instances of the pointer types c_char_p,
c_wchar_p, and c_void_p changes the memory location they point to, not
the contents of the memory block (of course not, because Python strings
are immutable):

You should be careful, however, not to pass them to functions expecting
pointers to mutable memory. If you need mutable memory blocks, ctypes
has a create_string_buffer function which creates these in various ways.
The current memory block contents can be accessed (or changed) with the
raw property; if you want to access it as NUL terminated string, use the
value property:
"""

Drawback -- it looks like you may have to preset the size of the
buffer...

--
Wulfraed Dennis Lee Bieber KD6MOG
(e-mail address removed) (e-mail address removed)
HTTP://wlfraed.home.netcom.com/
(Bestiaria Support Staff: (e-mail address removed))
HTTP://www.bestiaria.com/
 
C

Craig

Sorry, I wasn't trying to exclude any credit from Dennis, I just
wasn't sure if he wanted to be listed.

As responded to elsewhere -- it was more a case of minimizing the
"non-work" traffic on my work email address...

This is what I have tried:

LPBSTR = POINTER(c_void_p)

And, on the last variation of this, this function needs to receive
BSTR values passed back from the dll:
short FAR PASCAL VmxGet(LPHANDLE lpDatasetNumber, LPSHORT lpSecIndex,
LPSHORT lpOption, BSTR *SrchKey,
BSTR *SecKey, BSTR *PriKey, LPSTR
lpTypeDef);
SrchKey is provided by me, SecKey and PriKey are returned by the dll,
and TypeDef is defined by me and filled by the dll.
For this, I have added:
VmxGet = windll.vbis5032.VmxGet
VmxGet.restype = c_short
VmxGet.argtypes = [LPHANDLE, LPSHORT, LPSHORT, LPBSTR, LPBSTR, LPBSTR,
LPBSTR]

Remember that Python strings are immutable... Also note that the
signature you give above seems to differentiate between passing a
pointer variable LPSTR (ie; lpTypeDef is just 4-bytes which will/should
contain the address pointing to the real string) and passing the address
of the first byte of a string buffer BSTR (*PriKey is the same as
&PriKey[0] -- but PriKey itself has to be a sequence of characters or an
empty buffer allocated to hold them).

From the ctypes documentation:
http://docs.python.org/lib/node453.html

"""
Assigning a new value to instances of the pointer types c_char_p,
c_wchar_p, and c_void_p changes the memory location they point to, not
the contents of the memory block (of course not, because Python strings
are immutable):

c_char_p('Hello, World')>>> c_s.value = "Hi, there"
c_char_p('Hi, there')
Hello, World

You should be careful, however, not to pass them to functions expecting
pointers to mutable memory. If you need mutable memory blocks, ctypes
has a create_string_buffer function which creates these in various ways.
The current memory block contents can be accessed (or changed) with the
raw property; if you want to access it as NUL terminated string, use the
value property:

10 'Hello\x00\x00\x00\x00\x00'>>> p.value = "Hi"
10 'Hi\x00lo\x00\x00\x00\x00\x00'

"""

Drawback -- it looks like you may have to preset the size of the
buffer...

--
Wulfraed Dennis Lee Bieber KD6MOG
(e-mail address removed) (e-mail address removed)
HTTP://wlfraed.home.netcom.com/
(Bestiaria Support Staff: (e-mail address removed))
HTTP://www.bestiaria.com/


That is not really a drawback. Pretty much everything is fixed-length
anyway.

I guess I am having trouble with the whole immutable thing.

And, the pointers too.

Anyway, I have the following for "types":
LPBSTR = POINTER(c_void_p)
HANDLE = POINTER(POINTER(c_long))
LPHANDLE = POINTER(HANDLE)
LPSHORT = POINTER(c_short)
LPVSTATS = POINTER(c_void_p)
c_string = c_char_p
LPSTR = c_string

I guess passing a structure is out of the question. So, I tried a
string (something - just to get the data back):
#short FAR PASCAL VmxInfo(LPHANDLE lpDatasetNumber, LPVSTATS
lpvstats);
VmxInfo = windll.vbis5032.VmxInfo
VmxInfo.restype = c_short
VmxInfo.argtypes = [LPHANDLE, LPSTR]
VsamInfo = create_string_buffer("12345678901234567890123456789012")
printf ("VsamInfo = \x22%s\x22\n", VsamInfo.raw)
print "Ready to call (Library = " + find_library("vbis5032") + ") ..."
res = VmxInfo( byref(hwmcb), byref(VsamInfo) )
And I get:
Ready to call (Library = C:\Windows\vbis5032.dll) ...
Traceback (most recent call last):
File "C:\temp\vbisam_test_2.py", line 101, in <module>
res = VmxInfo( byref(hwmcb), byref(VsamInfo) )
ctypes.ArgumentError: argument 2: <type 'exceptions.TypeError'>: wrong
type


So, to get to the VmxGet, I comment out the VmxInfo call, and proceed
to:
SrchKey =
windll.oleaut32.SysAllocStringByteLen("MSD19PH
\x00", 41)
SecKey =
windll.oleaut32.SysAllocStringByteLen("1234567890123456789012345678901234567890\x00",
41)
PriKey =
windll.oleaut32.SysAllocStringByteLen("1234567890123456789012345678901234567890\x00",
41)
TypeDef = create_string_buffer("X".center(129, "X"))
print "Ready to call (Library = " + find_library("vbis5032") + ") ..."
res = VmxGet( byref(hwmcb), byref(SecIndex), byref(Option),
byref(c_void_p(SrchKey)), byref(c_void_p(SecKey)),
byref(c_void_p(PriKey)), byref(c_void_p(TypeDef)) )
And for this, I get:
Ready to call (Library = C:\Windows\vbis5032.dll) ...
Traceback (most recent call last):
File "C:\temp\vbisam_test_2.py", line 114, in <module>
res = VmxGet( byref(hwmcb), byref(SecIndex), byref(Option),
byref(c_void_p(S
rchKey)), byref(c_void_p(SecKey)), byref(c_void_p(PriKey)),
byref(TypeDef) )
ctypes.ArgumentError: argument 7: <type 'exceptions.TypeError'>: wrong
type


I know I am missing something, probably very basic (pardon the pun) to
Python.
 
D

Dennis Lee Bieber

Anyway, I have the following for "types":
LPBSTR = POINTER(c_void_p)
HANDLE = POINTER(POINTER(c_long))
LPHANDLE = POINTER(HANDLE)
LPSHORT = POINTER(c_short)
LPVSTATS = POINTER(c_void_p)
c_string = c_char_p
LPSTR = c_string

I guess passing a structure is out of the question. So, I tried a
string (something - just to get the data back):
#short FAR PASCAL VmxInfo(LPHANDLE lpDatasetNumber, LPVSTATS
lpvstats);
VmxInfo = windll.vbis5032.VmxInfo
VmxInfo.restype = c_short
VmxInfo.argtypes = [LPHANDLE, LPSTR]
VsamInfo = create_string_buffer("12345678901234567890123456789012")
printf ("VsamInfo = \x22%s\x22\n", VsamInfo.raw)
print "Ready to call (Library = " + find_library("vbis5032") + ") ..."
res = VmxInfo( byref(hwmcb), byref(VsamInfo) )
And I get:
Ready to call (Library = C:\Windows\vbis5032.dll) ...
Traceback (most recent call last):
File "C:\temp\vbisam_test_2.py", line 101, in <module>
res = VmxInfo( byref(hwmcb), byref(VsamInfo) )
ctypes.ArgumentError: argument 2: <type 'exceptions.TypeError'>: wrong
type
Did you try just passing the buffer, rather than nesting a "byref"

http://docs.python.org/lib/ctypes-passing-pointers.html

Note how the string buffer parameter has NO odd games on it. This
especially applies to the other call below, for PriKey and the other
BSTR *... term

As for passing a structure... Well, I'd suggest using struct.pack to
push arguments into such a structure (though it it is using pointers to
other items it may get tricky), using create_string_buffer to make the
Python string "structure" a C-memory block, passing that, and if needed,
struct.unpack the item after the call.
--
Wulfraed Dennis Lee Bieber KD6MOG
(e-mail address removed) (e-mail address removed)
HTTP://wlfraed.home.netcom.com/
(Bestiaria Support Staff: (e-mail address removed))
HTTP://www.bestiaria.com/
 
C

Craig

Anyway, I have the following for "types":
LPBSTR = POINTER(c_void_p)
HANDLE = POINTER(POINTER(c_long))
LPHANDLE = POINTER(HANDLE)
LPSHORT = POINTER(c_short)
LPVSTATS = POINTER(c_void_p)
c_string = c_char_p
LPSTR = c_string
I guess passing a structure is out of the question. So, I tried a
string (something - just to get the data back):
#short FAR PASCAL VmxInfo(LPHANDLE lpDatasetNumber, LPVSTATS
lpvstats);
VmxInfo = windll.vbis5032.VmxInfo
VmxInfo.restype = c_short
VmxInfo.argtypes = [LPHANDLE, LPSTR]
VsamInfo = create_string_buffer("12345678901234567890123456789012")
printf ("VsamInfo = \x22%s\x22\n", VsamInfo.raw)
print "Ready to call (Library = " + find_library("vbis5032") + ") ..."
res = VmxInfo( byref(hwmcb), byref(VsamInfo) )
And I get:
Ready to call (Library = C:\Windows\vbis5032.dll) ...
Traceback (most recent call last):
File "C:\temp\vbisam_test_2.py", line 101, in <module>
res = VmxInfo( byref(hwmcb), byref(VsamInfo) )
ctypes.ArgumentError: argument 2: <type 'exceptions.TypeError'>: wrong
type

Did you try just passing the buffer, rather than nesting a "byref"

http://docs.python.org/lib/ctypes-passing-pointers.html

Note how the string buffer parameter has NO odd games on it. This
especially applies to the other call below, for PriKey and the other
BSTR *... term

As for passing a structure... Well, I'd suggest using struct.pack to
push arguments into such a structure (though it it is using pointers to
other items it may get tricky), using create_string_buffer to make the
Python string "structure" a C-memory block, passing that, and if needed,
struct.unpack the item after the call.
--
Wulfraed Dennis Lee Bieber KD6MOG
(e-mail address removed) (e-mail address removed)
HTTP://wlfraed.home.netcom.com/
(Bestiaria Support Staff: (e-mail address removed))
HTTP://www.bestiaria.com/


Based on your input, I changed:
LPSTR = c_char_p
VmxGet = windll.vbis5032.VmxGet
VmxGet.restype = c_short
VmxGet.argtypes = [LPHANDLE, LPSHORT, LPSHORT, LPBSTR, LPBSTR, LPBSTR,
LPSTR]
SrchKey =
windll.oleaut32.SysAllocStringByteLen("MSD19PH
\x00", 41)
SecKey =
windll.oleaut32.SysAllocStringByteLen("1234567890123456789012345678901234567890\x00",
41)
PriKey =
windll.oleaut32.SysAllocStringByteLen("1234567890123456789012345678901234567890\x00",
41)
TypeDef = create_string_buffer(128)
TypeDef.raw = "X".center(128, "X")
print "Ready to call (Library = " + find_library("vbis5032") + ") ..."
res = VmxGet( byref(hwmcb), byref(SecIndex), byref(Option),
byref(c_void_p(SrchKey)), byref(c_void_p(SecKey)),
byref(c_void_p(PriKey)), TypeDef )
printf ("After - res = %#x (%d), SecIndex = %d, Option = %d, hwmcb = %d
\n", res, res, SecIndex, Option, hwmcb)
printf ("SrchKey = \x22%s\x22\nSeckey = \x22%s\x22\nPriKey = \x22%s
\x22\nTypeDef = \x22%s\x22\n", SrchKey, SecKey, PriKey, TypeDef.raw)


I got back exactly what I expected for TypeDef, but SecKey and PriKey
were what I initialized them to , not what should have been returned.

Do you mean that I should change any string that is expected to be
changed by the dll to a create_string_buffer type (LPSTR) instead of a
windll.oleaut32.SysAllocStringByteLen type (LPBSTR) and then simply
list it in the call instead of byref, as I did with TypeDef?

Can it really be that simple?

As for the structure:
class VSTATS(Structure):
_fields_ = [
("nrecords", c_long),
("gps_used", c_short),
("gps_unused", c_short),
("max_key_len", c_short),
("grp_size", c_long),
("init_alloc", c_short),
("cr_opts", c_short),
("free_prop_area", c_short),
("format", c_void_p),
("reserved", c_void_p)
]
vStats = VSTATS(1, 2, 3, 4, 5, 6, 7, 8)
can I just use a create_string_buffer (LPSTR) and parse things up via
substrings after the call returns?
 
D

Dennis Lee Bieber

I got back exactly what I expected for TypeDef, but SecKey and PriKey
were what I initialized them to , not what should have been returned.

Do you mean that I should change any string that is expected to be
changed by the dll to a create_string_buffer type (LPSTR) instead of a
windll.oleaut32.SysAllocStringByteLen type (LPBSTR) and then simply
list it in the call instead of byref, as I did with TypeDef?
Based upon my interpretation of the C prototype and the ctypes
documentation (sample code), that IS what I expect... (I didn't expect
it to work with the TypeDef field though...)

There are two ways in which a C language function call can handle
returning string data. (Let's see if I remember how to code C; been 6
years)

char pre_alloc_buffer[] = "This is a fixed nul-terminated buffer space";
char *dynamic_buffer; /* this is an uninitialized pointer to char */
char *dynamic_too;

The function can either modify a buffer passed in:

int func1(char *bfr);

or it can allocate a buffer internally and return that either as a
function result, or via a reference to a pointer variable

char *func2(char *bfr[]); /* ugh, my memory is fading */
/* If I did this right, it is a pointer to array of character */


res = func1(pre_alloc_buffer);
or
res = func1(&pre_alloc_buffer[0]); /* long/explicit form */

The address of the start of the buffer space is passed, and func1
performs its magic in the pre-allocated buffer.

dynamic_too = func2(&dynamic_buffer);

Here, we pass the address of just a pointer variable (which
currently points to garbage). func2 is expected to perform something
wherein IT allocates memory and that memory address is what is put into
the argument/return

...
*bfr = malloc(...);
return (bfr);

Based on my interpretation of the prototypes, I'd have expected the
two BSTR to be the func1 style passing of a buffer, but the last
argument /might/ have been the second func2 style.

I really can't be more definitive; I've not used ctypes -- only read
the documentation; the only DLLs I've had to use were covered by the
win32 extension library which comes as part of the ActiveState Python
installation.
Can it really be that simple?

As for the structure:
class VSTATS(Structure):
_fields_ = [
("nrecords", c_long),
("gps_used", c_short),
("gps_unused", c_short),
("max_key_len", c_short),
("grp_size", c_long),
("init_alloc", c_short),
("cr_opts", c_short),
("free_prop_area", c_short),
("format", c_void_p),
("reserved", c_void_p)
]
vStats = VSTATS(1, 2, 3, 4, 5, 6, 7, 8)
can I just use a create_string_buffer (LPSTR) and parse things up via
substrings after the call returns?

It may be possible, though the library seems to have gone out of its
way to mask such efforts from the user.
--
Wulfraed Dennis Lee Bieber KD6MOG
(e-mail address removed) (e-mail address removed)
HTTP://wlfraed.home.netcom.com/
(Bestiaria Support Staff: (e-mail address removed))
HTTP://www.bestiaria.com/
 
C

Craig

I got back exactly what I expected for TypeDef, but SecKey and PriKey
were what I initialized them to , not what should have been returned.
Do you mean that I should change any string that is expected to be
changed by the dll to a create_string_buffer type (LPSTR) instead of a
windll.oleaut32.SysAllocStringByteLen type (LPBSTR) and then simply
list it in the call instead of byref, as I did with TypeDef?

Based upon my interpretation of the C prototype and the ctypes
documentation (sample code), that IS what I expect... (I didn't expect
it to work with the TypeDef field though...)

There are two ways in which a C language function call can handle
returning string data. (Let's see if I remember how to code C; been 6
years)

char pre_alloc_buffer[] = "This is a fixed nul-terminated buffer space";
char *dynamic_buffer; /* this is an uninitialized pointer to char */
char *dynamic_too;

The function can either modify a buffer passed in:

int func1(char *bfr);

or it can allocate a buffer internally and return that either as a
function result, or via a reference to a pointer variable

char *func2(char *bfr[]); /* ugh, my memory is fading */
/* If I did this right, it is a pointer to array of character */

res = func1(pre_alloc_buffer);
or
res = func1(&pre_alloc_buffer[0]); /* long/explicit form */

The address of the start of the buffer space is passed, and func1
performs its magic in the pre-allocated buffer.

dynamic_too = func2(&dynamic_buffer);

Here, we pass the address of just a pointer variable (which
currently points to garbage). func2 is expected to perform something
wherein IT allocates memory and that memory address is what is put into
the argument/return

...
*bfr = malloc(...);
return (bfr);

Based on my interpretation of the prototypes, I'd have expected the
two BSTR to be the func1 style passing of a buffer, but the last
argument /might/ have been the second func2 style.

I really can't be more definitive; I've not used ctypes -- only read
the documentation; the only DLLs I've had to use were covered by the
win32 extension library which comes as part of the ActiveState Python
installation.


Can it really be that simple?
As for the structure:
class VSTATS(Structure):
_fields_ = [
("nrecords", c_long),
("gps_used", c_short),
("gps_unused", c_short),
("max_key_len", c_short),
("grp_size", c_long),
("init_alloc", c_short),
("cr_opts", c_short),
("free_prop_area", c_short),
("format", c_void_p),
("reserved", c_void_p)
]
vStats = VSTATS(1, 2, 3, 4, 5, 6, 7, 8)
can I just use a create_string_buffer (LPSTR) and parse things up via
substrings after the call returns?

It may be possible, though the library seems to have gone out of its
way to mask such efforts from the user.
--
Wulfraed Dennis Lee Bieber KD6MOG
(e-mail address removed) (e-mail address removed)
HTTP://wlfraed.home.netcom.com/
(Bestiaria Support Staff: (e-mail address removed))
HTTP://www.bestiaria.com/


This dll was designed to be used from either C or Visual Basic 6.

I have the declare statements for VB6, if that helps.

Based on the results I have so far (and I have tried MANY permutations
like trying to use the LPSTR for SecKey and PriKey which are returned
as is TypeDef), it looks like SecKey and PriKey are being used as data
instead of pointers.

For this, I try:
LPSTR = c_char_p
VmxGet.argtypes = [LPHANDLE, LPSHORT, LPSHORT, LPBSTR, LPSTR, LPBSTR,
LPSTR]
SrchKey =
windll.oleaut32.SysAllocStringByteLen("MSD19DN
\x00", 41)
SecKey = create_string_buffer(41)
SecKey.raw = "1234567890123456789012345678901234567890"
PriKey =
windll.oleaut32.SysAllocStringByteLen("ABCDEFGHIJKLMNOPQRSTUVWXYZ12345678901234\x00",
41)
TypeDef = create_string_buffer(128)
TypeDef.raw = "X".center(128, "X")
res = VmxGet( byref(hwmcb), byref(SecIndex), byref(Option),
byref(c_void_p(SrchKey)), SecKey, byref(c_void_p(PriKey)), TypeDef )
and I get:
Traceback (most recent call last):
File "C:\temp\vbisam_test_2.py", line 158, in <module>
res = VmxGet( byref(hwmcb), byref(SecIndex), byref(Option),
byref(c_void_p(S
rchKey)), SecKey, byref(c_void_p(PriKey)), TypeDef )
WindowsError: exception: access violation reading 0x3433322D

I notice that SecKey.raw starts with "1234" and the exception address
is 0x3433322D, which is "432-".

And, changing to:
VmxGet.argtypes = [LPHANDLE, LPSHORT, LPSHORT, LPBSTR, LPBSTR, LPSTR,
LPSTR]
PriKey = create_string_buffer(41)
PriKey.raw = "ABCDEFGHIJKLMNOPQRSTUVWXYZ12345678901234"
res = VmxGet( byref(hwmcb), byref(SecIndex), byref(Option),
byref(c_void_p(SrchKey)), byref(c_void_p(SecKey)), PriKey, TypeDef )
I then get:
Traceback (most recent call last):
File "C:\temp\vbisam_test_2.py", line 159, in <module>
res = VmxGet( byref(hwmcb), byref(SecIndex), byref(Option),
byref(c_void_p(S
rchKey)), byref(c_void_p(SecKey)), PriKey, TypeDef )
WindowsError: exception: access violation reading 0x4443423D

I notice that PriKey.raw starts with "ABCD" and the exception address
is 0x4443423D, which is "DBC=".


So, I can see that there is data where pointers are expected.

I just don't see how to pass those pointers. When they were passed as
LPBSTR (and oleaut32.SysAllocString), the calls "worked" in that they
didn't get exceptions and they did return the correct record, but the
SecKey and PriKey fields were not correct, and they were BSTR
(pointers).

I did get the VmxInfo to work by:
VSTATS_fmt = "lhhhlhhhlll"
VsamInfo = create_string_buffer(36)
temp = unpack(VSTATS_fmt, VsamInfo.raw)
vStats = VSTATS(temp[0], temp[1], temp[2], temp[3], temp[4], temp[5],
temp[6], temp[7])


Any idea on how to jump the last hurdle on VmxGet?

This is the very last thing that is stopping my project.
 
D

Dennis Lee Bieber

This dll was designed to be used from either C or Visual Basic 6.

I have the declare statements for VB6, if that helps.
Probably not that much -- I'd bet it's full of variant records said:
Based on the results I have so far (and I have tried MANY permutations
like trying to use the LPSTR for SecKey and PriKey which are returned
as is TypeDef), it looks like SecKey and PriKey are being used as data
instead of pointers.

For this, I try:
LPSTR = c_char_p
VmxGet.argtypes = [LPHANDLE, LPSHORT, LPSHORT, LPBSTR, LPSTR, LPBSTR,
LPSTR]
SrchKey =
windll.oleaut32.SysAllocStringByteLen("MSD19DN
\x00", 41)
SecKey = create_string_buffer(41)
SecKey.raw = "1234567890123456789012345678901234567890"
PriKey =
windll.oleaut32.SysAllocStringByteLen("ABCDEFGHIJKLMNOPQRSTUVWXYZ12345678901234\x00",
41)
TypeDef = create_string_buffer(128)
TypeDef.raw = "X".center(128, "X")
res = VmxGet( byref(hwmcb), byref(SecIndex), byref(Option),
byref(c_void_p(SrchKey)), SecKey, byref(c_void_p(PriKey)), TypeDef )
and I get:
Traceback (most recent call last):
File "C:\temp\vbisam_test_2.py", line 158, in <module>
res = VmxGet( byref(hwmcb), byref(SecIndex), byref(Option),
byref(c_void_p(S
rchKey)), SecKey, byref(c_void_p(PriKey)), TypeDef )
WindowsError: exception: access violation reading 0x3433322D

I notice that SecKey.raw starts with "1234" and the exception address
is 0x3433322D, which is "432-".
0x2D is 4 less than the expected 0x31...
And, changing to:
VmxGet.argtypes = [LPHANDLE, LPSHORT, LPSHORT, LPBSTR, LPBSTR, LPSTR,
LPSTR]
PriKey = create_string_buffer(41)
PriKey.raw = "ABCDEFGHIJKLMNOPQRSTUVWXYZ12345678901234"
res = VmxGet( byref(hwmcb), byref(SecIndex), byref(Option),
byref(c_void_p(SrchKey)), byref(c_void_p(SecKey)), PriKey, TypeDef )
I then get:
Traceback (most recent call last):
File "C:\temp\vbisam_test_2.py", line 159, in <module>
res = VmxGet( byref(hwmcb), byref(SecIndex), byref(Option),
byref(c_void_p(S
rchKey)), byref(c_void_p(SecKey)), PriKey, TypeDef )
WindowsError: exception: access violation reading 0x4443423D

I notice that PriKey.raw starts with "ABCD" and the exception address
is 0x4443423D, which is "DBC=".
... and 0x3D is 4 less than the expected 0x41

Which leads me to suspect that BSTR are a structure, not a plain
string, in which the address given is supposed to be prefaced with a
length value. IOWs, something like:

|----|--------------------------------------|
^ ^ C-string data
| |Address to be passed
|(address - 4) to get to a length count field

Confirmed: http://msdn2.microsoft.com/en-us/library/ms221069(VS.85).aspx

(the URL is from HTTP through to the end, including .aspx)

Creating such may not be difficult -- but passing it to the DLL
could be. I don't know if ctypes allows pointer arithmetic.

ctypes OLESTR is a c_wchar_p, but that is still a c-style string with no
length prefix.

The only mention of BSTR in the win32 extensions is in text that
implies that the win32 extension library takes care of converting
from/to BSTR and Python strings transparently -- but that won't work for
your third-party library I suspect.

Might have to beg the author(s) of ctypes to add a BSTR type to the
list of those supported... as I can find no means of tweaking the
address computed by byref() to point somewhere into a structure rather
than the beginning of the structure.


Okay, the return value from SysAlloc... IS the integer representing
the address of a BSTR structure (IE, the address four bytes in from the
real memory start)... Note how backing up 4 bytes reveals the BSTR
length field

What happens if you just pass that item as-is, no byref(), no
conversion to ctypes pointer types...


PriKey = ctypes.windll.oleaut32.SysAllocStringByteLen("1234567890", 41)
SecKey = ctypes.windll.oleaut32.SysAllocStringByteLen("1234567890", 41)

res = VmxGet( byref(hwmcb),
byref(SecIndex),
byref(Option),
byref(c_void_p(SrchKey)),
SecKey,
PriKey,
TypeDef )


You'll need to use the .string_at() or .wstring_at() functions to
extract any data changed by the call. Should not be a Python
immutability problem as all "you" allocated in Python is the integer
holding the address. You may need to call a Sys... function to free the
memory that had been allocated -- Python doesn't know about it.
--
Wulfraed Dennis Lee Bieber KD6MOG
(e-mail address removed) (e-mail address removed)
HTTP://wlfraed.home.netcom.com/
(Bestiaria Support Staff: (e-mail address removed))
HTTP://www.bestiaria.com/
 
C

Craig

This dll was designed to be used from either C or Visual Basic 6.
I have the declare statements for VB6, if that helps.

Probably not that much -- I'd bet it's full of variant records <G>


Based on the results I have so far (and I have tried MANY permutations
like trying to use the LPSTR for SecKey and PriKey which are returned
as is TypeDef), it looks like SecKey and PriKey are being used as data
instead of pointers.
For this, I try:
LPSTR = c_char_p
VmxGet.argtypes = [LPHANDLE, LPSHORT, LPSHORT, LPBSTR, LPSTR, LPBSTR,
LPSTR]
SrchKey =
windll.oleaut32.SysAllocStringByteLen("MSD19DN
\x00", 41)
SecKey = create_string_buffer(41)
SecKey.raw = "1234567890123456789012345678901234567890"
PriKey =
windll.oleaut32.SysAllocStringByteLen("ABCDEFGHIJKLMNOPQRSTUVWXYZ12345678901234\x00",
41)
TypeDef = create_string_buffer(128)
TypeDef.raw = "X".center(128, "X")
res = VmxGet( byref(hwmcb), byref(SecIndex), byref(Option),
byref(c_void_p(SrchKey)), SecKey, byref(c_void_p(PriKey)), TypeDef )
and I get:
Traceback (most recent call last):
File "C:\temp\vbisam_test_2.py", line 158, in <module>
res = VmxGet( byref(hwmcb), byref(SecIndex), byref(Option),
byref(c_void_p(S
rchKey)), SecKey, byref(c_void_p(PriKey)), TypeDef )
WindowsError: exception: access violation reading 0x3433322D
I notice that SecKey.raw starts with "1234" and the exception address
is 0x3433322D, which is "432-".

0x2D is 4 less than the expected 0x31...


And, changing to:
VmxGet.argtypes = [LPHANDLE, LPSHORT, LPSHORT, LPBSTR, LPBSTR, LPSTR,
LPSTR]
PriKey = create_string_buffer(41)
PriKey.raw = "ABCDEFGHIJKLMNOPQRSTUVWXYZ12345678901234"
res = VmxGet( byref(hwmcb), byref(SecIndex), byref(Option),
byref(c_void_p(SrchKey)), byref(c_void_p(SecKey)), PriKey, TypeDef )
I then get:
Traceback (most recent call last):
File "C:\temp\vbisam_test_2.py", line 159, in <module>
res = VmxGet( byref(hwmcb), byref(SecIndex), byref(Option),
byref(c_void_p(S
rchKey)), byref(c_void_p(SecKey)), PriKey, TypeDef )
WindowsError: exception: access violation reading 0x4443423D
I notice that PriKey.raw starts with "ABCD" and the exception address
is 0x4443423D, which is "DBC=".

... and 0x3D is 4 less than the expected 0x41

Which leads me to suspect that BSTR are a structure, not a plain
string, in which the address given is supposed to be prefaced with a
length value. IOWs, something like:

|----|--------------------------------------|
^ ^ C-string data
| |Address to be passed
|(address - 4) to get to a length count field

Confirmed:http://msdn2.microsoft.com/en-us/library/ms221069(VS.85).aspx

(the URL is from HTTP through to the end, including .aspx)

Creating such may not be difficult -- but passing it to the DLL
could be. I don't know if ctypes allows pointer arithmetic.

ctypes OLESTR is a c_wchar_p, but that is still a c-style string with no
length prefix.

The only mention of BSTR in the win32 extensions is in text that
implies that the win32 extension library takes care of converting
from/to BSTR and Python strings transparently -- but that won't work for
your third-party library I suspect.

Might have to beg the author(s) of ctypes to add a BSTR type to the
list of those supported... as I can find no means of tweaking the
address computed by byref() to point somewhere into a structure rather
than the beginning of the structure.

'\x0c\x00\x00\x001234567890\x00\x01\x00\x00\x00\x00'



Okay, the return value from SysAlloc... IS the integer representing
the address of a BSTR structure (IE, the address four bytes in from the
real memory start)... Note how backing up 4 bytes reveals the BSTR
length field

What happens if you just pass that item as-is, no byref(), no
conversion to ctypes pointer types...

PriKey = ctypes.windll.oleaut32.SysAllocStringByteLen("1234567890", 41)
SecKey = ctypes.windll.oleaut32.SysAllocStringByteLen("1234567890", 41)

res = VmxGet( byref(hwmcb),
byref(SecIndex),
byref(Option),
byref(c_void_p(SrchKey)),
SecKey,
PriKey,
TypeDef )

"1234567890\x00\x01:\x00\x00\x00\xb6\x01\x00\x000\x99#\x01\xe0\x94\x1a\x1e\x0b\x00\x00\x00\xff\xff\xff\xff\x00\x00\x00\x00'">>> ctypes.string_at(PriKey-4, 41+4)

")\x00\x00\x001234567890\x00\x01:\x00\x00\x00\xb6\x01\x00\x000\x99#\x01\xe0\x94\x1a\x1e\x0b\x00\x00\x00\xff\xff\xff\xff\x00\x00\x00\x00'"
41

You'll need to use the .string_at() or .wstring_at() functions to
extract any data changed by the call. Should not be a Python
immutability problem as all "you" allocated in Python is the integer
holding the address. You may need to call a Sys... function to free the
memory that had been allocated -- Python doesn't know about it.
--
Wulfraed Dennis Lee Bieber KD6MOG
(e-mail address removed) (e-mail address removed)
HTTP://wlfraed.home.netcom.com/
(Bestiaria Support Staff: (e-mail address removed))
HTTP://www.bestiaria.com/

The VB6 Declare is:
Declare Function VmxGet Lib "vbis5032.DLL" (DatasetHandle&,
SecIndexField%, Options%, SelectorKey$, RSecondaryKey$, RPrimaryKey$,
RRecordVariable As Any) As Integer

The only variant is the RRecordVariable, which is actually meant to be
a Type structure.

Anyway, I thought maybe there was a clue in here that I am missing.


Back to Python. I ran with the following:
VmxGet.argtypes = [LPHANDLE, LPSHORT, LPSHORT, LPBSTR, LPSTR, LPSTR,
LPSTR]
SrchKey =
windll.oleaut32.SysAllocStringByteLen("MSD19DN
\x00", 41)
SecKey =
windll.oleaut32.SysAllocStringByteLen("1234567890123456789012345678901234567890\x00",
41)
PriKey =
windll.oleaut32.SysAllocStringByteLen("ABCDEFGHIJKLMNOPQRSTUVWXYZ12345678901234\x00",
41)
TypeDef = create_string_buffer(128)
TypeDef.raw = "X".center(128, "X")
res = VmxGet( byref(hwmcb), byref(SecIndex), byref(Option),
byref(c_void_p(SrchKey)), SecKey, PriKey, TypeDef )
And, now I get:
Traceback (most recent call last):
File "C:\temp\vbisam_test_2.py", line 156, in <module>
res = VmxGet( byref(hwmcb), byref(SecIndex), byref(Option),
byref(c_void_p(S
rchKey)), SecKey, PriKey, TypeDef )
ctypes.ArgumentError: argument 5: <type 'exceptions.TypeError'>: wrong
type

So, now the problem is with:
VmxGet.argtypes = [LPHANDLE, LPSHORT, LPSHORT, LPBSTR, LPSTR, LPSTR,
LPSTR]
I am not sure what to put for arguments 5 and 6 now.

I realized the need to release the BSTRs myself, and already included:
windll.oleaut32.SysFreeString(SrchKey)
windll.oleaut32.SysFreeString(SecKey)
windll.oleaut32.SysFreeString(PriKey)
at the end of the program.

I have no problem using .string_at() to extract the returned values -
once the .argtypes let the call execute.


In case this helps, here is an excerpt from a C program that was
provided with the dll:
typedef struct tagMYREC
{
char name[51]; // 1 xref generated name key

char lastname[31]; // 2 last name
char firstname[21]; // 3 first name
char address[41]; // 4 address
char city[31]; // 5 xref city key
char state[3]; // 6 xref state
char zip[11]; // 7 xref zip code
char phone[21]; // 8 xref phone number
BSTR notes; // 9 notes

} MYREC;
typedef MYREC FAR * LPMYREC;

short rc; // return code (from VB/ISAM function calls)
static HANDLE nDatasetNumber;
static short cur_index;
static short GetOpt;
static BSTR Dummy;
static BSTR Throwaway;
static BSTR PrimaryKey;
static MYREC myrec;

rc = VmxGet(&nDatasetNumber, // int DatasetNumber // VIS_BUSY ?
&cur_index, // int SelectedIndex
&GetOpt, // int OptionParameter
&Dummy, // Don't need a Selector param with
these options
&Throwaway, // Don't need returned index entry in
this case
&PrimaryKey, // LPBSTR lpPriKey
(void *) &myrec); // LPVOID lpRecordStructure

For someone who knows C and Python, this may provide a clue to the
BSTR passing/return problem.
 
C

Craig

Probably not that much -- I'd bet it's full of variant records <G>
Based on the results I have so far (and I have tried MANY permutations
like trying to use the LPSTR for SecKey and PriKey which are returned
as is TypeDef), it looks like SecKey and PriKey are being used as data
instead of pointers.
For this, I try:
LPSTR = c_char_p
VmxGet.argtypes = [LPHANDLE, LPSHORT, LPSHORT, LPBSTR, LPSTR, LPBSTR,
LPSTR]
SrchKey =
windll.oleaut32.SysAllocStringByteLen("MSD19DN
\x00", 41)
SecKey = create_string_buffer(41)
SecKey.raw = "1234567890123456789012345678901234567890"
PriKey =
windll.oleaut32.SysAllocStringByteLen("ABCDEFGHIJKLMNOPQRSTUVWXYZ12345678901234\x00",
41)
TypeDef = create_string_buffer(128)
TypeDef.raw = "X".center(128, "X")
res = VmxGet( byref(hwmcb), byref(SecIndex), byref(Option),
byref(c_void_p(SrchKey)), SecKey, byref(c_void_p(PriKey)), TypeDef )
and I get:
Traceback (most recent call last):
File "C:\temp\vbisam_test_2.py", line 158, in <module>
res = VmxGet( byref(hwmcb), byref(SecIndex), byref(Option),
byref(c_void_p(S
rchKey)), SecKey, byref(c_void_p(PriKey)), TypeDef )
WindowsError: exception: access violation reading 0x3433322D
I notice that SecKey.raw starts with "1234" and the exception address
is 0x3433322D, which is "432-".
0x2D is 4 less than the expected 0x31...
And, changing to:
VmxGet.argtypes = [LPHANDLE, LPSHORT, LPSHORT, LPBSTR, LPBSTR, LPSTR,
LPSTR]
PriKey = create_string_buffer(41)
PriKey.raw = "ABCDEFGHIJKLMNOPQRSTUVWXYZ12345678901234"
res = VmxGet( byref(hwmcb), byref(SecIndex), byref(Option),
byref(c_void_p(SrchKey)), byref(c_void_p(SecKey)), PriKey, TypeDef )
I then get:
Traceback (most recent call last):
File "C:\temp\vbisam_test_2.py", line 159, in <module>
res = VmxGet( byref(hwmcb), byref(SecIndex), byref(Option),
byref(c_void_p(S
rchKey)), byref(c_void_p(SecKey)), PriKey, TypeDef )
WindowsError: exception: access violation reading 0x4443423D
I notice that PriKey.raw starts with "ABCD" and the exception address
is 0x4443423D, which is "DBC=".
... and 0x3D is 4 less than the expected 0x41
Which leads me to suspect that BSTR are a structure, not a plain
string, in which the address given is supposed to be prefaced with a
length value. IOWs, something like:
|----|--------------------------------------|
^ ^ C-string data
| |Address to be passed
|(address - 4) to get to a length count field

(the URL is from HTTP through to the end, including .aspx)
Creating such may not be difficult -- but passing it to the DLL
could be. I don't know if ctypes allows pointer arithmetic.
ctypes OLESTR is a c_wchar_p, but that is still a c-style string with no
length prefix.
The only mention of BSTR in the win32 extensions is in text that
implies that the win32 extension library takes care of converting
from/to BSTR and Python strings transparently -- but that won't work for
your third-party library I suspect.
Might have to beg the author(s) of ctypes to add a BSTR type to the
list of those supported... as I can find no means of tweaking the
address computed by byref() to point somewhere into a structure rather
than the beginning of the structure.

Okay, the return value from SysAlloc... IS the integer representing
the address of a BSTR structure (IE, the address four bytes in from the
real memory start)... Note how backing up 4 bytes reveals the BSTR
length field
What happens if you just pass that item as-is, no byref(), no
conversion to ctypes pointer types...
PriKey = ctypes.windll.oleaut32.SysAllocStringByteLen("1234567890", 41)
SecKey = ctypes.windll.oleaut32.SysAllocStringByteLen("1234567890", 41)
res = VmxGet( byref(hwmcb),
byref(SecIndex),
byref(Option),
byref(c_void_p(SrchKey)),
SecKey,
PriKey,
TypeDef )
"1234567890\x00\x01:\x00\x00\x00\xb6\x01\x00\x000\x99#\x01\xe0\x94\x1a\x1e\x0b\x00\x00\x00\xff\xff\xff\xff\x00\x00\x00\x00'">>> ctypes.string_at(PriKey-4, 41+4)

You'll need to use the .string_at() or .wstring_at() functions to
extract any data changed by the call. Should not be a Python
immutability problem as all "you" allocated in Python is the integer
holding the address. You may need to call a Sys... function to free the
memory that had been allocated -- Python doesn't know about it.
--
Wulfraed Dennis Lee Bieber KD6MOG
(e-mail address removed) (e-mail address removed)
HTTP://wlfraed.home.netcom.com/
(Bestiaria Support Staff: (e-mail address removed))
HTTP://www.bestiaria.com/

The VB6 Declare is:
Declare Function VmxGet Lib "vbis5032.DLL" (DatasetHandle&,
SecIndexField%, Options%, SelectorKey$, RSecondaryKey$, RPrimaryKey$,
RRecordVariable As Any) As Integer

The only variant is the RRecordVariable, which is actually meant to be
a Type structure.

Anyway, I thought maybe there was a clue in here that I am missing.

Back to Python. I ran with the following:
VmxGet.argtypes = [LPHANDLE, LPSHORT, LPSHORT, LPBSTR, LPSTR, LPSTR,
LPSTR]
SrchKey =
windll.oleaut32.SysAllocStringByteLen("MSD19DN
\x00", 41)
SecKey =
windll.oleaut32.SysAllocStringByteLen("1234567890123456789012345678901234567890\x00",
41)
PriKey =
windll.oleaut32.SysAllocStringByteLen("ABCDEFGHIJKLMNOPQRSTUVWXYZ12345678901234\x00",
41)
TypeDef = create_string_buffer(128)
TypeDef.raw = "X".center(128, "X")
res = VmxGet( byref(hwmcb), byref(SecIndex), byref(Option),
byref(c_void_p(SrchKey)), SecKey, PriKey, TypeDef )
And, now I get:
Traceback (most recent call last):
File "C:\temp\vbisam_test_2.py", line 156, in <module>
res = VmxGet( byref(hwmcb), byref(SecIndex), byref(Option),
byref(c_void_p(S
rchKey)), SecKey, PriKey, TypeDef )
ctypes.ArgumentError: argument 5: <type 'exceptions.TypeError'>: wrong
type

So, now the problem is with:
VmxGet.argtypes = [LPHANDLE, LPSHORT, LPSHORT, LPBSTR, LPSTR, LPSTR,
LPSTR]
I am not sure what to put for arguments 5 and 6 now.

I realized the need to release the BSTRs myself, and already included:
windll.oleaut32.SysFreeString(SrchKey)
windll.oleaut32.SysFreeString(SecKey)
windll.oleaut32.SysFreeString(PriKey)
at the end of the program.

I have no problem using .string_at() to extract the returned values -
once the .argtypes let the call execute.

In case this helps, here is an excerpt from a C program that was
provided with the dll:
typedef struct tagMYREC
{
char name[51]; // 1 xref generated name key

char lastname[31]; // 2 last name
char firstname[21]; // 3 first name
char address[41]; // 4 address
char city[31]; // 5 xref city key
char state[3]; // 6 xref state
char zip[11]; // 7 xref zip code
char phone[21]; // 8 xref phone number
BSTR notes; // 9 notes

} MYREC;
typedef MYREC FAR * LPMYREC;

short rc; // return code (from VB/ISAM function calls)
static HANDLE nDatasetNumber;
static short cur_index;
static short GetOpt;
static BSTR Dummy;
static BSTR Throwaway;
static BSTR PrimaryKey;
static MYREC myrec;

rc = VmxGet(&nDatasetNumber, // int DatasetNumber // VIS_BUSY ?
&cur_index, // int SelectedIndex
&GetOpt, // int OptionParameter
&Dummy, // Don't need a Selector param with
these options
&Throwaway, // Don't need returned index entry in
this case
&PrimaryKey, // LPBSTR lpPriKey
(void *) &myrec); // LPVOID lpRecordStructure

For someone who knows C and Python, this may provide a clue to the
BSTR passing/return problem.

I received two emails from Dennis, included below (for completeness):
++++++++++++++++++++++++ Start of email #1 ++++++++++++++++++++++++
I'm back at work, hence the email.

-=-=-=-=-=-=-
So, now the problem is with:
VmxGet.argtypes = [LPHANDLE, LPSHORT, LPSHORT, LPBSTR, LPSTR, LPSTR,
LPSTR]
I am not sure what to put for arguments 5 and 6 now.

I realized the need to release the BSTRs myself, and already included:
windll.oleaut32.SysFreeString(SrchKey)
windll.oleaut32.SysFreeString(SecKey)
windll.oleaut32.SysFreeString(PriKey)
at the end of the program.

I have no problem using .string_at() to extract the returned values -
once the .argtypes let the call execute.
-=-=-=-=-=-=-=-

Might I suggest plain old c_int (or c_int32)

After all, you don't want a pointer /to/ the integer value
that was returned by the SysAlloc...; the integer value itself /is/ the
pointer (to the BSTR memory) that needs to be passed, as is, to the
DLL function. c_int should do just that -- take the integer value
"out" of the Python integer object, and put it on the stack for the
DLL function.


I must state that I'm surprised some parts of Windows is using
a hybridized BCPL string. Last place I saw using those was on the
Amiga -- in which, while the Exec, Intuition, Graphics, and other low-
level libraries were in C, the command line user interface (utility
commands, environment, etc.) were ported from Tripos -- and Tripos had
been written in BCPL.

The hybrid comment is that Windows is combining the BCPL
length value with C null terminations, so the string can be used in
both ways.
+++++++++++++++++++++++++ End of email #1 +++++++++++++++++++++++++

++++++++++++++++++++++++ Start of email #2 ++++++++++++++++++++++++
-=-=-=-=-=-=-
TypeDef = create_string_buffer(128)
TypeDef.raw = "X".center(128, "X")
-=-=-=-=-=-=-
typedef struct tagMYREC
{
char name[51]; // 1 xref generated name key

char lastname[31]; // 2 last name
char firstname[21]; // 3 first name
char address[41]; // 4 address
char city[31]; // 5 xref city key
char state[3]; // 6 xref state
char zip[11]; // 7 xref zip code
char phone[21]; // 8 xref phone number
BSTR notes; // 9 notes

} MYREC;

-=-=-=-=-=-=-

Might I suggest increasing the length of your string buffer.

Adding up the allocation in MYREC comes to:

179 bytes BEFORE including "notes" -- and as a BSTR
declaration, notes is going to be a 4-byte length, 1 or 2 nulls (the
spec for a unicode result implied a 2-byte null) PLUS whatever value
length is itself. You may need to find out what the maximum length
allowed for the notes field is in advance and add that to the buffer
size. Pity its not a BSTR *notes -- that would imply memory allocated
elsewhere and an address -- the .string_at() could use the pointer to
retrieve the real notes.
+++++++++++++++++++++++++ End of email #2 +++++++++++++++++++++++++

Sorry for the confusion on "MYREC" - this was just used in the sample
C program provided with the dll. It is not applicable to the files I
am using. For my file, the 128 is correct.

I tried:
VmxGet.argtypes = [LPHANDLE, LPSHORT, LPSHORT, LPBSTR, c_int32,
c_int32, LPSTR]
SrchKey =
windll.oleaut32.SysAllocStringByteLen("MSD19DN
\x00", 41)
SecKey =
windll.oleaut32.SysAllocStringByteLen("1234567890123456789012345678901234567890\x00",
41)
PriKey =
windll.oleaut32.SysAllocStringByteLen("ABCDEFGHIJKLMNOPQRSTUVWXYZ12345678901234\x00",
41)
TypeDef = create_string_buffer(128)
TypeDef.raw = "X".center(128, "X")
res = VmxGet( byref(hwmcb), byref(SecIndex), byref(Option),
byref(c_void_p(SrchKey)), SecKey, PriKey, TypeDef )
And got:
Traceback (most recent call last):
File "C:\temp\vbisam_test_2.py", line 156, in <module>
res = VmxGet( byref(hwmcb), byref(SecIndex), byref(Option),
byref(c_void_p(SrchKey)), SecKey, PriKey, TypeDef )
WindowsError: exception: access violation reading 0x3433322D

That was what I was getting at earlier. We know that it is a c_int32
type, but the .argtypes is not doing something right (or I am not
specifying it correctly).

How do I correctly pass what needs to be passed?
 
C

Craig

This dll was designed to be used from either C or Visual Basic 6.
I have the declare statements for VB6, if that helps.
Probably not that much -- I'd bet it's full of variant records <G>
Based on the results I have so far (and I have tried MANY permutations
like trying to use the LPSTR for SecKey and PriKey which are returned
as is TypeDef), it looks like SecKey and PriKey are being used as data
instead of pointers.
For this, I try:
LPSTR = c_char_p
VmxGet.argtypes = [LPHANDLE, LPSHORT, LPSHORT, LPBSTR, LPSTR, LPBSTR,
LPSTR]
SrchKey =
windll.oleaut32.SysAllocStringByteLen("MSD19DN
\x00", 41)
SecKey = create_string_buffer(41)
SecKey.raw = "1234567890123456789012345678901234567890"
PriKey =
windll.oleaut32.SysAllocStringByteLen("ABCDEFGHIJKLMNOPQRSTUVWXYZ12345678901234\x00",
41)
TypeDef = create_string_buffer(128)
TypeDef.raw = "X".center(128, "X")
res = VmxGet( byref(hwmcb), byref(SecIndex), byref(Option),
byref(c_void_p(SrchKey)), SecKey, byref(c_void_p(PriKey)), TypeDef )
and I get:
Traceback (most recent call last):
File "C:\temp\vbisam_test_2.py", line 158, in <module>
res = VmxGet( byref(hwmcb), byref(SecIndex), byref(Option),
byref(c_void_p(S
rchKey)), SecKey, byref(c_void_p(PriKey)), TypeDef )
WindowsError: exception: access violation reading 0x3433322D
I notice that SecKey.raw starts with "1234" and the exception address
is 0x3433322D, which is "432-".
0x2D is 4 less than the expected 0x31...
And, changing to:
VmxGet.argtypes = [LPHANDLE, LPSHORT, LPSHORT, LPBSTR, LPBSTR, LPSTR,
LPSTR]
PriKey = create_string_buffer(41)
PriKey.raw = "ABCDEFGHIJKLMNOPQRSTUVWXYZ12345678901234"
res = VmxGet( byref(hwmcb), byref(SecIndex), byref(Option),
byref(c_void_p(SrchKey)), byref(c_void_p(SecKey)), PriKey, TypeDef )
I then get:
Traceback (most recent call last):
File "C:\temp\vbisam_test_2.py", line 159, in <module>
res = VmxGet( byref(hwmcb), byref(SecIndex), byref(Option),
byref(c_void_p(S
rchKey)), byref(c_void_p(SecKey)), PriKey, TypeDef )
WindowsError: exception: access violation reading 0x4443423D
I notice that PriKey.raw starts with "ABCD" and the exception address
is 0x4443423D, which is "DBC=".
... and 0x3D is 4 less than the expected 0x41
Which leads me to suspect that BSTR are a structure, not a plain
string, in which the address given is supposed to be prefaced with a
length value. IOWs, something like:
|----|--------------------------------------|
^ ^ C-string data
| |Address to be passed
|(address - 4) to get to a length count field
Confirmed:http://msdn2.microsoft.com/en-us/library/ms221069(VS.85).aspx
(the URL is from HTTP through to the end, including .aspx)
Creating such may not be difficult -- but passing it to the DLL
could be. I don't know if ctypes allows pointer arithmetic.
ctypes OLESTR is a c_wchar_p, but that is still a c-style string with no
length prefix.
The only mention of BSTR in the win32 extensions is in text that
implies that the win32 extension library takes care of converting
from/to BSTR and Python strings transparently -- but that won't work for
your third-party library I suspect.
Might have to beg the author(s) of ctypes to add a BSTR type to the
list of those supported... as I can find no means of tweaking the
address computed by byref() to point somewhere into a structure rather
than the beginning of the structure.
pk = ctypes.windll.oleaut32.SysAllocStringByteLen("1234567890", 12)
pk
1373844
id(pk)
18941724
ctypes.string_at(pk)
'1234567890'
ctypes.string_at(pk-4, 20)
'\x0c\x00\x00\x001234567890\x00\x01\x00\x00\x00\x00'
Okay, the return value from SysAlloc... IS the integer representing
the address of a BSTR structure (IE, the address four bytes in from the
real memory start)... Note how backing up 4 bytes reveals the BSTR
length field
What happens if you just pass that item as-is, no byref(), no
conversion to ctypes pointer types...
PriKey = ctypes.windll.oleaut32.SysAllocStringByteLen("1234567890", 41)
SecKey = ctypes.windll.oleaut32.SysAllocStringByteLen("1234567890", 41)
res = VmxGet( byref(hwmcb),
byref(SecIndex),
byref(Option),
byref(c_void_p(SrchKey)),
SecKey,
PriKey,
TypeDef )
PriKey = ctypes.windll.oleaut32.SysAllocStringByteLen("1234567890", 41)
ctypes.string_at(PriKey, 41)
"1234567890\x00\x01:\x00\x00\x00\xb6\x01\x00\x000\x99#\x01\xe0\x94\x1a\x1e\x0b\x00\x00\x00\xff\xff\xff\xff\x00\x00\x00\x00'">>> ctypes.string_at(PriKey-4, 41+4)
")\x00\x00\x001234567890\x00\x01:\x00\x00\x00\xb6\x01\x00\x000\x99#\x01\xe0\x94\x1a\x1e\x0b\x00\x00\x00\xff\xff\xff\xff\x00\x00\x00\x00'"
ord(")")
41
You'll need to use the .string_at() or .wstring_at() functions to
extract any data changed by the call. Should not be a Python
immutability problem as all "you" allocated in Python is the integer
holding the address. You may need to call a Sys... function to free the
memory that had been allocated -- Python doesn't know about it.
--
Wulfraed Dennis Lee Bieber KD6MOG
(e-mail address removed) (e-mail address removed)
HTTP://wlfraed.home.netcom.com/
(Bestiaria Support Staff: (e-mail address removed))
HTTP://www.bestiaria.com/
The VB6 Declare is:
Declare Function VmxGet Lib "vbis5032.DLL" (DatasetHandle&,
SecIndexField%, Options%, SelectorKey$, RSecondaryKey$, RPrimaryKey$,
RRecordVariable As Any) As Integer
The only variant is the RRecordVariable, which is actually meant to be
a Type structure.
Anyway, I thought maybe there was a clue in here that I am missing.
Back to Python. I ran with the following:
VmxGet.argtypes = [LPHANDLE, LPSHORT, LPSHORT, LPBSTR, LPSTR, LPSTR,
LPSTR]
SrchKey =
windll.oleaut32.SysAllocStringByteLen("MSD19DN
\x00", 41)
SecKey =
windll.oleaut32.SysAllocStringByteLen("1234567890123456789012345678901234567890\x00",
41)
PriKey =
windll.oleaut32.SysAllocStringByteLen("ABCDEFGHIJKLMNOPQRSTUVWXYZ12345678901234\x00",
41)
TypeDef = create_string_buffer(128)
TypeDef.raw = "X".center(128, "X")
res = VmxGet( byref(hwmcb), byref(SecIndex), byref(Option),
byref(c_void_p(SrchKey)), SecKey, PriKey, TypeDef )
And, now I get:
Traceback (most recent call last):
File "C:\temp\vbisam_test_2.py", line 156, in <module>
res = VmxGet( byref(hwmcb), byref(SecIndex), byref(Option),
byref(c_void_p(S
rchKey)), SecKey, PriKey, TypeDef )
ctypes.ArgumentError: argument 5: <type 'exceptions.TypeError'>: wrong
type
So, now the problem is with:
VmxGet.argtypes = [LPHANDLE, LPSHORT, LPSHORT, LPBSTR, LPSTR, LPSTR,
LPSTR]
I am not sure what to put for arguments 5 and 6 now.
I realized the need to release the BSTRs myself, and already included:
windll.oleaut32.SysFreeString(SrchKey)
windll.oleaut32.SysFreeString(SecKey)
windll.oleaut32.SysFreeString(PriKey)
at the end of the program.
I have no problem using .string_at() to extract the returned values -
once the .argtypes let the call execute.
In case this helps, here is an excerpt from a C program that was
provided with the dll:
typedef struct tagMYREC
{
char name[51]; // 1 xref generated name key
char lastname[31]; // 2 last name
char firstname[21]; // 3 first name
char address[41]; // 4 address
char city[31]; // 5 xref city key
char state[3]; // 6 xref state
char zip[11]; // 7 xref zip code
char phone[21]; // 8 xref phone number
BSTR notes; // 9 notes
} MYREC;
typedef MYREC FAR * LPMYREC;
short rc; // return code (from VB/ISAM function calls)
static HANDLE nDatasetNumber;
static short cur_index;
static short GetOpt;
static BSTR Dummy;
static BSTR Throwaway;
static BSTR PrimaryKey;
static MYREC myrec;
rc = VmxGet(&nDatasetNumber, // int DatasetNumber // VIS_BUSY ?
&cur_index, // int SelectedIndex
&GetOpt, // int OptionParameter
&Dummy, // Don't need a Selector param with
these options
&Throwaway, // Don't need returned index entry in
this case
&PrimaryKey, // LPBSTR lpPriKey
(void *) &myrec); // LPVOID lpRecordStructure
For someone who knows C and Python, this may provide a clue to the
BSTR passing/return problem.

I received two emails from Dennis, included below (for completeness):
++++++++++++++++++++++++ Start of email #1 ++++++++++++++++++++++++
I'm back at work, hence the email.

-=-=-=-=-=-=-
So, now the problem is with:
VmxGet.argtypes = [LPHANDLE, LPSHORT, LPSHORT, LPBSTR, LPSTR, LPSTR,
LPSTR]
I am not sure what to put for arguments 5 and 6 now.

I realized the need to release the BSTRs...

read more >>

I received an email from Dennis, which I have included below (for
completeness):
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Last email; since I'm at the end of my analytical skills
(lacking the library I can't perform tests of my own).

Since the value being passed should have been the address of
the BSTR structure (put a >> print "address = 0x%8.8X" % PriKey <<
just before the call), for it to claim it can't read 0x3433322D
implies another layer of indirection in the library... in that it must
have used the passed address to pick up the first four bytes of the
string portion of the BSTR, and is then trying to treat /that/ as the
address to what it expects to really be the BSTR (subtracting four
from that to get to the expected length field).

My last try therefor...

Try changing the argtype to a pointer-to-long (or whatever
pointer types have been defined). Then pass >> byref(c_int32(PriKey))
<< (Have we gone full circle yet?)

My hope is that, based upon the above evaluation, this final
attempt will pass an address pointing TO the address stored in the
c_int32, and that the library will use /that/ address to get to the
BSTR itself.

I'd also suggest that, just for safety, you hold onto a
reference to the original allocations... Just in case the library really
intends to allocate internally and change the pointer value being
passed (you may need to encapsulate it outside of the library call:

ci_PriKey = c_int32(PriKey)

do library call: ... , byref(ci_PriKey), ...

if ci_PriKey.value != PriKey:
# you were returned a new buffer

Don't forget to treat the other BSTR the same way...

+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

I would have gladly provided the dll in question if you had requested
it.

Anyway, I added the following, for completeness:
LPLONG = POINTER(c_long)

And, then:
VmxGet.argtypes = [LPHANDLE, LPSHORT, LPSHORT, LPBSTR, LPLONG, LPLONG,
LPSTR]
print "\nVmxGet test - looking for valid record..."
printf ("Before - PriKey = 0x%8.8X, SecKey = 0x%8.8X\n", PriKey,
SecKey)
printf (" ci_PriKey = 0x%8.8X, ci_SecKey = 0x%8.8X\n",
ci_PriKey.value, ci_SecKey.value)
res = VmxGet( byref(hwmcb), byref(SecIndex), byref(Option),
byref(c_void_p(SrchKey)), byref(ci_SecKey), byref(ci_PriKey),
TypeDef )
printf ("After - PriKey = 0x%8.8X, SecKey = 0x%8.8X\n", PriKey,
SecKey)
printf (" ci_PriKey = 0x%8.8X, ci_SecKey = 0x%8.8X\n",
ci_PriKey.value, ci_SecKey.value)
printf ("Record returned = \x22%s\x22\n", TypeDef.raw)
printf (" Key returned = \x22%s\x22\n", string_at(ci_PriKey.value,
41))
printf ("2ndKey returned = \x22%s\x22\n", string_at(ci_SecKey.value,
41))

And this is what I got:
VmxGet test - looking for valid record...
Before - PriKey = 0x0044F56C, SecKey = 0x0044F534
ci_PriKey = 0x0044F56C, ci_SecKey = 0x0044F534
After - PriKey = 0x0044F56C, SecKey = 0x0044F534
ci_PriKey = 0x0043D49C, ci_SecKey = 0x0043D484
Record returned = "MSD19DN Testing
Board Of E
ducation 4 "
Key returned = "MSD19DN"
2ndKey returned = "MSD19DN"


PERFECT!


Now, I just have to write some wrapper code and I should be able to
turn this set of calls into a VB/ISAM object, that can then be
instantiated for each file I need.


Dennis, thank you very much for all of your wonderful contributions.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,744
Messages
2,569,483
Members
44,903
Latest member
orderPeak8CBDGummies

Latest Threads

Top