Search code examples
pythoncshared-librariesctypesdllexport

Ctypes WindowsError: exception: access violation writing 0x0000000000000000 while calling a DLL function from another dll file


I have a program that load .so file from linux which works fine with no problem. Now, I'm trying to make the program cross-platform. After struggling for a while, I have managed to compile a dll file to support Windows but when I tried to load from ctypes, I get this error:

"WindowsError: exception: access violation writing 0x0000000000000000"

It seems it can't even properly pass the arguments to my c-function. I'm thinking I might have made some mistake while converting my c-code for Windows dll or the my python code probably need some more work for properly loading dll and utilize it in Windows. I'm familiar with python but am an novice to both ctypes and C. I tried to search what I'm missing but couldn't figure out what to do. :(

I've tried few more things and found where the error occurs but still don't know how to solve. So my problem occurs when the dll function tries to call another dll function inside. I've updated my code to include that part.

I've checked that another dll("mylib.dll") call inside my c-code works fine by calling the initfunc inside a main function(In another c-code with the same calling convention.) So my "mylib.dll" doesn't have a problem. I'm guessing I might have to do something more if I want to call a dll function from inside a dll function?

Below is my c-code for linux and Windows and How I call them in python.

I have edited my code so it would be Minimal, Complete, and Verifiable example, as Antti suggested. I'm quite new to Stack Overflow and didn't understand what it means to make "Minimal, Complete, and Verifiable example" at first. Thanks for the advice and sorry for my ignorance. Now I can reproduce the same problem with my code below.

//header param_header.h
typedef struct MYSTRUCT MYSTRUCT;

struct MYSTRUCT
{
 double param1;
 double param2;
};

//mylib.c this was compiled as an .so(gcc mylib.c -fPIC -shared -o mylib.so) and .dll

#include "param_header.h"
#include <stdio.h>
#ifdef __linux__
int update_param(char *pstruct, char *paramname, double param)
#else
__declspec(dllexport) int update_param(char *pstruct, char *paramname, double param)
#endif
{
 printf("Print this if function runs");
 return 0;
}


//my_c_code.c  --> this compiled again an .so & .dll and called by python ctypes

#include "param_header.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifdef __linux__
#include <dlfcn.h>
#else
#include <windows.h>
#endif

#ifdef __linux__
MYSTRUCT *initfunc(flag, number, params, paramnames)
#else
__declspec(dllexport) MYSTRUCT *initfunc(flag, number, params, paramnames)
#endif
int flag;
int number;
double params[100];
char *paramnames[100];
{
 int index;
 int check;
 MYSTRUCT *pstruct=(MYSTRUCT *)malloc(sizeof(MYSTRUCT));
 memset(pstruct,0,sizeof(MYSTRUCT));
 #ifdef __linux__
 void *pHandle;
 pHandle=dlopen("./mylib.so",RTLD_LAZY);
 int(*update_param)(char*, char*, double) = dlsym(pHandle, "update_param");
 #else
 HINSTANCE pHandle;
 pHandle=LoadLibrary("./mylib.dll");
 int(__cdecl *update_param)(char*,char*, double);
 FARPROC updateparam = GetProcAddress(pHandle, "update_param");
 if (!updateparam)
 {
  check = GetLastError();
  printf("%d\n", check);
 }
 update_param = (int(__cdecl *)(char*, char*, double))updateparam;
 #endif
 for (index=0;index < number;index++) {
  (*update_param)((char*)pstruct, paramnames[index],params[index]); // <--this line fails only for the windows. 
 }
 return pstruct;
}                  

And below is my python code to access the function.

//mystruct.py
from ctypes import *
class MYSTRUCT(Structure):
  _fields_ = [("param1",c_double),
             ("param2",c_double)]
//mypython code
from ctypes import *
from mystruct import *
mydll=cdll.LoadLibrary("./my_c_code.so")#"./my_c_code.dll" for windows.
libhandle=mydll._handle
c_initfunc=mydll.initfunc
c_initfunc.restype=POINTER(MYSTRUCT)
c_initfunc.argtypes=[c_int,c_int,c_double*100,c_char_p*100]
import numpy as np
param_dict={"a":1,"b":2}
params=(c_double * 100)(*np.float_(param_dict.values()))
paramnames=(c_char_p * 100)(*param_dict.keys())    
flag=c_int(1)
number=c_int(len(param_dict.values()))
out=c_initfunc(flag, number, params, paramnames) <-- Error here.

