首页 > 解决方案 > 导致内存冲突的 C SDK 函数的 PInvoke 实现

问题描述

我想从 C# 调用一个 C DLL 函数。该函数在 C 包含头文件中描述为:

#ifdef _WIN32
#ifdef CMGO_BUILD_DLL
    #define CMGO_DLL_API __declspec(dllexport)
#else
    #define CMGO_DLL_API __declspec(dllimport)
#endif

#define CMGO_API_CC __stdcall
#else
#define CMGO_DLL_API
#define CMGO_API_CC
#endif
typedef void* CMGO_DATACHANNEL_HANDLE;
CMGO_DLL_API int CMGO_API_CC cmgo_create_datachannel(const char* host, unsigned short port, CMGO_DATACHANNEL_HANDLE* handle);

显示如何使用cmgo_create_datachanell DLL 调用来创建通道的示例 C 文件具有以下示例用法:

int main(int argc, char** argv)
{
    int st;
    char address[] = "127.0.0.1";
    unsigned int port = 9800;

    CMGO_DATACHANNEL_HANDLE handle = NULL;
    struct CMGO_DATACHANNEL_READ_RESULT result;

    memset(&result, 0, sizeof(result));

    printf("Creating DataChannel [%s,%u] ...\n", address, port);

    st = cmgo_create_datachannel(address, port, &handle);

在 C# 中,我编写了以下内容:

const string CarmenSDKDLL = "cmgocapi.dll";

[DllImport(CarmenSDKDLL, CallingConvention= CallingConvention.Cdecl)]
static extern int cmgo_create_datachannel(string host, ushort port, IntPtr handle);

static void Main(string[] args)
{
    const string ANPRServer = "127.0.0.1";
    ushort port = 9800;
    IntPtr iHandle = new IntPtr(0);

    int iResult = cmgo_create_datachannel(ANPRServer, port, iHandle);
}

当我运行 C# 代码时出现异常

System.AccessViolationException HResult=0x80004003 Message=试图读取或写入受保护的内存。

谁能指出我正确的方向并解释我在 C# 代码中写错了什么?

标签: c#

解决方案


您的代码的主要问题是您传递handle错误 - 它实际上是 a void**,而不是 a void*; 意思是,它是一个out IntPtr,而不是一个IntPtr

更改调用代码如下:

const string CarmenSDKDLL = "cmgocapi.dll";

[DllImport(CarmenSDKDLL, CallingConvention = CallingConvention.StdCall)]
extern
static int cmgo_create_datachannel([MarshalAs(UnmanagedType.LPStr)] string host,
                                   ushort port,
                                   out IntPtr handle);

上述声明中有一些更正:

  • 调用约定- 您需要匹配声明的调用约定,即__stdcall, 不是__cdecl
  • 字符串类型- C# 字符串是 Unicode,而不是 ANSI。您需要指定正确的编组类型。
  • out参数- 注意声明typedef void* CMGO_DATACHANNEL_HANDLE;- 它是一个void*; 但是,该函数需要一个指向该类型的指针——现在是一个void**.

您得到的访问冲突异常特别是由于最后一个要点 - C 函数正在尝试写入无效的内存位置。

调用代码

const string ANPRServer = "127.0.0.1"; 
ushort port = 9800;

int errorCode = cmgo_create_datachannel(ANPRServer, port, out var handle);

if(errorCode == 0)
{
   // handle is a valid pointer of type IntPtr
}

通常,通过 P/Invoke 调用的 C 函数中发生的错误可能与您在 C# 调用代码中看到的异常有很大不同。我建议查看Marshal.GetLastWin32Error()文档和详细示例,以改进使用 P/Invoke 时的错误处理。


推荐阅读