简体   繁体   中英

Call LabVIEW/Windows API DLL from Python

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM