views:

240

answers:

3

Hello everyone!

I sure hope this won't be an already answered question or a stupid one. Recently I've been programming with several instruments. Trying to communicate between them in order to create a testing program. However I've encoutered some problems with one specific instrument when I'm trying to call functions that I've "masked" out from the instruments DLL file. When I use the interactive python shell it works perfectly (although its alot of word clobbering). But when I implement the functions in a object-oriented manner the program fails, well actually it doesn't fail it just doesn't do anything. This is the first method that's called: (ctypes and ctypes.util is imported)

    def init_hardware(self):
    """ Inits the instrument """
    self.write_log("Initialising the automatic tuner")
    version_string = create_string_buffer(80)
    self.error_string = create_string_buffer(80)
    self.name = "Maury MT982EU"
    self.write_log("Tuner DLL path: %s", find_library('MLibTuners'))
    self.maury = WinDLL('MlibTuners')
    self.maury.get_tuner_driver_version(version_string)
    if (version_string.value == ""):
        self.write_log("IMPORTANT: Error obtaining the driver version")
    else:
        self.write_log("Version number of the DLL: %s" % version_string.value)
    self.ThreeTypeLong = c_long * 3

Now that works swell, everything is perfect and I get perfect log-entries. But when I try to run a method further into the program called:

def add_tuner_and_controller(self, name, serial_number, tuner_number=0):
    """ Adds the tuner to the driver object, controller is inside the tuner """
    self.write_log("Adding tuner %d and the built-in controller" % tuner_number)
    TempType = self.ThreeTypeLong()
    self.maury.add_controller(c_short(tuner_number), c_char_p(self.file_path), c_char_p(name), c_int(0), c_int(0), 
                              c_long(0), c_short(serial_number), self.error_string)
    self.maury.add_tuner(c_short(tuner_number), c_char_p(name), c_short(serial_number), c_short(0),
                            c_short(1), pointer(c_double()), TempType, pointer(c_double()), pointer(c_double()),
                            pointer(c_double()), self.error_string)

The program suddenly stops working/keeps running , nothing happenes when the "self.maury"-line is called. When I place everything in the init_hardware method it works perfectly so I'm guessing there's a slight memory "error" or something with the objective oriented structure. I really want it to remain this way, is there anyway to isolate the functions in this manner? or do I have to restrict myself to a big chunk of code?


EDIT:
Documentation info:
[Legend: The stars indicate pointers and the brackets indicate arrays]

The add_tuner function adds or updates one tuner in the tuner driver object.

short add_tuner(short tuner_number, char model[ ], short serial_number, short ctlr_num, short ctlr_port, short *no_of_motors, long max_range[ ], double *fmin, double *fmax, double *fcrossover, char error_string[ ])

Output: no_motors, max_range (array of three numbers), fmin, fmax, fcrossover,error_string (80+ characters long), function-return->Error flag



The add_controller function adds or updates one controller in the tuner driver object

short add_controller(short controller_number, char driver[ ], char model[ ], int timeout, int address, long delay_ms, char error_string[ ])

Output: error_string, function-return->Error flag

+1  A: 

I'm not sure about your exact problem, but here's a couple general tips:

For those functions that you are calling outside of the constructor, I would strongly recommend setting their argtypes in the constructor as well. Once you've declared the argtypes, you shouldn't need to cast all the arguments as c_short, c_double, etc. Moreover, if you do accidentally pass an incorrect argument to a C function, Python will raise a runtime error instead of crashing within the DLL.

Another minor detail, but you should be using x = 0; byref(x) or maybe POINTER(c_double)() instead of pointer(c_double()) in the tuner and controller.

I've been writing some ctypes classes in Python 2.6 recently as well, and I haven't seen any issues like what you're describing. Since there apparently aren't any Python bug reports on that either, I strongly believe that there's just a minute detail that we are both overlooking in your method that is having a problem.

Mark Rushakoff
I'm currently using Python 2.5, is there perhaps an issue there?, safe to travel to a new version?
From http://python.org/download/: "The current production versions are Python 2.6.2 and Python 3.1... Note that both Python 2.6 and 3.1 are considered stable production releases..."
Mark Rushakoff
I am using lot of modules with ctypes related code in production with Python 2.5 and I have not seen similar problem. Anyway, +1 for setting argtypes.
Jiri
A: 

Are any of the parameters in add-controller or add-tuner actually return values?

Strongly recommend that you define prototypes of your functions, rather than calling them direct with casts of all the parameters.

I'm sure you've read this page already, but the section you want to look at is Function Prototypes. Makes the code much cleaner and easier to trace/debug.

Also -- as Mark Rushakoff mentions too -- using pointer(c_double()) and like in your call is pretty icky. I have much better luck w/ POINTER(), and recommend again that you predeclare the value as a variable, and pass the variable in your function call. Then at least you can examine its value afterward for strange behavior.

EDIT: So your prototype and call will look something like this:

prototype = WINFUNCTYPE(
    c_int,              # Return value (correct? Guess)
    c_short,            # tuner_number
    c_char_p,           # file_path
    c_char_p,           # name
    c_int,              # 0?
    c_int,              # 0?
    c_long,             # 0?
    c_short,            # serial_number
    c_char_p,           # error_string
)
# 1 input, 2 output, 4 input default to zero (I think; check doc page)
paramflags = (1, 'TunerNumber' ), (1, 'FilePath' ), (1, 'Name' ), ...
AddController = prototype(('add_controller', WinDLL.MlibTuners), paramflags)

Then your call is much cleaner:

arg1 = 0
arg2 = 0
arg3 = 0
AddController(tuner_number, self.file_path, name, arg1, arg2, arg3, 
    serial_number, self.error_string)
John Pirie
Well this doesn't actually solve my issue, but thanks for the hints anyways! so you want me to declare the prototypes and set the paramflags in the constructor? I understand that the error will be easier to find if it was a syntax error. But this isn't, I think. I've changed and moved everything to the constructor and then it works. But as soon as a method is called from my running class it always get stuck at the "self.maury" line. Maybe i should have the DLL as a parameter to every method?
A: 

I found out that the only way to call the functions in the exported DLL was to use the DLL through a parameter in each method. (the program exports the dll and in each method called it will send it as a parameter). It looks pretty ugly but that's the only way I found to be working for me. I even tried exporting the DLL as an class attribute. The system I'm working with is pretty hefty so I guess there is some boboo-code somewhere that makes it fail. Thanks for all the feedback and tips!

/Mazdak