I am trying to call a Windows API DLL from Python which provides functions to control external hardware. However, this does not work if passing or receiving parameters from any of said functions is involved. The DLL provides a function int DoProberCommand(LPCSTR Command, LPSTR Response)
which returns a zero on success and a non-zero value otherwise. Using Visual Studio C/C++ tools the DLL's documentation suggests using the Windows API functions LoadLibrary
and GetProcAddress
. In C/C++ this worked flawlessly. The follwing code was used:
#include <iostream>
#include <Windows.h>
// typedefs for DLL
typedef int (WINAPI *REGISTERPROBERAPP)(LPCSTR, LPCSTR, UINT);
typedef int (WINAPI *DOPROBERCOMMAND)(LPCSTR, LPCSTR)
int main() {
// Load DLL
HMODULE MsgServeLib = NULL;
MsgServeLib = LoadLibrary("MsgServe.dll");
// Find functions in DLL
REGISTERPROBERAPP RegisterProberApp = (REGISTERPROBERAPP)GetProcAddress(MsgServeLib, "RegisterProberApp");
DOPROBERCOMMAND DoProberCommand = (DOPROBERCOMMAND)GetProcAddress(MsgServeLib, "DoProberCommand");
FARPROC CloseProberApp = GetProcAddress(MsgServeLib, "CloseProberApp");
RegisterProberApp("AppName", "AppName", 1);
char cmdBuf[255] = "StepNextDie";
char resBuf[255];
if (DoProberCommand(cmdBuf, resBuf)) {
std::cout << "Failure" << std:endl;
} else {
std::cout << "Success" << std::endl;
}
// Free Memory
CloseProberApp();
FreeLibrary(MsgServeLib);
MsgServeLib = NULL;
return 0;
}
This works as expected. However, I need a Python interface for the DLL. My first attempt was ctypes:
import ctypes
lib = ctypes.WinDLL(r'Z:\path\to\MsgServe.dll')
_RegisterProberApp = lib.RegisterProberApp
_RegisterProberApp.restype = ctypes.c_int
_RegisterProberApp.argtypes = [ctypes.wintypes.LPCSTR, ctypes.wintypes.LPCSTR, ctypes.c_int]
_CloseProberApp = lib.CloseProberApp
_RegisterProberApp.restype = ctypes.c_int
_DoProberCommand= lib.DoProberCommand
_DoProberCommand.restype = ctypes.c_int
_DoProberCommand.argtypes = [ctypes.wintypes.LPCSTR, ctypes.wintypes.LPCSTR]
_RegisterProberApp("PyApp", "PyApp", 1); # works
_DoProberCommand("StepNextDie",""); # does not work
cmdBuf = ctypes.create_string_buffer("StepNextDie",255)
resBuf = ctypes.create_string_buffer(255)
_DoProberCommand(cmdBuf, resBuf) # does not work
cmdBuf = ctypes.create_unicode_buffer("StepNextDie",255)
resBuf = ctypes.create_unicode_buffer(255)
_DoProberCommand(cmdBuf, resBuf) # does not work
_RegisterProberApp
works fine however _DoProberCommand
always returns a non zero value nor can I observe any changes to my hardware unlike the C/C++ version. I also tried ctypes.c_char_p
and ctypes.c_wchar_p
for the argtypes but this did not work as well. In Visual Studio I had to choose the MultiByte option in the project settings because otherwise my code would not compile. I do not know if ctypes can handle that so I tried building a wrapper DLL around the MsgServe.dll
using the MultiByteToWideChar
function provided by the Windows API . I used the code given here . The C/C++ code reads:
#include <Windows.h>
// defines
#define DLLEXPORT __declspec(dllexport)
// typedefs
typedef int(WINAPI *REGISTERPROBERAPP)(LPCSTR, LPCSTR, UINT);
typedef int(WINAPI *DOPROBERCOMMAND)(LPCSTR, LPSTR);
typedef int(WINAPI *CLOSEPROBERAPP)();
// prototypes
HINSTANCE g_ServiceLib = LoadLibrary((LPCWSTR)"MsgServe.dll");
REGISTERPROBERAPP g_RegisterProberApp = (REGISTERPROBERAPP) GetProcAddress(g_ServiceLib, "RegisterProberApp");
CLOSEPROBERAPP g_CloseProberApp = (CLOSEPROBERAPP) GetProcAddress(g_ServiceLib, "CloseProberApp");
DOPROBERCOMMAND g_DoProberCommand = (DOPROBERCOMMAND) GetProcAddress(g_ServiceLib, "DoProberCommand");
// helper funcs
BOOL MByteToUnicode(LPCSTR multiByteStr, LPWSTR unicodeStr, DWORD size)
{
// Get the required size of the buffer that receives the Unicode string.
DWORD minSize;
minSize = MultiByteToWideChar (CP_ACP, 0, multiByteStr, -1, NULL, 0);
if(size < minSize)
{
return FALSE;
}
// Convert string from multi-byte to Unicode.
MultiByteToWideChar (CP_ACP, 0, multiByteStr, -1, unicodeStr, minSize);
return TRUE;
}
BOOL UnicodeToMByte(LPCWSTR unicodeStr, LPSTR multiByteStr, DWORD size)
{
// Get the required size of the buffer that receives the multiByte string.
DWORD minSize;
minSize = WideCharToMultiByte(CP_OEMCP,NULL,unicodeStr,-1,NULL,0,NULL,FALSE);
if(size < minSize)
{
return FALSE;
}
// Convert string from Unicode to multi-byte.
WideCharToMultiByte(CP_OEMCP,NULL,unicodeStr,-1,multiByteStr,size,NULL,FALSE);
return TRUE;
}
extern "C" {
DLLEXPORT int RegisterProberApp() {
// 0 -> Alle Anwendungen
// 1 -> Lock auf diese Anwendung
return g_RegisterProberApp("PyMsgServe", "PyMsgServe", 1);
}
DLLEXPORT int CloseProberApp() {
return g_CloseProberApp();
}
DLLEXPORT int DoProberCommand(LPCWSTR command, LPWSTR response) {
LPSTR mCommand = NULL;
LPSTR mResponse = NULL;
UnicodeToMByte(command, mCommand, wcslen(command));
int x = g_DoProberCommand(mCommand, mResponse);
MByteToUnicode(mResponse, response, strlen(mResponse));
return x;
}
}
The exported DLL compiles without any flaws but when calling it from Python with ctypes I get an "access violation reading". I also tried using SWIG (the code is basically the same, just wrapped in a class) which gave the some problem as using ctypes without the additional DLL.
Any suggestions on how to resolve this error?
EDIT: Added the code I am using at the time:
File "LPCSTRConvert.py"
import ctypes
from ctypes import wintypes
class Converter:
# Init func
# TODO: Load conversion functions from Kernel32
def __init__(self):
# get functions
self._wideCharToMultiByte = ctypes.windll.kernel32.WideCharToMultiByte
self._multiByteToWideChar = ctypes.windll.kernel32.MultiByteToWideChar
# set return type
self._wideCharToMultiByte.restype = ctypes.c_int
self._multiByteToWideChar.restype = ctypes.c_int
# set argument types
self._wideCharToMultiByte.argtypes = [wintypes.UINT, wintypes.DWORD,
wintypes.LPCWSTR, ctypes.c_int,
wintypes.LPSTR, ctypes.c_int,
wintypes.LPCSTR,
ctypes.POINTER(ctypes.c_long)]
self._multiByteToWideChar.argtypes = [wintypes.UINT, wintypes.DWORD,
wintypes.LPCSTR, ctypes.c_int,
wintypes.LPWSTR, ctypes.c_int]
return
# WideCharToMultiByte
# TODO: Converts a WideChar character set to a MultiByte character set
# WideChar -> UTF8
def WideCharToMultiByte(self, fn):
_CP_UTF8 = 65001 # UTF-8
_CP_ACP = 0 # ANSI
codePage = _CP_UTF8
dwFlags = 0
lpWideCharStr = fn
cchWideChar = len(fn)
lpMultiByteStr = None
cbMultiByte = 0 # zero requests size
lpDefaultChar = None
lpUsedDefaultChar = None
# get size
mbcssize = self._wideCharToMultiByte(codePage,
dwFlags,
lpWideCharStr,
cchWideChar,
lpMultiByteStr,
cbMultiByte,
lpDefaultChar,
lpUsedDefaultChar)
if mbcssize <= 0:
raise WinError(mbcssize)
lpMultiByteStr = ctypes.create_string_buffer(mbcssize)
# convert
retcode = self._wideCharToMultiByte(codePage,
dwFlags,
lpWideCharStr,
cchWideChar,
lpMultiByteStr,
mbcssize,
lpDefaultChar,
lpUsedDefaultChar)
if retcode <= 0:
raise WinError(retcode)
return lpMultiByteStr.value
File "Prober.py"
try:
from LPCSTRConvert import Converter
import ctypes
from ctypes import wintypes
except ImportError:
print("Can't import ctypes")
# import dll
DllPath = r'Z:\path\to\MsgServe.dll'
_msgServe = ctypes.WinDLL(DllPath)
# load functions
_registerProberApp = _msgServe.RegisterProberApp
_closeProberApp = _msgServe.CloseProberApp
_doProberCommand = _msgServe.DoProberCommand
# set return types
_registerProberApp.restype = ctypes.c_int
_doProberCommand.restype = ctypes.c_int
# set arguments
_registerProberApp.argtypes = [wintypes.LPCSTR, wintypes.LPCSTR, wintypes.UINT]
_doProberCommand.argtypes = [wintypes.LPCSTR, wintypes.LPCSTR]
# Registriere Prober
if _registerProberApp('PyApp'.encode('utf-8'),
'PyApp'.encode('utf-8'), 1) == 1:
print("Opened")
else:
print("Error")
conv = Converter()
resBuf = ctypes.create_string_buffer(255)
y = _doProberCommand(conv.WideCharToMultiByte('StepNextDie'),
resBuf)
if y == 0:
print("Works")
else:
print("Error Code: " + str(y))
print("ResBuf: " + str(resBuf.value))
if _closeProberApp() == 1:
print("Exit")
else:
print("Could not exit.")
This raises the output:
Python 3.7.2 (tags/v3.7.2:9a3ffc0492, Dec 23 2018, 22:20:52) [MSC v.1916 32 bit (Intel)]
Type "copyright", "credits" or "license" for more information.
IPython 7.3.0 -- An enhanced Interactive Python.
try:
from LPCSTRConvert import Converter
import ctypes
from ctypes import wintypes
except ImportError:
print("Can't import ctypes")
# import dll
DllPath = r'Z:\path\to\MsgServe.dll'
_msgServe = ctypes.WinDLL(DllPath)
# load functions
_registerProberApp = _msgServe.RegisterProberApp
_closeProberApp = _msgServe.CloseProberApp
_doProberCommand = _msgServe.DoProberCommand
# set return types
_registerProberApp.restype = ctypes.c_int
_doProberCommand.restype = ctypes.c_int
# set arguments
_registerProberApp.argtypes = [wintypes.LPCSTR, wintypes.LPCSTR, wintypes.UINT]
_doProberCommand.argtypes = [wintypes.LPCSTR, wintypes.LPCSTR]
# Registriere Prober
if _registerProberApp('PyApp'.encode('utf-8'),
'PyApp'.encode('utf-8'), 1) == 1:
print("Opened")
else:
print("Error")
conv = Converter()
resBuf = ctypes.create_string_buffer(255)
y = _doProberCommand(conv.WideCharToMultiByte('StepNextDie'),
resBuf)
if y == 0:
print("Works")
else:
print("Error Code: " + str(y))
print("ResBuf: " + str(resBuf.value))
if _closeProberApp() == 1:
print("Exit")
else:
print("Could not exit.")
Opened
Error Code: 509
ResBuf: b': StepNextDie'
Exit
I managed to solve the problem by using an additional DLL as layer between the MsgServe.dll
and Python. According to my online search the problem is that Windows expects a specific character set for a file path (in the LoadLibrary()
function call). Therefore I initially chose the MultiByte-Charset but because of this the LabVIEW-DLL did not work anymore. My solution is the macro _T()
from tchar.h
(see code below) which deals with the conversion. Now everything works as expected.
Layer.dll source:
#include <Windows.h>
#include <stdio.h>
#include <tchar.h>
#define DLLEXPORT __declspec(dllexport)
// typedefs for functions
typedef int (WINAPI *REGISTERPROBERAPP)(LPCSTR, LPCSTR, UINT);
typedef int (WINAPI *DOPROBERCOMMAND)(LPCSTR, LPCSTR);
// MsgServe variables
HMODULE hMsgServe = nullptr;
REGISTERPROBERAPP mRegisterProberApp;
DOPROBERCOMMAND mDoProberCommand;
FARPROC mCloseProberApp;
extern "C" DLLEXPORT void Init() {
hMsgServe = LoadLibrary( _T("MsgServe.dll") );
mRegisterProberApp = (REGISTERPROBERAPP)GetProcAddress(hMsgServe, "RegisterProberApp");
mDoProberCommand = (DOPROBERCOMMAND)GetProcAddress(hMsgServe, "DoProberCommand");
mCloseProberApp = GetProcAddress(hMsgServe, "CloseProberApp");
mRegisterProberApp("PyProb", "PyProb", 1);
}
extern "C" DLLEXPORT void Quit() {
mCloseProberApp();
FreeLibrary(hMsgServe);
hMsgServe = nullptr;
}
extern "C" DLLEXPORT void DoProberCommand(char* Cmd) {
// buffers
char CmdBuf[256];
char ResBuf[256];
// store command
sprintf_s(CmdBuf, Cmd);
mDoProberCommand(CmdBuf, ResBuf);
}
Python source:
import ctypes
hProberBench = ctypes.WinDLL(r"path\to\Layer.dll")
_Init = hProberBench.Init
_Quit = hProberBench.Quit
_DoProberCommand = hProberBench.DoProberCommand
_Init.argtypes = None
_Init.restype = None
_Quit.argtypes = None
_Quit.restype = None
_DoProberCommand.argtypes = [ctypes.c_char_p]
_DoProberCommand.restype = None
_Init()
_DoProberCommand("StepNextDie".encode('utf-8'))
_Quit()
Thank you @CristiFati for your help.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.