Python | Accessing Dll Using Ctypes

Loading dll using Python Ctypes

Make sure your compiler and version of Python are both 32-bit or both 64-bit. You can't mix, which is the cause of OSError: [WinError 193] %1 is not a valid Win32 application.

Next, make sure to compile as a C program and not C++. That's the cause of the name mangling mention in your answer.

Example (note compiler is for x86 not x64:

C:\>cl /LD /W4 test.c
Microsoft (R) C/C++ Optimizing Compiler Version 17.00.61030 for x86
Copyright (C) Microsoft Corporation. All rights reserved.

test.c
Microsoft (R) Incremental Linker Version 11.00.61030.0
Copyright (C) Microsoft Corporation. All rights reserved.

/out:test.dll
/dll
/implib:test.lib
test.obj
Creating library test.lib and object test.exp

Now use a 32-bit Python:

C:\>py -2
Python 2.7.13 (v2.7.13:a06454b1afa1, Dec 17 2016, 20:42:59) [MSC v.1500 32 bit (Intel)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> from ctypes import *
>>> lib = CDLL('test')
>>> lib.sum(2, 3)
5

If you compile as C++, you can still call functions by exporting them as C, which prevents the C++ name mangling:

test.cpp

extern "C" __declspec(dllexport) int sum(int a, int b) {
return a + b;
}

Accessing DLL function using ctypes

You're on the right track but got a bit confused along the way - taking a code file and giving it a .dll extension isn't enough to turn it into a DLL, you do still need to compile it. Your linker is telling you that it doesn't know how to parse your file:

myfunctionsDLL2.dll : warning LNK4048: Invalid format file; ignored

because it only knows how to parse real executables.


You should rename your second code file (the one you named my_functionsDLL2.dll) back to my_functions.c - and compile it the same way you did the first time. But this time, because you added __declspec(dllexport) to your function definition, your compiler (cl.exe) will know that these functions are meant to be exported from the DLL and will export them, making them available through ctypes.

Import dll using ctypes in virtual environment

So, it turns out the problem was not ctypes, permissions or anything like that: the problem was the virtual environment. in fact there was a problem, not only with my environment, but with the miniconda installation itself. I was not able to reinstall the environment nor to update miniconda. I solved everything by reinstalling miniconda from scratch and recreating the environment. Now the code above works as expected.

Wrapping DLL functions using ctypes

Your problem is that you don't have the namespaces quite right. Its

cppdll.InitNetwork.argtypes = [ctypes.c_char, ctypes.c_char, ctypes.c_int]

You can import the ctypes data you need directly into the module. And since ctypes creates function wrappers for you, you don't really need your own def to do the same thing. You can call them with cppdll.Whatever but if you like having the functions at the namespace level, you can just create variables for them.

from ctypes import WinDLL, c_char, c_int
import os

#load dll
cppdll = ctypes.WinDLL("C:\\VS_projects\\MusicWebService\\MusicWebService\\NetServerInterface.dll")

#initialize network:
InitNetwork = cppdll.InitNetwork
InitNetwork.argtypes = [c_char, c_char, c_int]

#judging if the server is online after network initialization:
GetOnlineStatus = cppdll.GetOnlineStatus

#get song name:
GetMusicSongName = cppdll.GetMusicSongName
GetMusicSongName.argtypes = [c_int, c_int]

#making the device playing music:
PlayServerMusic = cppdll.PlayServerMusic
PlayServerMusic = ...

Python | accessing dll using ctypes

nss3.dll is linked to the following DLLs, which are all located in the Firefox directory: nssutil3.dll, plc4.dll, plds4.dll, nspr4.dll, and mozcrt19.dll. The system library loader looks for these files in the DLL search path of the process, which includes the application directory, system directories, the current directory, and each of the directories listed in the PATH environment variable.

The simplest solution is to change the current directory to the DLL Firefox directory. However, that's not thread safe, so I wouldn't rely on it in general. Another option is to append the Firefox directory to the PATH environment variable, which is what I suggested in my original version of this answer. However, that's not much better than modifying the current directory.

Newer versions of Windows (NT 6.0+ with update KB2533623) allow the DLL search path to be updated in a thread-safe manner via SetDefaultDllDirectories, AddDllDirectory, and RemoveDllDirectory. But that approach would be over the top here.

In this case, for the sake of both simplicity and compatibility with older versions of Windows, it suffices to call LoadLibraryEx with the flag LOAD_WITH_ALTERED_SEARCH_PATH. You need to load the DLL using an absolute path, else the behavior is undefined. For convenience we can subclass ctypes.CDLL and ctypes.WinDLL to call LoadLibraryEx instead of LoadLibrary.

import os
import ctypes

if os.name == 'nt':
from ctypes import wintypes

kernel32 = ctypes.WinDLL('kernel32', use_last_error=True)

def check_bool(result, func, args):
if not result:
raise ctypes.WinError(ctypes.get_last_error())
return args

kernel32.LoadLibraryExW.errcheck = check_bool
kernel32.LoadLibraryExW.restype = wintypes.HMODULE
kernel32.LoadLibraryExW.argtypes = (wintypes.LPCWSTR,
wintypes.HANDLE,
wintypes.DWORD)

class CDLLEx(ctypes.CDLL):
def __init__(self, name, mode=0, handle=None,
use_errno=True, use_last_error=False):
if os.name == 'nt' and handle is None:
handle = kernel32.LoadLibraryExW(name, None, mode)
super(CDLLEx, self).__init__(name, mode, handle,
use_errno, use_last_error)

class WinDLLEx(ctypes.WinDLL):
def __init__(self, name, mode=0, handle=None,
use_errno=False, use_last_error=True):
if os.name == 'nt' and handle is None:
handle = kernel32.LoadLibraryExW(name, None, mode)
super(WinDLLEx, self).__init__(name, mode, handle,
use_errno, use_last_error)

Here are all of the available LoadLibraryEx flags:

DONT_RESOLVE_DLL_REFERENCES         = 0x00000001
LOAD_LIBRARY_AS_DATAFILE = 0x00000002
LOAD_WITH_ALTERED_SEARCH_PATH = 0x00000008
LOAD_IGNORE_CODE_AUTHZ_LEVEL = 0x00000010 # NT 6.1
LOAD_LIBRARY_AS_IMAGE_RESOURCE = 0x00000020 # NT 6.0
LOAD_LIBRARY_AS_DATAFILE_EXCLUSIVE = 0x00000040 # NT 6.0

# These cannot be combined with LOAD_WITH_ALTERED_SEARCH_PATH.
# Install update KB2533623 for NT 6.0 & 6.1.
LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR = 0x00000100
LOAD_LIBRARY_SEARCH_APPLICATION_DIR = 0x00000200
LOAD_LIBRARY_SEARCH_USER_DIRS = 0x00000400
LOAD_LIBRARY_SEARCH_SYSTEM32 = 0x00000800
LOAD_LIBRARY_SEARCH_DEFAULT_DIRS = 0x00001000

For example:

firefox_path = r'F:\Softwares\Mozilla Firefox'
nss3 = CDLLEx(os.path.join(firefox_path, 'nss3.dll'),
LOAD_WITH_ALTERED_SEARCH_PATH)

nss3.NSS_GetVersion.restype = c_char_p

>>> nss3.NSS_GetVersion()
'3.13.5.0 Basic ECC'

Trying to get a response from a dll function using ctypes python

The function returns void, so capturing the return value does nothing. You have created x and y variables to hold the result and need to inspect them after calling the function. Here's a working example with a sample DLL function implementation:

test.c

__declspec(dllexport)
void get_xy_pos(short *xpos, short *ypos) {
*xpos = 5;
*ypos = 7;
}

test.py

import ctypes as ct

dll = ct.CDLL('./test')

# Good practice is to define .argtypes and .restype so ctypes can do type-checking
dll.get_xy_pos.argtypes = ct.POINTER(ct.c_short),ct.POINTER(ct.c_short)
dll.get_xy_pos.restype = None

def get_xy_pos():
x = ct.c_short() # storage for output parameters
y = ct.c_short()
dll.get_xy_pos(ct.byref(x), ct.byref(y)) # pass by reference
return x.value,y.value # inspect the return values

print(get_xy_pos())

Output:

(5, 7)

Pass object to DLL function imported with ctypes

On a 64-bit system, return values default to c_int (32-bit). At a minimum, set the .restype to at least a c_void_p to indicate a 64-bit pointer is returned.

Ideally, set .argtypes and .restype for each function called.

import ctypes

hllDll = ctypes.WinDLL(r"libsie.dll")
hllDll.sie_context_new.argtypes = () # optional but recommended
hllDll.sie_context_new.restype = ctypes.c_void_p # add this
hllDll.sie_context_new.argtypes = ctypes.c_void_p, ctypes.c_char_p # guess, need prototype
# hllDll.sie_context_new.restype = ???

context = hllDll.sie_context_new()
file = hllDll.sie_file_open(context, b"test.sie")

How to use ctypes to call a DLL function with double underline function name?

The function can be called with the following syntax as well, and bypasses the obfuscation Python applies to "dunder" attributes of class instances:

self.windll['__apiJob']()

Example below:

test.cpp

extern "C" __declspec(dllexport)
int __apiJob() {
return 123;
}

test.py

import ctypes

class Job:

def __init__(self):
dll = ctypes.CDLL('./test')
self.apiJob = dll['__apiJob'] # bypass "dunder" class name mangling
self.apiJob.argtypes = ()
self.apiJob.restype = ctypes.c_int

def execute(self):
return self.apiJob()

a = Job()
result = a.execute()
print(result)

Output:

123

As an aside, WinDLL is used for DLLs declaring functions using __stdcall calling convention in 32-bit DLLs. CDLL is used for the default __cdecl calling convention. 64-bit DLLs have only one calling convention, so either works, but for portability keep this in mind.



Related Topics



Leave a reply



Submit