I am wanting to call a C DLL function from C#. The function is described in the C include header file as:
#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);
A sample C file showing how to use the cmgo_create_datachanell DLL call to create the channel has this example usage:
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);
In C# I have written the following:
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);
}
When I run the C# code I get an exception
System.AccessViolationException HResult=0x80004003 Message=Attempted to read or write protected memory.
Can anyone pointer me in the right direction and explain what I have written incorrectly in the C# code?
The main problem with your code is you're passing the handle
incorrectly - it is in fact a void**
, not a void*
; meaning, it's an out IntPtr
, not an IntPtr
Change the calling code as follows:
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);
There are a few corrections in the above declaration:
__stdcall
, not __cdecl
out
parameter - Notice the declaration typedef void* CMGO_DATACHANNEL_HANDLE;
- it's a void*
; however, the function takes a pointer to that type - which is now a void**
.The access violation exception you are getting is specifically due to the last bullet point - the C function is trying to write to an invalid memory location.
Calling code:
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
}
In general, the errors that happen in the C functions called via P/Invoke may differ quite a bit from the exceptions you see in C# calling code. I recommend looking into Marshal.GetLastWin32Error()
documentation and detailed example to improve the error handling in working with P/Invoke.
The handle
argument is obviously used to return a handle - you need to declare and call it with ref
. Other than that, string
arguments generally need some care - for example, by default, C# marshals strings as Unicode, and it seems that the DLL expects ANSI strings. So my guess at the correct P/Invoke signature is
[DllImport(CarmenSDKDLL, CallingConvention= CallingConvention.Cdecl)]
static extern int cmgo_create_datachannel([MarshalAs(UnmanagedType.LPStr)] string host, ushort port, ref IntPtr handle);
User contributions licensed under CC BY-SA 3.0