【问题标题】:ctypes wintypes WCHAR String Additional White Spacesctypes wintypes WCHAR 字符串 附加空格
【发布时间】:2019-06-13 15:55:26
【问题描述】:

为什么后面的每个字符后面都有一个空格?

C++ DLL

test.h:

#ifndef TEST_DLL_H
#define TEST_DLL_H
#define EXPORT __declspec(dllexport) __stdcall 

#include <iostream>
#include <Windows.h>

namespace Test_DLL
{
    struct Simple
    {
        TCHAR a[1024];
    };

    extern "C"
    {
        int EXPORT simple(Simple* a);
    }
};

#endif

test.cpp:

#include "test.h"

int EXPORT Test_DLL::simple(Simple* a)
{
    std::wcout << a->a << std::endl;

    return 0;
}

Python

test.py:

import ctypes
from ctypes import wintypes


class MyStructure(ctypes.Structure):
    _fields_ = [("a", wintypes.WCHAR * 1024)]


a = "Hello, world!"
hDLL = ctypes.LibraryLoader(ctypes.WinDLL)
hDLL_Test = hDLL.LoadLibrary(r"...\test.dll")
simple = hDLL_Test.simple
mystruct = MyStructure(a=a)
ret = simple(ctypes.byref(mystruct))

结果:

H e l l o ,   w o r l d ! 

问题出在 C++ DLL 方面吗?还是我在 Python 方面遗漏了什么?

【问题讨论】:

  • TCHAR a[1024] -- TCHAR 不保证是宽字符类型。您是否使用 Unicode 作为字符集构建了您的 DLL?或者更好的是,只需在前面声明 wchar_t 并跳过 TCHAR 的内容。
  • 是的,DLL 是使用 Unicode 字符集构建的。我用wchar_t 替换了TCHAR,但没有区别。我正在使用TCHAR,因为我最终必须使用的实际 DLL 在其文档中具有该类型。我的任务是使用别人开发的DLL,所以我无法更改C++源代码。我决定从我自己的简单示例开始,看看事情是如何运作的(我是ctypes 的新手)。但我不喜欢在这个小例子中看到这些额外的空白,因为当我使用实际的 DLL 时可能会发生同样的情况。
  • 通过查看字节序列的确切内容来检查您正在打印的字符串。由于打印功能,空格没有放在那里。字符串本身具有某种字节序列,其中第一个字节是 ASCII,第二个字节是 NULL 或其他字符(设置中 ASCII 和 Unicode/宽字符之间某种错误通信的所有标记)。
  • 奇怪...我将打印部分更改为std::wcout &lt;&lt; p-&gt;m_szExeDir[0] &lt;&lt; p-&gt;m_szExeDir[1] &lt;&lt; std::endl;,结果是H e 。所以前两个字符似乎是H 和e,但是显示的结果有额外的空格。这是您通过检查字符串所建议的吗?

标签: c++ python-3.x dll ctypes wchar-t


【解决方案1】:

一开始我认为这是您的代码中的一些小问题。调试时我发现情况并非如此。从您的示例开始,我开发了另一个说明一些关键点的示例。

test.h:

#if !defined(TEST_DLL_H)
#define TEST_DLL_H


#if defined(_WIN32)
#  if defined(TEST_EXPORTS)
#    define TEST_API __declspec(dllexport)
#  else
#    define TEST_API __declspec(dllimport)
#  endif
#  define CALLING_CONVENTION __cdecl
#else
#  define __TEXT(X) L##X
#  define TEXT(X) __TEXT(X)
#  define TEST_API
#  define CALLING_CONVENTION
#endif


namespace TestDll {
    typedef struct Simple_ {
        wchar_t a[1024];
    } Simple;

    extern "C" {
        TEST_API int CALLING_CONVENTION simple(Simple *pSimple);
        TEST_API int CALLING_CONVENTION printStr(char *pStr);
        TEST_API int CALLING_CONVENTION wprintWstr(wchar_t *pWstr);
        TEST_API wchar_t* CALLING_CONVENTION wstr();
        TEST_API void CALLING_CONVENTION clearWstr(wchar_t *pWstr);
    }
};

#endif  // TEST_DLL_H

test.cpp:

#define TEST_EXPORTS
#include "test.h"
#if defined(_WIN32)
#  include <Windows.h>
#else
#  include <wchar.h>
#  define __FUNCTION__ "function"
#endif
#include <stdio.h>
//#include <iostream>

#define PRINT_MSG_0() printf("From C: - [%s] (%d) - [%s]\n", __FILE__, __LINE__, __FUNCTION__)
#define WPRINT_MSG_0() wprintf(L"From C: - [%s] (%d) - [%s]\n", TEXT(__FILE__), __LINE__, TEXT(__FUNCTION__))

