Odd Errors from PInvoke struct/function - c#

Im currently writing a C# wrapper for a C++ API, but a specific struct and a function that relies on this struct have been giving very strange errors when debugging.
C++ Struct:
typedef struct
{
unsigned __int handle;
char name[80];
unsigned int unique_ID;
} DeviceInfo;
Followed by this function:
int __stdcall get_device_info(DeviceInfo di[], const int length_of_di_array, int* p_numValidDevices);
The struct and function is imported as such:
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct DeviceInfo
{
public UInt32 handle;
[MarshalAsAttribute(UnmanagedType.ByValTStr, SizeConst = 80)]
public String name;
public UInt32 unique_ID;
}
[DllImportAttribute("MyC++API.dll", EntryPoint = "get_device_info", CallingConvention = CallingConvention.StdCall)]
public static extern int get_device_info(ref DeviceInfo di, int length_of_di_array, ref numValidDevices);
The intended use of this struct and function is just to obtain some device info from the board Im accessing. Currently I do not have access to the function body in C++, so I can only assume it's working 100% (works fine in C++).
The issue is that when I use the function to run through an array of structs, it outputs the data I'm looking for, but also will begin to fail at runtime, giving me various error windows.
C# code:
static void Main()
{
int numValidDevices = 0; //initialize variable
DeviceInfo[] di = new DeviceInfo[16]; //max of 16 devices
for (int i = 0; i < numValidDevices; ++i) //sorts through all validated devices
{
rc = get_device_info(ref di[i], 16, ref numValidDevices); //accesses each device element and returns the data
Console.WriteLine("Handle: {0}\nName: {1}\nUnique ID: {2}", di[i].handle, di[i].name, di[i].unique_ID);
}
Console.ReadLine(); //stops console from closing prematurely
API_close(); //custom close function from the C++ API
}
Errors while debugging (information is still shown):
"An unhandled exception of type 'System.Threading.ThreadStateException' occurred in System.dll
Additional information: Thread has not been started."
"An unhandled exception of type 'System.ExecutionEngineException' occurred in mscorlib.dll"
Error while debugging (information is not shown, program fails to execute):
"An unhandled exception of type 'System.AccessViolationException' occurred in mscorlib.dll
Additional information: Attempted to read or write protected memory. This is often an indication that other memory is corrupt."
When closing the console window:
"The instruction at '0x7c9113c0' referenced memory at '0x00000000'. The memory could not be 'written'." (sometimes says 'read' instead of 'written').
I've done a lot of research on PInvoke and came across the Microsoft InteropAssistant application, various stack overflow articles such as this one, and this post seems even closer to what Im doing, but I'm still digging into how to use the Marshal.CoTaskMemAlloc/Free, and see if it even will do anyhting...
Thus far what I have for my struct and function are correct, I've tried changing the struct to use an IntPtr but that does not return a di.name value and the di.unique_ID becomes jibberish (oddly enough the di.handle stays valid)
C# code:
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct DeviceInfo
{
public UInt32 handle;
IntPtr p_name;
public String name { get { return Marshal.PtrToStringAnsi(p_name); } }
public UInt32 unique_ID;
}
Intended output:
Handle: 3126770193
Name: DEVICE_A
Unique ID: 12345678
IntPtr output:
Handle: 3126770193
Name:
Unique ID: 1145128264
Oddly enough, using an IntPtr results in none of the errors above, and runs fine.
This leads me to believe the issue lies with marshaling over the C++ char to a string, but I'm not sure if the issue lies with the marshaling, memory management (there is none?), or something I'm not catching entirely.
Any and all feedback would be really appreciated, I've been stumped on this for a number of weeks now...

The exceptions you get indicate that the unmanaged code you are pinvoking is destroying the garbage collected heap. It isn't crystal why, but you don't give the pinvoke marshaller much of a chance to do the Right Thing. It cannot properly pin the array. Start by declaring the function properly, it takes an array so declare one:
[DllImportAttribute("MyC++API.dll", CallingConvention = CallingConvention.StdCall)]
public static extern int get_device_info(
DeviceInfo[] di,
int length_of_di_array,
out int p_numValidDevices
);
Your first declaration of DeviceInfo is correct, the 2nd isn't since the string isn't a pointer.

