PInvoke 使用 wchar16_t 参数调用函数

Posted

技术标签:

【中文标题】PInvoke 使用 wchar16_t 参数调用函数【英文标题】:PInvoke calling of function with wchar16_t parameters 【发布时间】:2016-02-05 10:51:49 【问题描述】:

我有一个带有 C++ 函数的 .dll,它采用 const wchar16_t* 参数。 我正在尝试使用字符串、char 数组和带有附加 '\0' char 的 char 数组在 c# 中导入和使用它,但我没有得到任何结果。 当我在调试模式下在原始 c++ 程序中检查它时,它最后有额外的 '\0' 字符。我应该使用什么确切类型?

附:我不能 100% 确定这些参数会出现问题。 如果有人可以查看我attach 说明问题的小项目,我将非常感激并给予很多积分。 C++ 程序工作正常(我们得到登录响应),但在 c# 项目中 OnLoginResponseCallback 永远不会触发。

我在 C# 中所做的事情

[DllImport("ActiveTickServerAPI.dll", CharSet = CharSet.Unicode, EntryPoint = "?ATCreateLoginRequest@@YA_K_KPB_W1P6AX00PAU_ATLOGIN_RESPONSE@@@Z@Z", ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
        private static extern ulong ATCreateLoginRequest(ulong sessionId, string user, string pwd, ATLoginResponseCallback onLoginResponse);

 public delegate void ATLoginResponseCallback(ulong hSession, ulong hRequest, ATLOGINRESPONSE response);
        public delegate void ATRequestTimeoutCallback(ulong origRequest);
 static void Main(string[] args)
    
 lastRequest = ATCreateLoginRequest(hSession, userId, pw, OnLoginResponseCallback); 
                bool rc = ATSendRequest(hSession, lastRequest, 3000, OnRequestTimeoutCallback);
    
 static void OnLoginResponseCallback(ulong hSession, ulong hRequest, ATLOGINRESPONSE response)
        
            Console.WriteLine("!THIS SHOULD FIRE, but fire only timeout callback");
        

 [StructLayout(LayoutKind.Sequential)]
 public struct ATLOGINRESPONSE
    
        public byte loginResponse;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 255)]
        public byte[] permissions;
        public ATTIME serverTime;
    

[StructLayout(LayoutKind.Sequential)]
public struct ATTIME

    public ushort year;
    public ushort month;
    public ushort dayOfWeek;
    public ushort day;
    public ushort hour;
    public ushort minute;
    public ushort second;
    public ushort milliseconds;

在 c++ 中它是有效的(获取登录响应)。这是来自 api 文档的功能描述:

ACTIVETICKSERVERAPI_API uint64_t ATCreateLoginRequest   (   uint64_t    session,
    const wchar16_t *   userid,
    const wchar16_t *   password,
    ATLoginResponseCallback     pCallback 
)   

来自 c++ 的工作结构

typedef struct _ATLOGIN_RESPONSE

    ATLoginResponseType loginResponse;
    uint8_t permissions[255];
    ATTIME serverTime;
 ATLOGIN_RESPONSE, *LPATLOGIN_RESPONSE;

    typedef struct _ATTIME

    uint16_t year;
    uint16_t month;
    uint16_t dayOfWeek;
    uint16_t day;
    uint16_t hour;
    uint16_t minute;
    uint16_t second;
    uint16_t milliseconds;
 ATTIME, *LPATTIME;

【问题讨论】:

或许***.com/a/6089416/696034会给你答案? 不是我的情况,你关于 out 参数的链接。 这可能是帖子中的拼写错误,但空终止符是\0,而不是/0 是的,谢谢 请在问题中发布minimal reproducible example。我敢肯定,这很容易回答,但谁想下载外部项目? 【参考方案1】:

我下载了您的测试项目并立即修复了委托声明,因为它们是错误的:

    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    public delegate void ATSessionStatusChangeCallback(ulong hSession, byte statusTyp);

    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    public delegate void ATLoginResponseCallback(ulong hSession, ulong hRequest, ref ATLOGINRESPONSE response);

    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    public delegate void ATRequestTimeoutCallback(ulong origRequest);

我第一次运行我得到的程序:

SessionStatus - Connected
request timeout
!THIS SHOULD FIRE
SessionStatus - Connected
!THIS SHOULD FIRE

我第二次及以后的时间:

SessionStatus - Connected
!THIS SHOULD FIRE

没有超时。为什么它会像第一次那样表现,很难猜测。您可能需要等待 ATSendRequest() 直到您确认正确登录,类似这样。

