c# - 导致内存冲突的 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# 代码中写错了什么?
解决方案
您的代码的主要问题是您传递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 时的错误处理。
推荐阅读
- java - 无法在 android 中获取广告 ID 提供者
- java - 使用用户输入创建 2 个维度数组并查找特定列的总和
- ios - 如何在 iOS Swift 中剪切部分 CALayer?
- css - 使用html和css的组织结构图
- amazon-web-services - 无法通过 EC2 实例中的公共 IP 访问我的弹性搜索
- python-2.7 - RuntimeError:运行循环已在 pyttsx 中启动
- laravel - 产品详情页面未显示
- java - 带有用户输入的插入排序双数组 - JAVA
- php - 如何从数据库创建 JSON 树
- c# - 如何使用 Win32_PnPEntity 访问 USB 驱动器上的文件