#define DUMMY_TEXT_W L"Dummy text."


//using namespace std;


int TestDll::simple(Simple *pSimple) {
    //std::wcout << pSimple->a << std::endl;
    WPRINT_MSG_0();
    int ret = wprintf(L"%s", pSimple->a);
    wprintf(L"\n");
    return ret;
}


int TestDll::printStr(char *pStr) {
    PRINT_MSG_0();
    int ret = printf("%s", pStr);
    printf("\n");
    return ret;
}


int TestDll::wprintWstr(wchar_t *pWstr) {
    WPRINT_MSG_0();
    int ret = wprintf(L"%s", pWstr);
    wprintf(L"\n");
    int len = wcslen(pWstr);
    char *buf = (char*)pWstr;
    wprintf(L"Hex (%d): ", len);
    for (int i = 0; i < len * sizeof(wchar_t); i++)
        wprintf(L"%02X ", buf[i]);
    wprintf(L"\n");
    return ret;
}


wchar_t *TestDll::wstr() {
    wchar_t *ret = (wchar_t*)malloc((wcslen(DUMMY_TEXT_W) + 1) * sizeof(wchar_t));
    wcscpy(ret, DUMMY_TEXT_W);
    return ret;
}


void TestDll::clearWstr(wchar_t *pWstr) {
    free(pWstr);
}

main.cpp:

#include "test.h"
#include <stdio.h>
#if defined(_WIN32)
#  include <Windows.h>
#endif


int main() {
    char *text = "Hello, world!";
    TestDll::Simple s = { TEXT("Hello, world!") };
    int ret = simple(&s);  // ??? Compiles even if namespace not specified here !!!
    printf("\"simple\" returned %d\n", ret);
    ret = TestDll::printStr("Hello, world!");
    printf("\"printStr\" returned %d\n", ret);
    ret = TestDll::wprintWstr(s.a);
    printf("\"wprintWstr\" returned %d\n", ret);
    return 0;
}

code.py:

#!/usr/bin/env python3

import sys
import ctypes


DLL_NMAME = "./test.dll"
DUMMY_TEXT = "Hello, world!"


WCharArr1024 = ctypes.c_wchar * 1024

class SimpleStruct(ctypes.Structure):
    _fields_ = [
        ("a", WCharArr1024),
    ]


def main():

    test_dll = ctypes.CDLL(DLL_NMAME)

    simple_func = test_dll.simple
    simple_func.argtypes = [ctypes.POINTER(SimpleStruct)]
    simple_func.restype = ctypes.c_int
    stuct_obj = SimpleStruct(a=DUMMY_TEXT)

    print_str_func = test_dll.printStr
    print_str_func.argtypes = [ctypes.c_char_p]
    print_str_func.restype = ctypes.c_int

    wprint_wstr_func = test_dll.wprintWstr
    wprint_wstr_func.argtypes = [ctypes.c_wchar_p]
    wprint_wstr_func.restype = ctypes.c_int

    wstr_func = test_dll.wstr
    wstr_func.argtypes = []
    wstr_func.restype = ctypes.c_wchar_p

    clear_wstr_func = test_dll.clearWstr
    clear_wstr_func.argtypes = [ctypes.c_wchar_p]
    clear_wstr_func.restype = None

    #print("From PY: [{:s}]".format(stuct_obj.a))
    ret = simple_func(ctypes.byref(stuct_obj))
    print("\"{:s}\" returned {:d}".format(simple_func.__name__, ret))
    ret = print_str_func(DUMMY_TEXT.encode())
    print("\"{:s}\" returned {:d}".format(print_str_func.__name__, ret))
    #ret = wprint_wstr_func(ctypes.cast(DUMMY_TEXT.encode(), ctypes.c_wchar_p))
    ret = wprint_wstr_func(DUMMY_TEXT)
    print("\"{:s}\" returned {:d}".format(wprint_wstr_func.__name__, ret))
    s = wstr_func()
    print("\"{:s}\" returned \"{:s}\"".format(wstr_func.__name__, s))
    #clear_wstr_func(s)


if __name__ == "__main__":
    #print("Python {:s} on {:s}\n".format(sys.version, sys.platform))
    main()

变化:

  • 移除了 C++ 层(以排除尽可能多的变量)并且仅依赖于 C
  • 修改了代码以兼容 Nix(我已经在 Ubtu 上运行过它,但遇到了其他问题,我不打算讨论)
  • 添加了更多功能(这是一个调试过程),以收集尽可能多的情报
  • 进行了一些重命名、重构和其他不重要的更改
  • 在调查时,我发现了一个有趣的问题(来自 main.cpp 的评论)。显然 simple 函数编译即使我没有预先声明它的命名空间。这不适用于其他函数。经过一些快速尝试,我意识到这是因为 Simple 参数(可能是因为它也是命名空间的一部分?)。无论如何,没有花太多时间,也没有深入了解它(还),可能是 Undefined Behavior (它只是因为愚蠢的运气才起作用)
  • narrow 和wide 函数混用,这是一个NO - NO,仅用于调试/演示目的

输出:

e:\Work\Dev\StackOverflow\q054269984>"c:\Install\x86\Microsoft\Visual Studio Community\2015\vc\vcvarsall.bat" x64

e:\Work\Dev\StackOverflow\q054269984>dir /b
code.py
main.cpp
test.cpp
test.h

e:\Work\Dev\StackOverflow\q054269984>cl /nologo /DDLL /DUNICODE /MD /EHsc test.cpp  /link /NOLOGO /DLL /OUT:test.dll
test.cpp
   Creating library test.lib and object test.exp

e:\Work\Dev\StackOverflow\q054269984>cl /nologo /DUNICODE /MD /EHsc main.cpp  /link /NOLOGO /OUT:main.exe test.lib
main.cpp

e:\Work\Dev\StackOverflow\q054269984>dir /b
code.py
main.cpp
main.exe
main.obj
test.cpp
test.dll
test.exp
test.h
test.lib
test.obj

e:\Work\Dev\StackOverflow\q054269984>main.exe
From C: - [test.cpp] (23) - [TestDll::simple]
Hello, world!
"simple" returned 13
From C: - [test.cpp] (31) - [TestDll::printStr]
Hello, world!
"printStr" returned 13
From C: - [test.cpp] (39) - [TestDll::wprintWstr]
Hello, world!
Hex (13): 48 00 65 00 6C 00 6C 00 6F 00 2C 00 20 00 77 00 6F 00 72 00 6C 00 64 00 21 00
"wprintWstr" returned 13

e:\Work\Dev\StackOverflow\q054269984>"e:\Work\Dev\VEnvs\py_064_03.06.08_test0\Scripts\python.exe" code.py
Python 3.6.8 (tags/v3.6.8:3c6b436a57, Dec 24 2018, 00:16:47) [MSC v.1916 64 bit (AMD64)] on win32

F r o m   C :   -   [ t e s t . c p p ]   ( 2 3 )   -   [ T e s t D l l : : s i m p l e ]
 H e l l o ,   w o r l d !
 "simple" returned 13
From C: - [test.cpp] (31) - [TestDll::printStr]
Hello, world!
"printStr" returned 13
F r o m   C :   -   [ t e s t . c p p ]   ( 3 9 )   -   [ T e s t D l l : : w p r i n t W s t r ]
 H e l l o ,   w o r l d !
 H e x   ( 1 3 ) :   4 8   0 0   6 5   0 0   6 C   0 0   6 C   0 0   6 F   0 0   2 C   0 0   2 0   0 0   7 7   0 0   6 F   0 0   7 2   0 0   6 C   0 0   6 4   0 0   2 1   0 0
 "wprintWstr" returned 13
"wstr" returned "Dummy text."
  • 好像是Python相关
  • 字符串本身并没有弄乱(它们的长度和 wprintf 返回值是正确的)。这更像是 stdout 是罪魁祸首

然后,我更进一步:

e:\Work\Dev\StackOverflow\q054269984>for /f %f in ('dir /b "e:\Work\Dev\VEnvs\py_064*"') do ("e:\Work\Dev\VEnvs\%f\Scripts\python.exe" code.py)

e:\Work\Dev\StackOverflow\q054269984>("e:\Work\Dev\VEnvs\py_064_02.07.15_test0\Scripts\python.exe" code.py )
Python 2.7.15 (v2.7.15:ca079a3ea3, Apr 30 2018, 16:30:26) [MSC v.1500 64 bit (AMD64)] on win32

From C: - [test.cpp] (23) - [TestDll::simple]
Hello, world!
"simple" returned 13
From C: - [test.cpp] (31) - [TestDll::printStr]
Hello, world!
"printStr" returned 13
From C: - [test.cpp] (39) - [TestDll::wprintWstr]
Hello, world!
Hex (13): 48 00 65 00 6C 00 6C 00 6F 00 2C 00 20 00 77 00 6F 00 72 00 6C 00 64 00 21 00
"wprintWstr" returned 13
"wstr" returned "Dummy text."

