Closed stefnet00 closed 4 years ago
Disclaimer: I have not (yet) tested this against the actual DLL, but thanks for providing a link to it.
Question: Can you point me to some documentation of this function OR any kind of header files / C headers / C++ headers? Or do you have a working piece of code in another programming language that actually successfully calls this function?
From what I can see, there are a few conceptual mistakes. I tried to fix and annotate your code as far as possible:
import zugbruecke as ctypes
DLLPATH = "./5MW_Baseline-ServoData/DISCON_Win32.dll"
DISCONDLL = ctypes.windll.LoadLibrary(DLLPATH)
DISCON = DISCONDLL.DISCON
INFILE = b"./DISCON.IN"
data_length = 85
avrSWAPVal = [0.0] * data_length # list of Python floats, fixed length 85
avrSWAPVal[49] = 32
avrSWAPVal[50] = len(INFILE)
avrSWAPVal[51] = 32
aviFAIL = ctypes.c_int(0) # c_int
accINFILE = ctypes.create_string_buffer(int(avrSWAPVal[50])) # null-terminated string
accINFILE.value = INFILE
avcOUTNAME = ctypes.create_string_buffer(int(avrSWAPVal[51])) # null-terminated string
avcMSG = ctypes.create_string_buffer(int(avrSWAPVal[49])) # null-terminated string
DISCON.argtypes = (
ctypes.POINTER(ctypes.c_float * data_length), # appears to be of constant size?
ctypes.c_int, # aviFAIL
ctypes.POINTER(ctypes.c_char), # accINFILE
ctypes.POINTER(ctypes.c_char), # avcOUTNAME
ctypes.POINTER(ctypes.c_char), # avcMSG
)
DISCON.memsync = [
{
'p': [2], # accINFILE
'n': True, # null-terminated string
},
{
'p': [3], # avcOUTNAME
'n': True, # null-terminated string
},
{
'p': [4], # avcMSG
'n': True, # null-terminated string
},
]
ctypes_float_values = (ctypes.c_float * data_length)(*avrSWAPVal)
DISCON(
ctypes.pointer(ctypes_float_values), # must convert to pointer
aviFAIL, # passed as value
accINFILE, # is pointer, pass as is
avcOUTNAME, # is pointer, pass as is
avcMSG, # is pointer, pass as is
)
Result[:] = ctypes_float_values[:]
Based on your example, I am not entirely sure in what kind of way the functions expects its first argument, i.e. avrSWAPVal
. Is it an array/vector of fixed length or of dynamic/flexible length? I am just guessing from your example that it is fixed length (85
) because length information is not passed to the function through any other argument. Your original code indicated that it was zero-terminated (potentially of dynamic length), which (at the moment) only works for strings (char arrays).
I have fixed memsync
for the string-buffer arguments. Both argtypes
and memsync
should work if avrSWAPVal
is of constant length. If not, I'd need to know whether or not the function receives some information on its actual dynamic length.
I've just updated the question with added definition of the DISCON routine.
Thanks for the added information.
From context, I'd guess that aviFAIL
should be passed as a pointer: A flag used to indicate the success of this DLL call set as follows: 0 if the DLL call was successful, >0 if the DLL call was successful but cMessage should be issued as a warning messsage, <0 if the DLL call was unsuccessful or for any other reason the simulation is to be stopped at this point with cMessage as the error message.
I suppose the DLL function sets this value and you are supposed to read it after the function call.
With respect to avrSWAP
, I still have no idea about its length. The swap array, used to pass data to, and receive data from, the DLL controller.
... does not indicate any information on constant/dynamic length or where to find length information. How did you come up with 85
?
I could not find any definition document of DISCON yet. As i know avrSWAP
is fixed length. 85
was just the highest index accessed.
Thanks a lot s-m-e, I could make some progress thanks to your help.
I could find a header file in C which declares the DISCON routine:
#include <stdio.h>
#include <string.h>
extern "C"
{ void __declspec(dllexport) __cdecl DISCON(float *avrSwap, int *aviFail,
char *accInfile, char *avcOutname, char *avcMsg);
}
I also found a documentation which states:
So you were guessing right, aviFAIL
should be passed as a pointer. I have also checked this by changing its initial value. If set to 0
, the error message is:
access violation writing 0x00000000
If it is set to 100
, the error message is:
access violation writing 0x00000064
My current script is
import zugbruecke as ctypes
DLLPATH = "./5MW_Baseline-ServoData/DISCON_Win32.dll"
DISCONDLL = ctypes.windll.LoadLibrary(DLLPATH)
DISCON = DISCONDLL.DISCON
INFILE = b"./DISCON.IN"
data_length = 85
avrSWAPVal = [0.0] * data_length # list of Python floats, fixed length 85
avrSWAPVal[49] = 32
avrSWAPVal[50] = len(INFILE)
avrSWAPVal[51] = 32
aviFAIL = ctypes.c_int(0) # c_int
accINFILE = ctypes.create_string_buffer(int(avrSWAPVal[50])) # null-terminated string
accINFILE.value = INFILE
avcOUTNAME = ctypes.create_string_buffer(int(avrSWAPVal[51])) # null-terminated string
avcMSG = ctypes.create_string_buffer(int(avrSWAPVal[49])) # null-terminated string
DISCON.argtypes = (
ctypes.POINTER(ctypes.c_float * data_length), # appears to be of constant size?
ctypes.POINTER(ctypes.c_int), # aviFAIL
ctypes.POINTER(ctypes.c_char), # accINFILE
ctypes.POINTER(ctypes.c_char), # avcOUTNAME
ctypes.POINTER(ctypes.c_char), # avcMSG
)
DISCON.memsync = [
{
'p': [2], # accINFILE
'n': True, # null-terminated string
},
{
'p': [3], # avcOUTNAME
'n': True, # null-terminated string
},
{
'p': [4], # avcMSG
'n': True, # null-terminated string
},
]
ctypes_float_values = (ctypes.c_float * data_length)(*avrSWAPVal)
DISCON(
ctypes.pointer(ctypes_float_values), # must convert to pointer
aviFAIL, # passed as value (referring to test_devide.py)
accINFILE, # is pointer, pass as is
avcOUTNAME, # is pointer, pass as is
avcMSG, # is pointer, pass as is
)
Result[:] = ctypes_float_values[:]
The result is still an unwanted error message and I have no clue what's wrong:
ValueError: Procedure probably called with too many arguments (20 bytes in excess)
20 bytes in excess
and the __cdecl
in your C header are telling you that you tried to use the wrong calling convention. Use ctypes.cdll.LoadLibrary(DLLPATH)
instead of ctypes.windll.LoadLibrary(DLLPATH)
.
Besides, this may not account for all 20 bytes (I am not sure). Your OS is Linux or OS X, likely 64 Bit. zugbruecke
defaults to 32 bit Wine. If your DLL is 32 bit (very likely or you should have run into other issues), this is fine, bit it implies integers of different default lengths (64 bit Unix vs 32 bit "Windows"). Try specific lengths for aviFAIL
as in ctypes.c_int16
or ctypes.c_int32
instead of ctypes.c_int
if you are still running into issues with the number of arguments.
float *avrSwap
(as found in your C header) is a pointer to a float or float array without length information. It can have arbitrary length. I guess you have to figure out where and how the memory for this pointer is being allocated. Judging by your documentation, it is not your function which is responsible for allocating this memory. Or maybe it is? Maybe there are other functions that you have to call in advance for setting the stage (and allocating memory for this pointer).
You can get better test results by eliminating zugbruecke
's communication layer from the equation. zugbruecke
offers a command named wine-python
. It allows to run a Windows-version of Python directly on Wine. There you can use the actual ctypes
instead of zugbruecke
for debugging a little further. I fear though that either way you have to understand where float *avrSwap
is coming from.
Well, I'm way to little into programming in general to understand these hints of __cdecl
and 20 bytes in excess.
Thanks to your great support DISCON is executed without any system error now. It finishes with an error but that's due to the input values, not types. I have to fill avrSWAP
with correct values.
avrSWAP
has a default length of 164
and is then extended depending on some of its entries. Lets say the length is 164 + avrSWAP[10]
and avrSWAP[10]=10
the resulting length is 174
.
There is just another short question about the input file given in accINFILE
? Where do I have to copy the DISCON.IN
or what could be a correct path given in accINFILE
to make the input file available for the DLL?
Well, I'm way to little into programming in general to understand these hints of __cdecl and 20 bytes in excess.
That's more than ok. It took me ages to understand them ;)
There should really be some documentation in avrSWAP
somewhere. Otherwise, I'd implemented it as fixed-length for the time being but make it "much" longer, i.e. 1000 elements or something similarly "ridiculous". This should help with debugging.
There is just another short question about the input file given in accINFILE? Where do I have to copy the DISCON.IN or what could be a correct path given in accINFILE to make the input file available for the DLL?
I was wondering about this, too. It all depends on the DLL. Just to be on the safe side, I'd copy all stuff into your current working directory, i.e. where your Python script resides and where you run it. Once this is working, you can play with relative and absolute paths etc. Beyond that, the corresponding documentation is bizarre. For instance:
accInfile
is a char-array (a string in modern terms), which is totally fine. The DLL needs to know about its length because its developer did not use null-termination - also a common pattern. So passing the length through a separate parameter (avrSWAP[50]
) is fine - but it really should be an integer. In your case, the length is passes within an array of single-precision floating point numbers. This has the potential to blow up in a few facinating ways. It's not your fault - it's the fault of the DLL's developer. Anyway. I guess the following might work:
import zugbruecke as ctypes
DLLPATH = "./5MW_Baseline-ServoData/DISCON_Win32.dll"
DISCONDLL = ctypes.cdll.LoadLibrary(DLLPATH)
DISCON = DISCONDLL.DISCON
INFILE = b'DISCON.IN' # relative path without prefix pointing to CWD: './'
OUTNAME = b'DISCON.OUT' # relative path without prefix pointing to CWD: './'
data_length = 1024
string_buffer_length = 1024
avrSWAPVal = [0.0 for pos in range(data_length)] # list of Python floats, fixed length data_length
aviFAIL = ctypes.c_int32(0) # c_int - correct size? On Windows, this should be 32 bit
accINFILE = ctypes.create_string_buffer(string_buffer_length) # null-terminated string
accINFILE.value = INFILE
avrSWAPVal[50] = float(len(INFILE)) # bizarre
avcOUTNAME = ctypes.create_string_buffer(string_buffer_length) # null-terminated string
avcOUTNAME.value = OUTNAME
avrSWAPVal[51] = float(len(OUTNAME)) # bizarre
avcMSG = ctypes.create_string_buffer(string_buffer_length) # null-terminated string
avcMSG.value = b' ' * string_buffer_length
avrSWAPVal[49] = float(string_buffer_length) # bizarre
DISCON.argtypes = (
ctypes.POINTER(ctypes.c_float * data_length), # appears to be of constant size?
ctypes.POINTER(ctypes.c_int32), # aviFAIL
ctypes.POINTER(ctypes.c_char), # accINFILE
ctypes.POINTER(ctypes.c_char), # avcOUTNAME
ctypes.POINTER(ctypes.c_char), # avcMSG
)
DISCON.memsync = [
{
'p': [2], # accINFILE
'n': True, # null-terminated string
},
{
'p': [3], # avcOUTNAME
'n': True, # null-terminated string
},
{
'p': [4], # avcMSG
'n': True, # null-terminated string
},
]
ctypes_float_values = (ctypes.c_float * data_length)(*avrSWAPVal)
DISCON(
ctypes.pointer(ctypes_float_values), # must convert to pointer
aviFAIL, # passed as value (referring to test_devide.py)
accINFILE, # is pointer, pass as is
avcOUTNAME, # is pointer, pass as is
avcMSG, # is pointer, pass as is
)
Result[:] = ctypes_float_values[:]
I probably made a mistake in declaring avcMSG
correctly for your DLL. I edited the script in my last reply accordingly.
The script now does what I expect. I can call the DLL and receive useful outputs. Thats great! Thanks a lot!
There was only one thing with the indices. They start with 1
in the Fortran code and the documentation but with 0
in python. So I added a dummy entry at the beginning of avrSWAPVal
which is not transferred to ctypes_float_values
in order to have the same index numbers:
ctypes_float_values = (self.ctypes.c_float * len(avrSWAPVal[1:]))(*avrSWAPVal[1:])
self.DISCON(
self.ctypes.pointer(ctypes_float_values), # must convert to pointer
self.aviFAIL, # passed as value (referring to test_devide.py)
self.accINFILE, # is pointer, pass as is
self.avcOUTNAME, # is pointer, pass as is
self.avcMSG, # is pointer, pass as is
)
self.avrSWAPVal[1:] = ctypes_float_values[:]
That's great to hear. Good luck with your work :)
I'm working on a program to simulate wind turbine dynamics. Therefore I'd like to include blade pitch controllers of various turbines which are mostly shared by .dll files. I was excited when I found zugbruecke but unfortunately I could not get it running yet. Do you have any ideas what's wrong?
DISCON is written in fortran and begins with the following lines:
avrSWAP has a fixed length and the return variable (DISCON) is not set.
The files
DISCON_Win32.dll
, its entire source file, andDISCON.IN
can be found in the windows archive of FAST8 (I don't know if I'm allowed to upload them somewhere): https://nwtc.nrel.gov/FAST8The arguments are not correct and the call should give an error message. But what I get by now is:
OSError: exception: access violation writing 0x00000000