簡體   English   中英

從 Python 調用 LabVIEW/Windows API DLL

[英]Call LabVIEW/Windows API DLL from Python

我正在嘗試從 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++ 版本不同的硬件的任何變化。 我還為 argtypes 嘗試了ctypes.c_char_pctypes.c_wchar_p ,但這並沒有奏效。 在 Visual Studio 中,我必須在項目設置中選擇 MultiByte 選項,否則我的代碼將無法編譯。 我不知道 ctypes 是否可以處理,所以我嘗試使用Windows API提供的MultiByteToWideChar函數圍繞MsgServe.dll構建一個包裝 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

我設法通過使用一個額外的 DLL 作為MsgServe.dll和 Python 之間的層來解決這個問題。 根據我的在線搜索,問題是 Windows 需要文件路徑的特定字符集(在LoadLibrary()函數調用中)。 因此,我最初選擇了 MultiByte-Charset,但因此 LabVIEW-DLL 不再起作用。 我的解決方案是tchar.h (見下面的代碼)中的宏_T()處理轉換。 現在一切都按預期進行。

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

蟒蛇源:

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 的幫助。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM