C字符串文字与P/Invoke的C字符串数组/指针工作不正常

本文关键字:字符串 指针 工作 不正常 数组 Invoke 文字 | 更新日期: 2023-09-27 18:27:58

我有一个简单的情况设置:

基本.NET代码:

[DllImport ... stuff... use cdecl]
public static extern void SetCallback(CallbackDelegate c);
[UnmanagedFunctionPointer ... use cdecl]
public delegate CallbackDelegate(MarshalAs(single byte string with null terminating character pointer) string c)    
public static CallbackDelegate theNotGCdDelegate = null;
public void Start() {
    theNotGCdDelegate = new CallbackDelegate(CallbackCalledHere);
    SetCallback(theNotGCdDelegate);
}
public void CallbackCalledHere(string text) {
    Debug.WriteLine(text);
}

基本C代码(与MinGW一起编译):

__declspec(dllexport) void __cdecl SetCallback(void (__cdecl *TheCallback)(char* text)){
    // This does not work as expected:
    TheCallback("This is a string literal");
    // This works as expected:
    char pointerMessage[] = "This is also a string literal, but referenced by an array.";
    TheCallback(pointerMessage);
}

因此,我的情况(我保证我不会编造!)是,从C用字符串直接调用SetCallback(即TheCallback("Hello World"))会导致一个空字符串(字符串的第一个字节为NULL)。NET将其转换为一个空字符串(即")。我已经通过使用Marshal.ReadByte并将参数封送为IntPtr来确认这种情况。

在第二种情况下(当声明一个指针/数组并将该变量传递到TheCallback时),我得到了完美的字符串,正如预期的那样!

这在理论上对我来说没有意义。这两个字符串都应该放在C DLL的数据部分,并且都有永远不应该移动的指针!这两种情况都应该传递指向实际数据的指针。我的编译没有任何警告。

这可能是相关的,但可能不是:我在一台64位机器上运行。我100%确信,当我在MinGW中编译32位DLL时,我总是从.NET应用程序中的32位进程空间调用该DLL(显式输出32位程序集)。同样,当我编译64位DLL时,我总是从.NET应用程序中的64位进程空间调用该DLL(显式输出64位程序集)。

我已经验证了正确的32位标志显示在任务管理器中,并且在64位构建中丢失了。DLL仅位于bin目录中,而不是regsvr32'd。

重要的是,64位DLL没有出现这个问题:两个字符串都正确地传递到.NET运行时。32位DLL显示了我上面提到的问题

我正在编译/链接32位DLL和MinGW的gcc/ld(来自http://mingw.org/)。我正在编译/链接64位DLL和来自的mingw-w64-bin_i686-ming_20111220.zip包http://mingw-w64.sourceforge.net/

有人能解释一下这里发生了什么吗?或者您更愿意推荐不同的编译器或编译器标志?

编辑

我刚刚发现GetFunctionPointerForDelegate只适用于stdcall函数。我认为这是一个重要的观点。我一直在断断续续地使用这个Marshal调用,但我想我现在会完全避免它,因为回调应该是cdecl。我假设UnmanagedFunctionPointer表示法应该足以将指针传递给cdecl函数。

C字符串文字与P/Invoke的C字符串数组/指针工作不正常

尽管缺少我的"实际代码",但在编译时不使用ld解决了这个问题。显然,我丢失了一些链接器标志,这些标志是在使用gcc时由编译器自动添加的。当我改为单独使用gcc时,我所有的问题都消失了。

谢谢大家!

在Linux上,gcc通常将字符串文本放在只读部分(ELF格式为.rodata;不确定Windows的等效部分是什么)。

尝试使用-fwritable string选项进行编译,看看这是否有什么不同。

编辑

仔细检查了我的Linux系统(手头没有Mingw)。如果没有-fwritable string选项,字符串文字将放在.rodata部分。有了这个选项,他们进入.data.