python - 从 Python 调用 LabVIEW/Windows API DLL
问题描述
我正在尝试从 Python 调用一个 Windows API DLL,它提供了控制外部硬件的功能。但是,如果涉及从任何所述函数传递或接收参数,则这不起作用。DLL 提供了一个函数int DoProberCommand(LPCSTR Command, LPSTR Response)
,成功时返回零,否则返回非零值。使用 Visual Studio C/C++ 工具,DLL 的文档建议使用 Windows API 函数LoadLibrary
和GetProcAddress
. 在 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_p
了ctypes.c_wchar_p
argtypes 但这也不起作用。在 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
解决方案
我设法通过使用一个额外的 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 的帮助。
推荐阅读
- linux - Linux shell 选择目录和子目录中的文件
- eloquent - 如何通过 laravel 关系过滤
- django-rest-framework - 使用序列化器将 json/xml 转换为 json
- ruby-on-rails - 如何在 Ruby 上读取使用名为 rails-env-credentials 的 gem 创建的加密文件?
- typescript - 使用组件属性的 Mixin(Vue 2、TypeScript)
- r - 使用 ls 将对象获取到向量的代码在函数中不起作用。否则工作
- nginx - NGINX 用户友好的 url,无需更改浏览器中的路径
- sql-server - 如何识别 SQL 数据库中的锁定查询
- navigation - navigator.push 不导航到下一个屏幕
- c# - 如何在 Xamarin 项目中使用 CloudFunctions