从 .Net 调用本机函数时参数值混乱

Posted

技术标签:

【中文标题】从 .Net 调用本机函数时参数值混乱【英文标题】:Parameter values messed up when calling native functions from .Net 【发布时间】:2020-10-09 15:38:34 【问题描述】:

现在我有一个 c++ DLL 项目,我用它作为 .Net 中较低级别功能的包装器。

C++ 代码:

string testA(uint a) 
    cout << a;
    return "egrt";

bool testB(uint a) 
    cout << a;
    return false;

C++ 头文件:

#pragma once

#ifdef API_EXPORTS
#define API __declspec(dllexport)
#else
#define API __declspec(dllimport)
#endif

typedef unsigned int uint;

extern "C" API string testA(uint a);
extern "C" API bool testB(uint a);

C#代码:

[DllImport(...)] extern static string testA(uint a);
[DllImport(...)] extern static bool testB(uint a);

static void Main() 
    testA(10);
    testB(13);

由于某种原因,testA中的参数不是我给的数字,但在testB中,它可以正常工作。

这是正常行为吗?我究竟做错了什么?我怎样才能避免这种情况?

如果需要,我很乐意提供有关解决方案设置的更多详细信息。

【问题讨论】:

【参考方案1】:

DllImport 的默认调用约定是 stdcall,但 C/C++ 中的默认调用约定通常是 cdecl。您描述的症状很容易由两种语言之间的调用约定不匹配引起,因此请确保两组代码就使用哪种调用约定达成一致。

此外,一般来说,您不能安全地跨 DLL 边界传递 C++ std::string(或任何其他基于类的字符串类型),而且绝对不能跨语言传递。跨越语言边界时,仅使用可移植的 POD 类型非常重要。大多数语言都与 C 兼容,因此请仅使用 C 可以使用的类型(因此,在这种情况下,使用 char*/wchar_t* 表示字符串)。

还必须考虑内存管理问题。如何分配内存,谁负责释放内存以及如何释放内存等。在这种情况下,如果 C/C++ DLL 返回一个 char* 字符串,并且 C# 自动将该数据编组为本机 string,那么 C# 将尝试使用CoTaskMemFree()释放char*字符串,因此DLL需要使用CoTaskMemAlloc()分配char*字符串,例如:

char* __stdcall testA(uint32_t a) 
    cout << a;
    const char *str = "egrt";
    char *result = (char*) CoTaskMemAlloc(strlen(str)+1);
    if (result)
        strcpy(result, str);
    return result;


bool __stdcall testB(uint32_t a) 
    cout << a;
    return false;

#pragma once

#include <cstdint>

#ifdef API_EXPORTS
#define API __declspec(dllexport)
#else
#define API __declspec(dllimport)
#endif

extern "C" API char* __stdcall testA(uint32_t a);
extern "C" API bool __stdcall testB(uint32_t a);
[DllImport(..., CharSet=CharSet.Ansi)]
extern static string testA(uint a);

[DllImport(...)]
extern static bool testB(uint a);

static void Main() 
    testA(10);
    testB(13);

如果这不是 DLL 的选项,则必须将 char* 字符串编组为 IntPtr,并且 DLL 必须导出一个附加函数供 C# 调用以释放 @987654339 @字符串正确,例如:

char* __stdcall testA(uint32_t a) 
    cout << a;
    const char *str = "egrt";
    char *result = ...; // allocated some other way...
    if (result)
        strcpy(result, str);
    return result;


void __stdcall freeA(void *p) 
    cout << p;
    // deallocate p as needed...


bool __stdcall testB(uint32_t a) 
    cout << a;
    return false;

#pragma once

#include <cstdint>

#ifdef API_EXPORTS
#define API __declspec(dllexport)
#else
#define API __declspec(dllimport)
#endif

extern "C" API char* __stdcall testA(uint32_t a);
extern "C" API void __stdcall freeA(void *p);
extern "C" API bool __stdcall testB(uint32_t a);
[DllImport(...)]
extern static IntPtr testA(uint a);

[DllImport(...)]
extern static void freeA(IntPtr p);

[DllImport(...)]
extern static bool testB(uint a);

static void Main() 
    IntPtr p = testA(10);
    // string s = Marshal.PtrToStringAnsi(p);
    freeA(p);
    testB(13);

【讨论】:

那么请mark the answer as accepted。

以上是关于从 .Net 调用本机函数时参数值混乱的主要内容,如果未能解决你的问题,请参考以下文章

函数参数的扩展

Javascript - Jquery - 函数参数

如何在 SQL*Plus 中将参数(值)从函数传递/调用到“选择”stmt?

重构:简化函数调用

C#在方法调用中,参数按值传递与按引用传递的区别是啥?

4.函数