但显然修复了代表解决了问题,您现在在回调中获得了正确的 hSession 值。它还不完美,您需要确保它们不会被垃圾收集。将它们存储在静态变量中:

static ATSessionStatusChangeCallback statusCallback;
....
    if (res == true) 
        statusCallback = new ATSessionStatusChangeCallback(OnSessionStatusChangeCallback);
        res = ATInitSession(sessionNumber, "activetick1.activetick.com",
                 "activetick2.activetick.com", 443, statusCallback, true);
    

对另外两个做同样的事情。

【讨论】:

谢谢汉斯,你太棒了!我将能够在 5 小时内奖励赏金。虽然它变得比我想象的要复杂。在这个回调的时间结构和 loginDenied 类型响应中得到了一些疯狂的值,这意味着用户 ATGUID 是错误的,尽管我只是复制了所需的值。它必须是来回翻译结构的东西。我应该深入研究这个话题。【参考方案2】:

ATLOGINRESPONSE 的声明几乎肯定是错误的。

public class ATLOGINRESPONSE

    public byte loginResponse;
    public byte[] permissions;
    public ATTIME serverTime;

通过将其声明为一个类,您可以确保它将通过引用进行编组。但是你排除了它被价值编组的可能性。这可能没问题。无论如何,我想我更愿意声明为一个结构。

此外,前两个参数在我看来是错误的。第一个可能是一个枚举,应该这样声明。第二个是字节数组,但您需要指定如何编组它。像这样:

[StructLayout(LayoutKind.Sequential)]
public struct ATLOGINRESPONSE

    public ATLoginResponseType loginResponse; // you need to define the ATLoginResponseType enum
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 255)]
    public byte[] permissions;
    public ATTIME serverTime;

我们不知道ATTIME 的声明是否正确。

我在这里解开了你的函数名称:https://demangler.com/

C++ 函数有这个签名

unsigned __int64 __cdecl ATCreateLoginRequest(
    unsigned __int64,
    wchar_t const *,
    wchar_t const *,
    void (__cdecl*)(unsigned __int64,unsigned __int64,struct _ATLOGIN_RESPONSE *)
)

因此,您的委托被声明为使用错误的调用约定。应该是:

[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void ATLoginResponseCallback(
    ulong hSession, 
    ulong hRequest, 
    ref ATLOGINRESPONSE response
);

请注意,将ATLOGINRESPONSE 更改为结构后,我们必须将参数设为ref 参数。

函数 ATLoginResponseCallback 在您的 C# 代码中正确声明。

您的其他代表也需要使用[UnmanagedFunctionPointer(CallingConvention.Cdecl)] 声明。

除非仅从ATCreateLoginRequest 调用回调,否则传递的委托将被垃圾回收。因此,如果ATCreateLoginRequest 获取了该委托的副本并稍后调用,那么您将需要保持该委托的活动状态。将其分配给ATLoginResponseCallback 类型的变量,其生命周期超出了对回调的最终调用。

问题出在其他地方是完全合理的。

【讨论】:

大卫感谢您提供此信息,我将深入研究您的答案。权限是枚举,这就是我将其设为字节的原因。关于类/结构 - 在同一个程序中,我已经遇到了将结构更改为类的问题。这就是我最初上传项目的原因。这是简单的 1 页程序,其中 C++ 代码可以正常工作,但 C# 不能。 Pro dev 可以在 5 分钟内发现问题.. 谁在乎声誉?我想我受够了。我是来帮助人们学习的。你有兴趣这样做吗? 将其声明为一个类是获得 _ATLOGIN_RESPONSE* 的一种解决方法,但需要 [StructLayout]。传递 OnLoginResponseCallback 是致命的语法糖,委托不会在任何地方被引用并且将被垃圾收集。 从class切换到struct后需要ref。 我很高兴你自己在这方面工作并学习。干得好。

以上是关于PInvoke 使用 wchar16_t 参数调用函数的主要内容,如果未能解决你的问题,请参考以下文章

❥关于C++之ASCII/Unicode/ISO10646及wchar_t/char16_t/char32_t

C#通过PInvoke调用c++函数的备忘录

__vectorcall 通过 C++/CLI 或 PInvoke (C#)

如何检查检查PInvoke签名的调用约定和参数与非托管的目标签名是不是匹配?

c++0x中wchar_t的命运如何?

IndexOutOfRangeException - 无法使用 PInvoke 查看调用堆栈