Something's not adding up, here. It's not clear to me how the function is supposed to be called.
In particular, this declaration:
int __stdcall get_device_info(DeviceInfo di[], const int length_of_di_array, int* p_numValidDevices);
doesn't match how you're using it:
[DllImportAttribute("MyC++API.dll", EntryPoint = "get_device_info", CallingConvention = CallingConvention.StdCall)]
public static extern int get_device_info(ref DeviceInfo di, const int length_of_di_array, int* p_numValidDevices);
...
DeviceInfo[] di = new DeviceInfo[16]; //max of 16 devices
for (int i = 0; i < numValidDevices; ++i) //sorts through all validated devices
{
rc = get_device_info(ref di[i], 16, ref numValidDevices); //accesses each device element and returns the data
}
You're telling it that the array length is 16, starting at index i, which is wrong. Did you mean to only pass one element of the array at a time?
DeviceInfo[] di = new DeviceInfo[16]; //max of 16 devices
for (int i = 0; i < numValidDevices; ++i) //sorts through all validated devices
{
rc = get_device_info(ref di[i], 1, ref numValidDevices); //accesses each device element and returns the data
}
Or did you mean to pass the entire array once?
DeviceInfo[] di = new DeviceInfo[16]; //max of 16 devices
rc = get_device_info(ref di[0], 16, ref numValidDevices); //accesses each device element and returns the data
for (int i = 0; i < numValidDevices; ++i) //sorts through all validated devices
{
Console.WriteLine(...);
}
P.S. I would consider changing your p/invoke declaration to be:
[DllImportAttribute("MyC++API.dll", EntryPoint = "get_device_info", CallingConvention = CallingConvention.StdCall)]
public static extern int get_device_info(
[In, Out] [MarshalAs(UnmanagedType.LPArray, SizeParamIndex=1)] DeviceInfo[] di,
int length_of_di_array,
ref int p_numValidDevices);

So, as pointed out in the replies below, I had two problems:
1. I wasn't calling my DllImport correctly, the way I had it was in an attempt to hack together an output, in doing so I screwed up the memory allocation to the array of structs.
2. I tried to hack together an output and screwed up the code even more (tried to pass in the DeviceInfo array di as a single element di[number] instead of as a whole).
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct DeviceInfo
{
public UInt32 handle;
[MarshalAsAttribute(UnmanagedType.ByValTStr, SizeConst = 80)]
public String name;
public UInt32 unique_ID;
}
[DllImportAttribute("MyC++API.dll", EntryPoint = "get_device_info", CallingConvention = CallingConvention.StdCall)]
public static extern int get_device_info(
[In, Out] [MarshalAs(UnmanagedType.LPArray, SizeParamIndex=1)] DeviceInfo[] di,
int length_of_di_array,
ref int p_numValidDevices);
static void Main()
{
int numValidDevices = 0;
DeviceInfo[] di = new DeviceInfo[16];
get_device_info(di, 16, ref numValidDevices);
for (int i = 0; i < numValidDevices; ++i)
{
Console.WriteLine("Handle: {0}\nName: {1}\nUnique ID: {2}", di[i].handle, di[i].name, di[i].unique_ID);
}
Console.ReadLine();
API_close();
}

Related

C# interop returning System.Access.Violation

