Calling a C function with a char* argument through python ctypes difference python2 and python3

Question:

I am converting a file from python2 to python3 that calls a C function using the ctypes module.
The below minimal example works in python2, however raises the following error in python3 (3.11)

OSError: exception: access violation writing 0x00000000000094E0

// mydll.c
#include <stdio.h>

void myfunc(char* c, int i, char* c2) {
    printf("Hello World");
}

int main() {
    return 0;
}
# foo.py
import ctypes
import sys

PY3 = sys.version_info.major == 3

if PY3:
    clibrary = ctypes.WinDLL("mydll.dll", winmode=1)
else:
    clibrary = ctypes.WinDLL("mydll.dll")

prototype = ctypes.CFUNCTYPE(None, ctypes.c_char_p, ctypes.c_int, ctypes.c_char_p)

c1 = (ctypes.c_char * 512)()
i = ctypes.c_int(0)
c2 = (ctypes.c_char * (600 * 8))()
func = prototype(("myfunc", clibrary))


func(c1, i, c2)

I think this has something to do with unicode vs bytes representation of strings between python versions. From what I gather this looks like dereferencing a null pointer or something of that nature. I’ve tried using ctypes.create_string_buffer() but encounter the same error.

I expect the same code to work in both python2 and python3. What is causing the python3 error?

Asked By: ptatulea

||

Answers:

  • According to [Python.Docs]: ctypes – Loading shared libraries (emphasis is mine):

    The winmode parameter is used on Windows to specify how the library is loaded (since mode is ignored). It takes any value that is valid for the Win32 API LoadLibraryEx flags parameter. When omitted, the default is to use the flags that result in the most secure DLL load to avoiding issues such as DLL hijacking. Passing the full path to the DLL is the safest way to ensure the correct library and dependencies are loaded.

  • Then from [MS.Learn]: LoadLibraryExA function (libloaderapi.h) (DONT_RESOLVE_DLL_REFERENCES0x00000001) (emphasis still mine):

    Note Do not use this value; it is provided only for backward compatibility. If you are planning to access only data or resources in the DLL, use LOAD_LIBRARY_AS_DATAFILE_EXCLUSIVE or LOAD_LIBRARY_AS_IMAGE_RESOURCE or both. Otherwise, load the library as a DLL or executable module using the LoadLibrary function.

So, get rid of winmode (or set it to a supported value), and you should be fine.

Your example (a bit tweaked):

dll00.c:

#include <stdio.h>

#if defined(_WIN32)
#  define DLL00_EXPORT_API __declspec(dllexport)
#else
#  define DLL00_EXPORT_API
#endif


#if defined(__cplusplus)
extern "C" {
#endif

DLL00_EXPORT_API void func(char *pc0, int i, char *pc1);

#if defined(__cplusplus)
}
#endif


void func(char *pc0, int i, char *pc1)
{
    printf("Hello Worldn");
}

code00.py:

#!/usr/bin/env python

import ctypes as cts
import sys


DLL_NAME = "./dll00.{:s}".format("dll" if sys.platform[:3].lower() == "win" else "so")


def main(*argv):
    dll = cts.WinDLL(DLL_NAME)

    prototype = cts.WINFUNCTYPE(None, cts.c_char_p, cts.c_int, cts.c_char_p)
    c0 = (cts.c_char * 512)()
    i = 0
    c1 = (cts.c_char * (600 * 8))()
    func = prototype(("func", dll))

    func(c0, i, c1)


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.n")
    sys.exit(rc)

Output:

(py_pc064_03.11_test0) [cfati@CFATI-5510-0:e:WorkDevStackOverflowq074867788]> sopr.bat
### Set shorter prompt to better fit when pasted in StackOverflow (or other) pages ###

[prompt]> "c:Installpc032MicrosoftVisualStudioCommunity2019VCAuxiliaryBuildvcvarsall.bat" x64 > nul

[prompt]> dir /b
code00.py
dll00.c

[prompt]> cl /nologo /MD /DDLL dll00.c  /link /NOLOGO /DLL /OUT:dll00.dll
dll00.c
   Creating library dll00.lib and object dll00.exp

[prompt]> dir /b
code00.py
dll00.c
dll00.dll
dll00.exp
dll00.lib
dll00.obj

[prompt]>
[prompt]> "e:WorkDevVEnvspy_pc064_02.07.18_test0Scriptspython.exe" ./code00.py
Python 2.7.18 (v2.7.18:8d21aa21f2, Apr 20 2020, 13:25:05) [MSC v.1500 64 bit (AMD64)] 064bit on win32

Hello World

Done.


[prompt]> "e:WorkDevVEnvspy_pc064_03.11_test0Scriptspython.exe" ./code00.py
Python 3.11.0rc1 (main, Aug  8 2022, 11:30:54) [MSC v.1932 64 bit (AMD64)] 064bit on win32

Hello World

Done.

Notes:

Answered By: CristiFati