e:\Work\Dev\StackOverflow\q054269984>("e:\Work\Dev\VEnvs\py_064_03.04.04_test0\Scripts\python.exe" code.py )
Python 3.4.4 (v3.4.4:737efcadf5a6, Dec 20 2015, 20:20:57) [MSC v.1600 64 bit (AMD64)] on win32

From C: - [test.cpp] (23) - [TestDll::simple]
Hello, world!
"simple" returned 13
From C: - [test.cpp] (31) - [TestDll::printStr]
Hello, world!
"printStr" returned 13
From C: - [test.cpp] (39) - [TestDll::wprintWstr]
Hello, world!
Hex (13): 48 00 65 00 6C 00 6C 00 6F 00 2C 00 20 00 77 00 6F 00 72 00 6C 00 64 00 21 00
"wprintWstr" returned 13
"wstr" returned "Dummy text."

e:\Work\Dev\StackOverflow\q054269984>("e:\Work\Dev\VEnvs\py_064_03.05.04_test0\Scripts\python.exe" code.py )
Python 3.5.4 (v3.5.4:3f56838, Aug  8 2017, 02:17:05) [MSC v.1900 64 bit (AMD64)] on win32

F r o m   C :   -   [ t e s t . c p p ]   ( 2 3 )   -   [ T e s t D l l : : s i m p l e ]
 H e l l o ,   w o r l d !
 "simple" returned 13
From C: - [test.cpp] (31) - [TestDll::printStr]
Hello, world!
"printStr" returned 13
F r o m   C :   -   [ t e s t . c p p ]   ( 3 9 )   -   [ T e s t D l l : : w p r i n t W s t r ]
 H e l l o ,   w o r l d !
 H e x   ( 1 3 ) :   4 8   0 0   6 5   0 0   6 C   0 0   6 C   0 0   6 F   0 0   2 C   0 0   2 0   0 0   7 7   0 0   6 F   0 0   7 2   0 0   6 C   0 0   6 4   0 0   2 1   0 0
 "wprintWstr" returned 13
"wstr" returned "Dummy text."

e:\Work\Dev\StackOverflow\q054269984>("e:\Work\Dev\VEnvs\py_064_03.06.08_test0\Scripts\python.exe" code.py )
Python 3.6.8 (tags/v3.6.8:3c6b436a57, Dec 24 2018, 00:16:47) [MSC v.1916 64 bit (AMD64)] on win32

F r o m   C :   -   [ t e s t . c p p ]   ( 2 3 )   -   [ T e s t D l l : : s i m p l e ]
 H e l l o ,   w o r l d !
 "simple" returned 13
From C: - [test.cpp] (31) - [TestDll::printStr]
Hello, world!
"printStr" returned 13
F r o m   C :   -   [ t e s t . c p p ]   ( 3 9 )   -   [ T e s t D l l : : w p r i n t W s t r ]
 H e l l o ,   w o r l d !
 H e x   ( 1 3 ) :   4 8   0 0   6 5   0 0   6 C   0 0   6 C   0 0   6 F   0 0   2 C   0 0   2 0   0 0   7 7   0 0   6 F   0 0   7 2   0 0   6 C   0 0   6 4   0 0   2 1   0 0
 "wprintWstr" returned 13
"wstr" returned "Dummy text."

e:\Work\Dev\StackOverflow\q054269984>("e:\Work\Dev\VEnvs\py_064_03.07.02_test0\Scripts\python.exe" code.py )
Python 3.7.2 (tags/v3.7.2:9a3ffc0492, Dec 23 2018, 23:09:28) [MSC v.1916 64 bit (AMD64)] on win32

F r o m   C :   -   [ t e s t . c p p ]   ( 2 3 )   -   [ T e s t D l l : : s i m p l e ]
 H e l l o ,   w o r l d !
 "simple" returned 13
From C: - [test.cpp] (31) - [TestDll::printStr]
Hello, world!
"printStr" returned 13
F r o m   C :   -   [ t e s t . c p p ]   ( 3 9 )   -   [ T e s t D l l : : w p r i n t W s t r ]
 H e l l o ,   w o r l d !
 H e x   ( 1 3 ) :   4 8   0 0   6 5   0 0   6 C   0 0   6 C   0 0   6 F   0 0   2 C   0 0   2 0   0 0   7 7   0 0   6 F   0 0   7 2   0 0   6 C   0 0   6 4   0 0   2 1   0 0
 "wprintWstr" returned 13
"wstr" returned "Dummy text."

正如所见,从 Python 3.5 开始,该行为是可重现的。

我以为是因为[Python]: PEP 529 -- Change Windows filesystem encoding to UTF-8,但这仅适用于版本3.6。

