首页 > 解决方案 > 从 Python 调用 LabVIEW/Windows API DLL

问题描述

我正在尝试从 Python 调用一个 Windows API DLL,它提供了控制外部硬件的功能。但是,如果涉及从任何所述函数传递或接收参数,则这不起作用。DLL 提供了一个函数int DoProberCommand(LPCSTR Command, LPSTR Response),成功时返回零,否则返回非零值。使用 Visual Studio C/C++ 工具,DLL 的文档建议使用 Windows API 函数LoadLibraryGetProcAddress. 在 C/C++ 中,这完美无缺。使用了以下代码:

#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;
}

这按预期工作。但是,我需要一个用于 DLL 的 Python 接口。我的第一次尝试是 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工作正常但是_DoProberCommand总是返回一个非零值,我也不能观察到我的硬件与 C/C++ 版本不同的任何变化。我也尝试ctypes.c_char_pctypes.c_wchar_pargtypes 但这也不起作用。在 Visual Studio 中,我必须在项目设置中选择 MultiByte 选项,否则我的代码将无法编译。我不知道 ctypes 是否可以处理这个问题,所以我尝试MsgServe.dll使用Windows APIMultiByteToWideChar提供的函数构建一个包装 DLL 。我使用了这里给出的代码。C/C++ 代码如下:

#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;
    }

}

导出的 DLL 编译没有任何缺陷,但是当使用 ctypes 从 Python 调用它时,我得到“访问冲突读取”。我还尝试使用 SWIG(代码基本相同,只是包装在一个类中),这在使用 ctypes 而没有额外的 DLL 时出现了一些问题。

有关如何解决此错误的任何建议?

编辑:添加了我当时使用的代码:

文件“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

文件“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.")

这提高了输出:

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

标签: pythonwindowsvisual-c++ctypes

解决方案


我设法通过使用一个额外的 DLL 作为MsgServe.dll和 Python 之间的层来解决这个问题。根据我的在线搜索,问题是 Windows 需要文件路径的特定字符集(在LoadLibrary()函数调用中)。因此,我最初选择了 MultiByte-Charset,但由于这个原因,LabVIEW-DLL 不再起作用。我的解决方案是处理转换的宏_T()tchar.h见下面的代码)。现在一切都按预期工作。

Layer.dll 来源:

#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源码:

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()

感谢@CristiFati 的帮助。


推荐阅读