PInvoke 调用带有 wchar16_t 个参数的函数
PInvoke calling of function with wchar16_t parameters
我有一个带有 C++ 函数的 .dll,它接受 const wchar16_t*
参数。
我试图在 c# 中使用字符串、字符数组和带有附加 '\0' 字符的字符数组导入和使用它,但我没有得到任何结果。
当我在原始 C++ 程序中以调试模式检查它时,它末尾有附加的 '\0' 字符。我应该使用哪种类型?
P.s。我不是 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;
ATLOGINRESPONSE
的声明几乎肯定是错误的。
public class ATLOGINRESPONSE
{
public byte loginResponse;
public byte[] permissions;
public ATTIME serverTime;
}
通过将其声明为 class,您可以确保它将通过引用进行编组。但是你排除了它被价值编组的可能性。这可能没问题。无论如何,我想我更愿意声明为结构。
此外,前两个参数在我看来是错误的。第一个可能是一个枚举,应该这样声明。第二个是字节数组,但您需要指定如何编组它。像这样:
[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
参数。
在您的 C# 代码中正确声明了函数 ATLoginResponseCallback
。
您的其他代表也需要用 [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
声明。
除非仅从 ATCreateLoginRequest
调用回调,否则传递的委托会被垃圾收集。因此,如果 ATCreateLoginRequest
获取该委托的副本并稍后调用,那么您将需要使委托保持活动状态。将其分配给类型为 ATLoginResponseCallback
的变量,该变量的生命周期超出对回调的最终调用。
完全有可能问题出在其他地方。
我下载了您的测试项目并立即修复了委托声明,因为它们是错误的:
[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);
}
对另外两个做同样的事情。
我有一个带有 C++ 函数的 .dll,它接受 const wchar16_t*
参数。
我试图在 c# 中使用字符串、字符数组和带有附加 '\0' 字符的字符数组导入和使用它,但我没有得到任何结果。
当我在原始 C++ 程序中以调试模式检查它时,它末尾有附加的 '\0' 字符。我应该使用哪种类型?
P.s。我不是 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;
ATLOGINRESPONSE
的声明几乎肯定是错误的。
public class ATLOGINRESPONSE
{
public byte loginResponse;
public byte[] permissions;
public ATTIME serverTime;
}
通过将其声明为 class,您可以确保它将通过引用进行编组。但是你排除了它被价值编组的可能性。这可能没问题。无论如何,我想我更愿意声明为结构。
此外,前两个参数在我看来是错误的。第一个可能是一个枚举,应该这样声明。第二个是字节数组,但您需要指定如何编组它。像这样:
[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
参数。
在您的 C# 代码中正确声明了函数 ATLoginResponseCallback
。
您的其他代表也需要用 [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
声明。
除非仅从 ATCreateLoginRequest
调用回调,否则传递的委托会被垃圾收集。因此,如果 ATCreateLoginRequest
获取该委托的副本并稍后调用,那么您将需要使委托保持活动状态。将其分配给类型为 ATLoginResponseCallback
的变量,该变量的生命周期超出对回调的最终调用。
完全有可能问题出在其他地方。
我下载了您的测试项目并立即修复了委托声明,因为它们是错误的:
[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);
}
对另外两个做同样的事情。