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
__vectorcall 通过 C++/CLI 或 PInvoke (C#)