Search code examples
pythonpointersctypeswrapperoserror

Ctypes Issue - Impossible to work with 64-bits DLL versus 32-bits DLL - OSError: exception: access violation reading


After many hours of research and topics analysized, I can't find an explication & a way to solve properly my issue. If you found an answer, thank you very much in advance.

Summary of my issue:

I'm working with python 3.8 (64-bits versions) & a dll (64-bits too). And, I can't wrap this one properly into my python code, despite of ctypes capabilities (argstype, restype..etc) due to a pointer issue (from my little understanding).

What is troubling me, is that, the same DLL in 32 bits, with python 32-bits is working well with the my python code.

Please find here after:

  • The C Prototype
  • The Python Code to Wrap the C function
  • The Usage of the C function into the python code

C Function Prototype (from API description): WORD mpc_AddToScenarioPcd (BYTE, DWORD, DWORD, DWORD, DWORD, DWORD, DWORD, DWORD, POINTER(BYTE), DWORD, POINTER(BYTE), VOID*)

My Python Code to wrap this one (64 bits version & 32 bits version commented) :

from ctypes import *

# DLL Loading ---------------------------------------------------------------------------------------
# 64 Bits -------------------------------------------------------------------------------------------
dllpath = '%s/MP300Com64.dll' % str(os.path.dirname(__file__))
MP300Dll = WinDLL(dllpath)
MP300Dll_CDECL = CDLL(dllpath)

# 64 Bits -------------------------------------------------------------------------------------------
# dllpath = '%s/MP300Com.dll' % str(os.path.dirname(__file__))
# MP300Dll = WinDLL(dllpath)
# MP300Dll_CDECL = CDLL(dllpath)

# Keyword Type definition ---------------------------------------------------------------------------
WORD = c_ushort
DWORD = c_ulong
BYTE = c_ubyte

# Function Wrapped ----------------------------------------------------------------------------------
mpc_AddToScenarioPcd = MP300Dll_CDECL.MPC_AddToScenarioPcd
mpc_AddToScenarioPcd.argtypes = [BYTE, DWORD, DWORD, DWORD, DWORD, DWORD, DWORD, DWORD, 
                                 POINTER(BYTE), DWORD, POINTER(BYTE), POINTER(DWORD)]
mpc_AddToScenarioPcd.restype = WORD

# -- Creation of the First Pointer to the array of Bytes --------------------------------------------
array1 = (BYTE* len(5))(*[BYTE(1),BYTE(2),BYTE(3),BYTE(4),BYTE(5)])
array2 = (BYTE* len(5))(*[BYTE(10),BYTE(20),BYTE(30),BYTE(40),BYTE(50)])

# -- Pointer cast of the array1 and 2 ---------------------------------------------------------------
cast(array1, POINTER(BYTE))
cast(array2, POINTER(BYTE))
 
# VOID* obj creation --------------------------------------------------------------------------------
param = c_void_p()

# -- Function Usage through Python Code -------------------------------------------------------------
mpc_AddToScenarioPcd(BYTE(0),          
                     DWORD(0),           
                     (22),                       
                     DWORD(1),        
                     DWORD(1),       
                     DWORD(1),        
                     DWORD(1),             
                     DWORD(2),      
                     array1,               
                     DWORD(10),   
                     array2, 
                     param)  

With Python 3.4 (32-bits version) & DLL 32-bits:

=> Everything is working well, no error.

With Python 3.8 (64-bits version) & DLL 64-bits:

=> I get this exception: OSError: exception: access violation reading 0x000000005EC0B808

I need to use a 64-bit version, consequently I'm blocked with this issue... If someone could explain to me what is wrong with my pointer (or type usage..) in 64-bits, and, how to solve it, I'll be very grateful!!

Again thank you very much in advance for all your expertise & work on my topic,

PS: Sorry for the non functional code and the inability to reproduce the exception


Solution

  • Here's sample DLL code and minimal ctypes code that works on both 32- and 64-bit Python. Note the following points:

    • There are pre-defined Windows types in ctypes.wintypes.
    • When .argtypes and .restype are set correctly, number of parameters and types are checked and converted as needed between Python and C. Extra casting and wrapping is not usually required.
    • CDLL corresponds to 32-bit __cdecl calling convention. WinDLL corresponds to 32-bit __stdcall. In 64-bit, there is only one calling convention. It is implemented differently between Linux and Windows, but still only one, so both CDLL and WinDLL will work, but use the correct one for 32-bit portability.

    test.c

    #include <windows.h>
    #include <stdio.h>
    
    __declspec(dllexport)
    WORD mpc_AddToScenarioPcd(BYTE b, DWORD d1, DWORD d2, DWORD d3, DWORD d4, DWORD d5, DWORD d6, DWORD d7, LPBYTE pb1, DWORD d8, LPBYTE pb2, LPVOID pv) {
        printf("%hhu %u %u %u %u %u %u %u %p %u %p %p\n",b,d1,d2,d3,d4,d5,d6,d7,pb1,d8,pb2,pv);
        for(int i = 0; i < 5; ++i)
            printf("%hhu %hhu\n",pb1[i],pb2[i]);
        return 1234;
    }
    

    test.py

    from ctypes import *
    from ctypes.wintypes import *  # pre-defined Windows types
    
    dll = CDLL('./test')
    dll.mpc_AddToScenarioPcd.argtypes = BYTE,DWORD,DWORD,DWORD,DWORD,DWORD,DWORD,DWORD,LPBYTE,DWORD,LPBYTE,LPVOID
    dll.mpc_AddToScenarioPcd.restype = WORD
    
    b1 = (BYTE * 5)(1,2,3,4,5)
    b2 = (BYTE * 5)(10,20,30,40,50)
    print(f'{addressof(b1):016X} {addressof(b2):016X}')
    result = dll.mpc_AddToScenarioPcd(100,1,2,3,4,5,6,7,b1,8,b2,None)
    print(result)
    

    Output

    Note the addresses printed in Python and C are the same.

    00000194D0447A90 00000194D0447C10
    100 1 2 3 4 5 6 7 00000194D0447A90 8 00000194D0447C10 0000000000000000
    1 10
    2 20
    3 30
    4 40
    5 50
    1234