I am attempting to use a C function through C# Interop and I am receiving an access violation on one function. I have tried a number of things and I can't seem to solve this.
Here is the C code that needs to be changed into c# code:
typedef struct
{
char SerNo[64];
unsigned char hwVer;
HANDLE device; // Set by the API on return from SelectDevice()
} DeviceT;
This struct is used by the following function:
error = GetDevices(DeviceT *devices, unsigned int *numDevs, unsigned int maxDevs)
There is one other function in the C code:
error = SelectDevice(DeviceT *device)
So I began by defining DeviceT. I tried a few ways, but settled on this since it is simple:
[StructLayout(LayoutKind.Sequential)]
public struct DeviceT
{
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 64)]
public char[] SerNo;
public byte hwVer;
public IntPtr device;
}
The GetDevices function was set to this:
[DllImport("file.dll", CallingConvention = CallingConvention.Cdecl)]
public unsafe static extern ErrT GetDevices([In, Out] DeviceT[] devices, uint* numDevs, uint maxDev);
The SelectDevices function was set to this:
[DllImport("file.dll", CallingConvention = CallingConvention.Cdecl)]
public unsafe static extern ErrT SelectDevice([In, Out] DeviceT devices);
The code goes like this:
uint numDevs = 6;
uint maxDev = 6;
uint chosenIdx = 0;
DeviceT[] devices = new DeviceT[6];
err = GetDevices(devices, &NumberOfDevices, maxDev))
At this point everything is correct. The devices array has the correct information in it.
I now continue with (I just hard code select the first device)
chosenIdx = 0;
var chosenDevice = devices[chosenIdx];
err = SelectDevice(chosenDevice);
This last function returns a System.Access Violation
I tried a whole bunch of things but all end up with the same result. I suspect it has something to do with the HANDLE but I am not sure.
Thanks for any help.
SelectDevice takes a DeviceT *, but your P/Invoke signature takes a DeviceT. That is, you're passing in DeviceT by value rather than passing a pointer.
Try:
[DllImport("file.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern ErrT SelectDevice([In, Out] ref DeviceT devices);
err = SelectDevice(ref chosenDevice);

Passing an array of pointers to an unmanaged DLL function

I am trying to create and pass an array of pointers to an unmanaged DLL function using the following C# code.
[DllImport("libantumbra.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern uint AnCtx_Init(IntPtr ctx);
//create context
this.ptr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(IntPtr)));
AnCtx_Init(ptr);//returns 0 (non-error)
this.ctx = (IntPtr)Marshal.PtrToStructure(ptr, typeof(IntPtr));
[DllImport("libantumbra.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int AnDevice_GetList(IntPtr ctx, out IntPtr outdevs, out int outndevs);
IntPtr devs, ndevs;
AnDevice_GetList(ctx, out devs, out ndevs); //exception occurs here
However upon my last call I receive an AccessViolationException. I think it has to do with the array pointer I am passing however I have not been able to find a solution.
The end goal I am trying to achieve here is to pass a pointer to AnDevice_GetList and with the parameter outdevs be left with an array that has been populated by the DLL.
Let me know if you need any further info or have any ideas for me to try.
Edit:
Here is the function I am trying to call.
Header file:
An_DLL AnError AnDevice_GetList(AnCtx *ctx, AnDeviceInfo ***outdevs,
size_t *outndevs);
typedef struct AnDevice AnDevice;
typedef int AnError;
typedef struct AnCtx AnCtx;
And implementation:
AnError AnDevice_GetList(AnCtx *ctx, AnDeviceInfo ***outdevs, size_t *outndevs)
{
An_LOG(ctx, AnLog_DEBUG, "enumerate devices...");
libusb_device **udevs;
ssize_t ndevs = libusb_get_device_list(ctx->uctx, &udevs);
if (ndevs < 0) {
An_LOG(ctx, AnLog_ERROR, "libusb_get_device_list: %s",
libusb_strerror(ndevs));
return AnError_LIBUSB;
}
AnDeviceInfo **devs = malloc((ndevs + 1) * sizeof *devs);
if (!devs) {
An_LOG(ctx, AnLog_ERROR, "malloc: %s", strerror(errno));
return AnError_MALLOCFAILED;
}
memset(devs, 0, (ndevs + 1) * sizeof *devs);
size_t j = 0;
for (ssize_t i = 0; i < ndevs; ++i) {
libusb_device *udev = udevs[i];
AnDeviceInfo info;
An_LOG(ctx, AnLog_DEBUG, "device: bus %03d addr %03d",
libusb_get_bus_number(udev), libusb_get_device_address(udev));
if (populate_info(ctx, &info, udev))
continue;
An_LOG(ctx, AnLog_DEBUG, "vid 0x%04x pid 0x%04x",
info.devdes.idVendor, info.devdes.idProduct);
if (!match_vid_pid(info.devdes.idVendor, info.devdes.idProduct)) {
An_LOG(ctx, AnLog_DEBUG, " does not match Antumbra VID/PID");
continue;
}
devs[j] = malloc(sizeof *devs[j]);
if (!devs[j]) {
An_LOG(ctx, AnLog_ERROR, "malloc: %s", strerror(errno));
continue;
}
libusb_ref_device(udev);
*devs[j] = info;
++j;
}
libusb_free_device_list(udevs, 1);
*outdevs = devs;
*outndevs = j;
return AnError_SUCCESS;
}
Your unmanaged function is declared like this:
AnError AnDevice_GetList(AnCtx *ctx, AnDeviceInfo ***outdevs, size_t *outndevs)
You should translate that as:
[DllImport("libantumbra.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int AnDevice_GetList(IntPtr ctx, out IntPtr outdevs,
out IntPtr outndevs);
And this is almost exactly as you have done. The only differences are that the return value is int and the outndevs parameter is of type IntPtr. That's because size_t is pointer sized on the platforms that I am aware of.
Call it like this:
IntPtr ctx = ...; // create a context somehow
IntPtr devs;
IntPtr ndevs;
int retval = AnDevice_GetList(ctx, out devs, out ndevs);
if (retval != AnError_SUCCESS)
// handle error
So, where could your code be going wrong? One likely explanation is that the context that you pass is invalid. Another possibility is that you execute 64 bit code and the incorrect size of outndevs in your translation caused the error.
This is a pretty hard API to call using p/invoke. What can you do now with devs. You can copy the values into an IntPtr[] array easily enough. And presumably the library has functions that operate on these opaque device pointers. But you have to keep hold of devs and pass it back to the library to deallocate it. Presumably the library exports a function to do that?
Based on your comments and various updates, it looks like you are not getting a proper context. We can only guess, but I expect that AnCtx_Init is declared as
AnError AnCtx_Init(AnCtx **octx)
That is a pointer to opaque context AnCtx*. Translate that as:
[DllImport("libantumbra.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int AnCtx_Init(out IntPtr octx);
Call it like this:
IntPtr ctx;
int retval = AnCtx_Init(out ctx);
if (retval != AnError_SUCCESS)
// handle error
The big thing that you have to do now is start checking for errors. Unmanaged code won't throw exceptions. You need to do error checking yourself. It is laborious, but it must be done. Take it one function at a time. Once you are sure a function call is working, move on to the next.
Some things don't make sense in your example. You create ptr2 and allocate space for it but never copy anything into that space, and you don't pass it to your AnDevice_GetList function so that seems completely unnecessary. You create ptArray but never use it anywhere either.
In this code, you're creating a managed array of IntPtr structures, and allocating memory for each of them to point to, and the size of what they are pointing to is the size of a single pointer:
IntPtr[] ptArray = new IntPtr[] {
Marshal.AllocHGlobal(IntPtr.Size),
Marshal.AllocHGlobal(IntPtr.Size)
};
To really help we need a clear understanding of exactly what AnDevice_GetList is going to do. If AnDevice_GetList is populating an array of pointers, what do they point to? Do they point to structures that were allocated by AnDevice_GetList? If so, then what you want to do is to create an array of IntPtr and pin it while you make the unmanaged call. Since you're creating an array for the call to fill, do NOT pass the array as an out parameter.
[DllImport("libsomething.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern uint AnDevice_GetList(IntPtr outdevs);
IntPtr[] ptArray = new IntPtr[numberOfPointersRequired];
GCHandle handle = GCHandle.Alloc(ptArray);
try
{
AnDevice_GetList(handle.AddrOfPinnedObject());
}
finally
{
handle.Free();
}
I left off the other parameters, because I have no idea what you're doing with them or how you're expecting them to be handled.

Working with pointer offsets in C#

I've been working on an application that use API to get events from the Windows event log. I'm stuck on pointer offsets at the moment. The specific struct I'm using is EVENTLOGRECORD (see: http://msdn.microsoft.com/en-us/library/aa363646(v=vs.85).aspx). My C# struct is defined as:
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto, Pack = 1)]
internal struct EVENTLOGRECORD
{
internal UInt32 Length;
internal UInt32 Reserved;
internal UInt32 RecordNumber;
internal UInt32 TimeGenerated;
internal UInt32 TimeWritten;
internal UInt32 EventID;
internal UInt16 EventType;
internal UInt16 NumStrings;
internal UInt16 EventCategory;
internal UInt16 ReservedFlags;
internal UInt32 ClosingRecordNumber;
internal UInt32 StringOffset;
internal UInt32 UserSidLength;
internal UInt32 UserSidOffset;
internal UInt32 DataLength;
internal UInt32 DataOffset;
}
My ReadEventLog function is declared as:
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Auto, EntryPoint = "ReadEventLog")]
internal static extern Boolean ReadEventLog(IntPtr hEventLog, EVT_READ_FLAGS dwReadFlags, UInt32 dwRecordOffset, IntPtr lpBuffer, UInt32 nNumberOfBytesToRead, out UInt32 pnBytesRead, out UInt32 pnMinNumberOfBytesNeeded);
I'm able to get the struct filled with data and I can get to SourceName and ComputerName sections by using IntPtr.Add. Example:
IntPtr pSrc = IntPtr.Add(pRecord, Marshal.SizeOf(typeof(EVENTLOGRECORD)));
string sSrc = Marshal.PtrToStringAuto(pSrc);
Console.WriteLine("source: {0}\n", sSrc);
IntPtr pComp = IntPtr.Add(pSrc, (sSrc.Length * 2) + 2);
string sComp = Marshal.PtrToStringAuto(pComp);
Console.WriteLine("computer: {0}\n", sComp);
My issue is trying to get the Strings portion from the struct. I can't seem to figure out what the correct offsets will be. I can do it in C++, but I can't seem to make it work in C#. Here's a snippet of what I use in C++ (elr is (EVENTLOGRECORD*)pRecord):
char* strings = (LPSTR)((LPBYTE) elr + elr->StringOffset);
while (elr->NumStrings)
{
wprintf(L"String: %s\n", strings);
strings += (wcslen((wchar_t*)strings) * sizeof(wchar_t)) + sizeof(wchar_t);
elr->NumStrings--;
}
Hopefully someone can help explain what I'm missing. I'd also be curious if there are any alternatives to IntPtr.Add since that require .NET 4.0. I'm not an expert with p/invoke by any means. Thanks.
It you use Marshal.PtrToStructure() to copy the first part of your block of data to a EVENTLOGRECORD, then you should be able to just do something like:
EVENTLOGRECORD record;
... Copy the ptr into record ...
IntPtr pStrings = IntPtr.Add(pRecord, (record.StringOffset * 2));
I'd be happy to get this going for you, but I'm too lazy to do all the other p/invoke bit which get as far as being able to make the ReadEventLog call.
After some time away from coding to clear the brain, I finally have a solution. I was incorrectly making the offset too large since I was taking the size of the struct (EVENTLOGRECORD), adding it to StringOffset, then adding that to the existing object. All I really needed to do was add the offset to the existing IntPtr (pRecord). Not sure why that didn't click before.
So simply doing:
int offset = ((int)(((EVENTLOGRECORD)o).StringOffset));
IntPtr pStrings = IntPtr.Add(pRecord, offset);
string sString = Marshal.PtrToStringAuto(pStrings);
Console.WriteLine("string : {0}\n", sString);
..is enough to get the string. Thanks for the assists though. I apparently lack rep to mark suggestions as helpful. Guess I just needed time away from this.
It might be too late to ask. Why are you not using the System.Diagnostics.EventLog class of .NET?
As far as I can see you have problems with the EventLog class. Did you try the EventLogReader class which does work for Windows Versions Vista and later?
With Reflector you can also find out how they do the marshalling. It looks like that it is not possible to let the marshaller do the complete work. They do read the data intoa a simple byte array and store it inside an EventLogEntry class which does read from the byte array directly the required data.
To read the replacement strings they do in the EventLogEntry class. The databuf member is the byte array from the native ReadEventLog call.
[MonitoringDescription("LogEntryReplacementStrings")]
public string[] ReplacementStrings
{
get
{
string[] strArray = new string[this.ShortFrom(this.dataBuf, this.bufOffset + 0x1a)];
int index = 0;
int offset = this.bufOffset + this.IntFrom(this.dataBuf, this.bufOffset + 0x24);
StringBuilder builder = new StringBuilder();
while (index < strArray.Length)
{
char ch = this.CharFrom(this.dataBuf, offset);
if (ch != '\0')
{
builder.Append(ch);
}
else
{
strArray[index] = builder.ToString();
index++;
builder = new StringBuilder();
}
offset += 2;
}
return strArray;
}
}
Yours,
Alois Kraus

P/Invoke C# struct with strings to C void*

I'm having a problem creating a C# P/invoke wrapper around a third party C library. In particular, the library has a method with the signature
int command(SomeHandle *handle, int commandNum, void *data, int datasize);
It is a wildcard method that does different things depending on commandNum. data can be a pointer to anything, like a single integer, or a char[], or a struct of some kind (my problem).
I have declared the wrapper as follows:
[DllImport("LIBRARY.DLL", EntryPoint = "command")]
public static extern int Command(IntPtr Handle, int CommandNum, [In, Out] IntPtr Data, int DataSize);
Now, when i call it with an opcode to fill a byte[] it works:
//WORKS, Buffer contains "library 1.0" after the call
const int BUFFER_SIZE = 128;
byte[] Buffer = new byte[BUFFER_SIZE];
int BytesWritten = 0;
GCHandle BufferHandle = GCHandle.Alloc(Buffer, GCHandleType.Pinned);
try
{
BytesWritten = Command(MyHandle, GET_VERSION, BufferHandle.AddrOfPinnedObject(), BUFFER_SIZE);
}
finally
{
BufferHandle.Free();
}
However, when I try the same with a simple struct, I cannot make it work, no matter what I try. the struct looks like this:
public struct FormatInfoType
{
public int Format;
public IntPtr Name; //const char*
public IntPtr Extension; //const char*
}
Here I am supposed to fill "Format" with an int (say, 1) and then the call to "command(...)" is meant to give me back the name and extension fields
If I pass this struct, the code compiles and runs correctly, but the values in the struct are never modified. If I change the IntPtr's to Strings or StringBuilders (and I've tried a myriad of MarshalAs attributes), then I cannot get the IntPtr to the struct because the it becomes non-blittable and the GCHandle line throws an exception.
Any help on this would be greatly appreciated.
EDIT:
I've tried many ways to call command() with the structure, but currently it looks like this:
FormatInfoType f = new FormatInfoType();
f.Format = 1;
f.Name = IntPtr.Zero;
f.Extension = IntPtr.Zero;
GCHandle fHandle = GCHandle.Alloc(f, GCHandleType.Pinned);
try
{
Command(MyHandle, GET_FORMAT_INFO, fHandle.AddrOfPinnedObject(), Marshal.SizeOf(f));
}
finally
{
fHandle.Free();
}
You can overload p/invoke signatures, try:
[DllImport("LIBRARY.DLL", EntryPoint = "command")]
public static extern int Command(IntPtr Handle, int CommandNum, ref FormatInfoType Data, int DataSize);

How to write the C# prototype to P/Invoke a function which takes a char* as an argument, and returns its value there

As the title suggests, I'm trying to write some C# code to interop with a C DLL (It's a Windows device driver, basically). The error code function signature in C is:
UINT DLLErrorMsg( DWORD errorCode, char * pBuf, UINT nSize );
nSize is the size of pBuf. If nSize can fit the error message, it is copied into pBuf and 0 returned, otherwise (the buffer was too small and) the return value is the minimum size the buffer needs to be to fit the error message.
I've tried some variants of the following:
internal class DLLWrapper
{
[DllImport(_libName, EntryPoint="DLLErrorMsg", CallingConvention=CallingConvention.Cdecl)]
public static extern UInt32 GetErrorMessage(UInt32 dwError, ref char * pBuf, UInt32 nBufSize);
}
The client code at the moment looks something like this:
GetError( UInt32 errorCode )
{
char[] errorMsg = new char[bufSize];
UInt32 moreChars = DLLWrapper.GetErrorMessage(errorCode, out errorMsg, bufSize);
if (moreChars > 0)
{
errorMsg = new char[moreChars];
TWLDLLInterface.GetErrorMessage(errorCode, out errorMsg, moreChars);
}
}
But I get an exception at the call to GetErrorMessage:
An unhandled exception of type 'System.ArgumentException' occurred in NdevInterface.dll
Additional information: Method's type signature is not Interop compatible.
I've also tried playing around with IntPtr and trying Marshal.IntPtrToStringAuto(), but that wasn't helpful because I need to allocate the buffer, and I obviously can't cast a char[] into an IntPtr.
I've tried searching through the MSDN and general internet for tips on how to do this, but most of it seems to be a little different than what I'm trying.
What am I doing wrong?
You can marshal this using a StringBuilder. For details, see this P/Invoke intro:
[DllImport(_libName, EntryPoint="DLLErrorMsg", CallingConvention=CallingConvention.Cdecl)]
public static extern UInt32 GetErrorMessage(UInt32 dwError, StringBuilder pBuf, UInt32 nBufSize);
Just make sure the StringBuilder has been constructed with enough memory for the pBuf to get filled in.
This example is very similar to what you're trying to achieve.
I think you want this:
internal class DLLWrapper
{
[DllImport(_libName, EntryPoint="DLLErrorMsg", CallingConvention=CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
private static extern UInt32 GetErrorMessage(UInt32 dwError, StringBuilder * pBuf, UInt32 nBufSize);
public static UInt32 GetErrorMessage( UInt32 dwError, out string msg)
{
uint buffersize = 1024;
StringBuilder sb = new StringBuilder((int)buffersize);
uint result = GetErrorMessage( dwError, sb, buffersize );
msg = sb.ToString();
return result;
}
}

Categories