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);
    }

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