Search code examples
pythoncdllenumsctypes

Ctypes, Passing pointer to enum, receiving uint_32


I'm trying to pass an pointer to an enum. I found a tutorial for ctypes and enum here [ctypes structures ][1] but my application is a little different. the pseudocode for my DLL looks like this

#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include <stdlib.h>

#ifdef SOME_API_EXPORTS
#define SOME_API __declspec(dllexport)
#else
#define SOME_API __declspec(dllimport)
#endif

typedef enum   
{
    off,
    on    
} MyEnum;

SOME_API int32_t get_param(MyEnum* param1)
{
    int status = error;
    if (param1 == NULL)
    {
        return status;
    }
    //do some processing
    status = done;
    return status;
}

what I did in python looks similar to this:

    import ctypes
    from enum import IntEnum
   
    test = ctypes.WinDLL('Project.dll')
    
    if (test == 0):
        print( " Could not open DLL")
    
    class CtypesEnum(IntEnum):
        @classmethod
        def from_param(cls, obj):
            return int(obj) 
    
    class myEnum(CtypesEnum):
    
        off = 0                
        on = 1                     
    
    
    getParam = test.get_param
    getParam.argtypes = [ctypes.POINTER(ctypes.c_int)]
    getParam.restype= ctypes.c_uint
    getParam(myEnum.on)

The error I get now is

     Traceback (most recent call last):
      File "<pyshell#5>", line 1, in <module>
        getParam(myEnum.on)
    ctypes.ArgumentError: argument 1: <class 'TypeError'>: expected LP_c_long instance instead of myEnum

What's the correct way of passing an enum pointer using ctypes.I couldn't find an example and I'm kinda new to python :/

  [1]: https://v4.chriskrycho.com/2015/ctypes-structures-and-dll-exports.html

This was my solution for the python part. I read 1 and 0 respectively.

import ctypes
from ctypes import *

test = ctypes.WinDLL('Project.dll')

if (test == 0):
    print( " Could not open DLL")
 
class myEnum(c_int):

    off = 0                
    on = 1

    
getParam = test.get_param
getParam.argtype =  myEnum()
getParam.restype= ctypes.c_int
result = test.get_param(myEnum.on)
print(result)
result = test.get_param(myEnum.off)
print(result)

Solution

  • Listing [Python.Docs]: ctypes - A foreign function library for Python.
    As I stated in my comment, the 1st error is (most likely) the same thing as [SO]: C function called from Python via ctypes returns incorrect value (@CristiFati's answer), then after the edit, another one (which occurs before the 1st) pops up.
    Here's how to get rid of all.

    dll00.c:

    #include <stdio.h>
    #include <stdint.h>
    
    #if defined(_WIN32)
    #  define DLL00_EXPORT_API __declspec(dllexport)
    #else
    #  define DLL00_EXPORT_API
    #endif
    
    
    typedef enum {
        Off,
        On,
    } Enum;
    
    
    #if defined(__cplusplus)
    extern "C" {
    #endif
    
    DLL00_EXPORT_API int32_t get_param(Enum *pparam);
    
    #if defined(__cplusplus)
    }
    #endif
    
    
    int32_t get_param(Enum *pparam) {
        int status = -1;
        if (pparam == NULL) {
            printf("NULL arg\n");
            return status;
        }
        printf("C - arg: %d\n", *pparam);
        if (pparam == NULL) {
            return status;
        }
        //do some processing
        status = 1;
        return status;
    }
    

    code00.py:

    #!/usr/bin/env python
    
    import sys
    import ctypes as ct
    from enum import IntEnum
    
    
    DLL_NAME = "./dll00.{:s}".format("dll" if sys.platform[:3].lower() == "win" else "so")
    
    
    class CtypesEnum(IntEnum):
        @classmethod
        def from_param(cls, obj):
            return int(obj)
    
    
    class OOEnum(CtypesEnum):
        Off = 0
        On = 1
    
    
    
    def main(*argv):
        dll = ct.CDLL(DLL_NAME)  # Use ct.WinDLL for Win 32bit
        get_param = dll.get_param
        get_param.argtypes = (ct.POINTER(ct.c_int),)
        get_param.restype = ct.c_int
    
        for en in [
            OOEnum.Off,
            OOEnum.On,
        ]:
            get_param(ct.pointer(ct.c_int(en.value)))
    
    
    if __name__ == "__main__":
        print("Python {:s} {:03d}bit on {:s}\n".format(" ".join(elem.strip() for elem in sys.version.split("\n")),
                                                       64 if sys.maxsize > 0x100000000 else 32, sys.platform))
        rc = main(*sys.argv[1:])
        print("\nDone.")
        sys.exit(rc)
    

    Output:

    [cfati@cfati-5510-0:/mnt/e/Work/Dev/StackOverflow/q068826175]> ~/sopr.sh
    ### Set shorter prompt to better fit when pasted in StackOverflow (or other) pages ###
    
    [064bit prompt]> ls
    code00.py  dll00.c
    [064bit prompt]> 
    [064bit prompt]> gcc -shared -m64 -fPIC -o dll00.so dll00.c
    [064bit prompt]> ls
    code00.py  dll00.c  dll00.so
    [064bit prompt]> 
    [064bit prompt]> ${PY_VENVS_HOME}/py_pc064_03_08_test0/bin/python code00.py 
    Python 3.8.10 (default, Jun  2 2021, 10:49:15) [GCC 9.4.0] 064bit on linux
    
    C - arg: 0
    C - arg: 1
    
    Done.
    

    Things are the same on Win, only build (and run) commands differ.