然后我开始阅读,(我什至尝试在 Python 3.4 和 Python 3.5 之间进行区分)但没有多大成功。我看过的一些文章:

然后我注意到[SO]: Output unicode strings in Windows console app (@DuckMaestro's answer)并开始玩[MS.Docs]: _setmode。

添加:

#include <io.h>
#include <fcntl.h>


static int set_stdout_mode(int mode) {
    fflush(stdout);
    int ret = _setmode(_fileno(stdout), mode);
    return ret;
}

并在 test.cpp 中像 int stdout_mode = set_stdout_mode(_O_TEXT); 一样调用它,然后从 C 输出任何内容(和 C++ :std::wcout 行未注释),产生:

e:\Work\Dev\StackOverflow\q054269984>"e:\Work\Dev\VEnvs\py_064_03.06.08_test0\Scripts\python.exe" code.py
Python 3.6.8 (tags/v3.6.8:3c6b436a57, Dec 24 2018, 00:16:47) [MSC v.1916 64 bit (AMD64)] on win32

Hello, world!
From C: - [test.cpp] (32) - [TestDll::simple]
Hello, world!
"simple" returned 13
From C: - [test.cpp] (40) - [TestDll::printStr]
Hello, world!
"printStr" returned 13
From C: - [test.cpp] (48) - [TestDll::wprintWstr]
Hello, world!
Hex (13): 48 00 65 00 6C 00 6C 00 6F 00 2C 00 20 00 77 00 6F 00 72 00 6C 00 64 00 21 00
"wprintWstr" returned 13
"wstr" returned "Dummy text."
  • 虽然它有效,但我不知道为什么。可能是未定义的行为
    • 打印 _setmode 的返回值,显示 Python 3.4 以及 main.exe 自动将模式设置为 _O_TEXT (0x4000),而较新的 Python 版本(那些不起作用)将其设置为_O_BINARY (0x8000) - 这显然 似乎是原因(可能是相关:[Python]: Issue #16587 - Py_Initialize breaks wprintf on Windows)
    • 尝试将其设置为任何广泛相关的常量(_O_U16TEXT、_O_U8TEXT、_O_WTEXT)在调用 时会导致程序崩溃printf 或std::cout(即使在使用宽功能完成后恢复原始模式 - 在窄功能之前)
  • 尝试输出真正的 Unicode 字符,但不起作用(很可能)
  • 您可以在 Python 方面实现相同的目标:msvcrt.setmode(sys.stdout.fileno(), 0x4000)

【讨论】:

  • 哦哇...太疯狂了!非常感谢所有这些调查工作!我认为这很简单!所以我想答案似乎是,如果我错了,请纠正我,没有明确的解释/解决这个问题。实际上,我的情况有所好转,因为我得到了一个与我需要调用的最终函数交互的 C++ 代码。所以我不再需要从 Python 传递wchar_t,因为我创建了另一个层(在 C++ 和一个 DLL 中),它位于 Python 和另一个 DLL 中的目标函数之间。 :)
  • 可能有一个更简单的选择,我只是没有它:)。肯定是有解释的。正如我在最后第三个项目符号中所写,Pyton 3.5+ 将 stdout 的模式设置为 _O_BINARY,而较旧的和 main.exe 将其设置为 _O_TEXT。显然对于 _O_TEXT 它有效,或者这就是它有效的原因。设置不是显式调用函数,只是默认设置的值。
  • 我很困惑,因为提问者说WriteConsoleW(...L"abc"...) 在 C++ 中不起作用。这与 Python 完全无关,只是控制台窗口属于 Python。也许 Python 正在做一些 C++ 不喜欢的控制台初始化。这或许可以解释为什么 _setmode 会修复它。顺便问一下,你有哪个 Windows 版本?
  • @BarmakShemirani:WriteConsoleW 直到现在才出现。 wcout 是否在幕后调用它(我在这方面的知识相当有限)? ?这也是我的观点,较新的 Python 版本为控制台设置了二进制模式(还没有把我的手指放在它上面)并作为效果 C++ (以及 C) 输出垃圾。但我不知道为什么,对我来说也没有意义。我在 Win 10 上运行。
  • 实际上,Visual Studio 对std::wcout 的实现似乎使用WriteFile,而不是WriteConsoleW。我认为您对_setmode 的最初假设是有道理的。
猜你喜欢
  • 1970-01-01
  • 2018-12-02
  • 1970-01-01
  • 2020-04-01
  • 1970-01-01
  • 2015-09-21
  • 2014-03-18
  • 2022-01-21
  • 2016-10-28
相关资源
最近更新 更多