I'm not sure if this is an enough information to debug... but with the combination of above python code and Linux c-code compiled ".so" file. I don't have any problem.. but I get the error for the dll case. Any idea will be appreciated.


Solution

  • After fixing 2 errors in your (Python) code, I was able to successfully run it. Instead of guessing what your error might be (I still think it's a matter of .dll not being found, maybe due to wrong naming), I went the other way around and refactored your code.
    One thing that I want to point out is ctypes page: [Python 3]: ctypes - A foreign function library for Python.

    header.h:

    #if defined(_WIN32)
    #define GENERIC_API __declspec(dllexport)
    #else
    #define GENERIC_API
    #endif
    
    #define PRINT_MSG_0() printf("From C - [%s] (%d) - [%s]\n", __FILE__, __LINE__, __FUNCTION__)
    
    
    typedef struct STRUCT_ {
        double param1;
        double param2;
    } STRUCT;
    

    dll0.c:

    #include "header.h"
    #include <stdio.h>
    
    #define DLL0_API GENERIC_API
    
    
    DLL0_API int updateParam(char *pstruct, char *paramname, double param) {
        PRINT_MSG_0();
        return 0;
    }
    

    dll1.c:

    #include "header.h"
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    #if defined(_WIN32)
    #include <windows.h>
    #else
    #include <dlfcn.h>
    #endif
    
    #define DLL1_API GENERIC_API
    #define UPDATE_PARAM_FUNC_NAME "updateParam"
    
    
    typedef int(__cdecl *UpdateParamFuncPtr)(char*, char*, double);
    
    
    DLL1_API STRUCT *initFunc(flag, number, params, paramnames)
        int flag;
        int number;
        double params[100];
        char *paramnames[100];
    {
        int index = 0;
        UpdateParamFuncPtr updateParam = NULL;
        STRUCT *pStruct = (STRUCT*)malloc(sizeof(STRUCT));
        memset(pStruct, 0, sizeof(STRUCT));
    
    #if defined(_WIN32)
        HMODULE pHandle = LoadLibrary("./dll0.dll");
        if (!pHandle) {
            printf("LoadLibrary failed: %d\n", GetLastError());
            return NULL;
        }
        updateParam = (UpdateParamFuncPtr)GetProcAddress(pHandle, UPDATE_PARAM_FUNC_NAME);
        if (!updateParam) {
            printf("GetProcAddress failed: %d\n", GetLastError());
            FreeLibrary(pHandle);
            return NULL;
        }
    #else
        void *pHandle = dlopen("./dll0.so", RTLD_LAZY);
        if (!pHandle) {
            printf("dlopen failed: %s\n", dlerror());
            return NULL;
        }
        updateParam = dlsym(pHandle, UPDATE_PARAM_FUNC_NAME);
        if (!updateParam) {
            printf("dlsym failed: %s\n", dlerror());
            dlclose(pHandle);
            return NULL;
        }
    #endif
        PRINT_MSG_0();
        for (index = 0; index < number; index++) {
            (*updateParam)((char*)pStruct, paramnames[index], params[index]);
        }
    #if defined(_WIN32)
        FreeLibrary(pHandle);
    #else
        dlclose(pHandle);
    #endif
        return pStruct;
    }
    
    
    DLL1_API void freeStruct(STRUCT *pStruct) {
        free(pStruct);
    }
    

    code.py:

    #!/usr/bin/env python3
    
    import sys
    import traceback
    from ctypes import c_int, c_double, c_char_p, \
        Structure, CDLL, POINTER
    
    
    class Struct(Structure):
        _fields_ = [
            ("param1", c_double),
            ("param2", c_double),
        ]
    
    
    StructPtr = POINTER(Struct)
    
    DoubleArray100 = c_double * 100
    CharPArray100 = c_char_p * 100
    
    
    def main():
        dll1_dll = CDLL("./dll1.dll")
        init_func_func = dll1_dll.initFunc
        init_func_func.argtypes = [c_int, c_int, DoubleArray100, CharPArray100]
        init_func_func.restype = StructPtr
        free_struct_func = dll1_dll.freeStruct
        free_struct_func.argtypes = [StructPtr]
        param_dict = {
            b"a": 1,
            b"b": 2,
        }
        params = DoubleArray100(*param_dict.values())
        paramnames = CharPArray100(*param_dict.keys())
        flag = 1
        number = len(param_dict)
        out = init_func_func(flag, number, params, paramnames)
        print(out)
        try:
            struct_obj = out.contents
            for field_name, _ in struct_obj._fields_:
                print("    {:s}: {:}".format(field_name, getattr(struct_obj, field_name)))
        except:
            traceback.print_exc()
        finally:
            free_struct_func(out)
        print("Done.")
    
    
    if __name__ == "__main__":
        print("Python {:s} on {:s}\n".format(sys.version, sys.platform))
        main()
    

    Notes:

    • Did some refactoring
      • Renames: files, variables, ... (names that start with "My", scratch my brain)
      • Tried to avoid code duplication (this can be improved more) - extracted it in common areas (like files, variables, ...)
      • Other non critical stuff
    • Added a freeStruct function that will deallocate the pointer returned by initFunc, otherwise there will be memory leaks
    • C:
      • Didn't test on Lnx (didn't start the VM), but it should work
      • If something goes wrong (GetProcAddress returns NULL), exit initFunc instead of just printing message and going further. This was a good candidate for the Access Violation
      • Unload the (inner) .dll before exiting initFunc
      • Reversed the Lnx / Win conditionals logic (__linux__ / _WIN32 macro checking) since the dlfcn functions are generic for all Nixes (and if you for example try to build your code on OSX or Solaris (where __linux__ is not defined), it will fall on the Win branch, and obviously fail)
    • Python:
      • Removed np. The code didn't compile with it, and it's absolutely not necessary
      • Changed param_dict keys from str to bytes to match ctypes.c_char_p (as I'm using Python 3)

    Output:

    (py35x64_test) e:\Work\Dev\StackOverflow\q053909121>"c:\Install\x86\Microsoft\Visual Studio Community\2015\vc\vcvarsall.bat" x64
    
    (py35x64_test) e:\Work\Dev\StackOverflow\q053909121>dir /b
    code.py
    dll0.c
    dll1.c
    header.h
    original_code_dir
    
    (py35x64_test) e:\Work\Dev\StackOverflow\q053909121>cl /nologo /DDLL /MD dll0.c  /link /NOLOGO /DLL /OUT:dll0.dll
    dll0.c
       Creating library dll0.lib and object dll0.exp
    
    (py35x64_test) e:\Work\Dev\StackOverflow\q053909121>cl /nologo /DDLL /MD dll1.c  /link /NOLOGO /DLL /OUT:dll1.dll
    dll1.c
       Creating library dll1.lib and object dll1.exp
    
    (py35x64_test) e:\Work\Dev\StackOverflow\q053909121>dir /b
    code.py
    dll0.c
    dll0.dll
    dll0.exp
    dll0.lib
    dll0.obj
    dll1.c
    dll1.dll
    dll1.exp
    dll1.lib
    dll1.obj
    header.h
    original_code_dir
    
    (py35x64_test) e:\Work\Dev\StackOverflow\q053909121>"e:\Work\Dev\VEnvs\py35x64_test\Scripts\python.exe" code.py
    Python 3.5.4 (v3.5.4:3f56838, Aug  8 2017, 02:17:05) [MSC v.1900 64 bit (AMD64)] on win32
    
    From C - [dll1.c] (56) - [initFunc]
    From C - [dll0.c] (9) - [updateParam]
    From C - [dll0.c] (9) - [updateParam]
    <__main__.LP_STRUCT object at 0x000001B2D3AA80C8>
        param1: 0.0
        param2: 0.0
    Done.
    

    Ho! Ho! Hooo! :)