How to DLLImport a C/C++ P/Invoke function that return a pointer to a pointer of struct in C#? - c#

I installed libserialport for Debian 11 in raspberrypi (source code). I would like to write a .NET application that send data via USB CDC. My target is to create a wraper of libserialport for C# .NET 7 but I got trouble with this function SP_API enum sp_return sp_get_port_by_name(const char *portname, struct sp_port **port_ptr);
My C# code:
using System.ComponentModel.Design;
using System.Runtime.InteropServices;
namespace SerialPortC
{
internal class Program
{
static void Main(string[] args)
{
string portName = "/dev/ttyACM0";
IntPtr portPtr = new IntPtr();
int error = 0;
error = sp_get_port_by_name(portName, portPtr); // error = -1 after executing this
Console.WriteLine("Error Code: " + error);
}
[DllImport("libserialport.so", CallingConvention = CallingConvention.StdCall)]
public static extern int sp_get_port_by_name(string portName, IntPtr portPtr);
}
}
And after executing sp_get_port_by_name function. It always return -1 (Invalid Parameter).
What wrong in my C# code? Please help to correct it!
I have already tried
IntPtr portPtr = new IntPtr();
and
IntPtr portPtr = IntPtr.Zero;
But still got the same error (-1)
I exppected a correct C# DLLImport for SP_API enum sp_return sp_get_port_by_name(const char *portname, struct sp_port **port_ptr);

Related

P/Invoke runtime STD error not redirected to file

I am working with a C++ DLL that I access with P/Invokes from C# code. The problem is I cannot redirect the std error that happens in the unmanaged side to a file. This works well if the build is in DEBUG but when the build is in RELEASE the STD logfile-unmanaged does not contain the error but contains and does not close the application, it keeps running like there was no error:
Before Error
After Error
In C++ the STD error is redirected to a file like this:
extern "C" __declspec(dllexport) void RedirectStd()
{
int fileNO = _fileno(stderr);
_close(fileNO);
int file = _open("logfile-unmanaged", O_CREAT | O_RDWR, 0644);
_dup2(file, fileNO);
}
A runtime error in C++ is generated like this:
extern "C" __declspec(dllexport) void DoException()
{
fprintf(stderr, "Before Error\n");
int a = 0;
int b = 10 / a;
fprintf(stderr, "After Error\n");
}
In C# I'm calling both of those methods:
[DllImport("TestError.dll", CallingConvention = CallingConvention.Cdecl, SetLastError = true)]
internal static extern void RedirectStd();
[DllImport("TestError.dll", CallingConvention = CallingConvention.Cdecl, SetLastError = true)]
internal static extern void DoException();
My Main function:
[HandleProcessCorruptedStateExceptions]
[SecurityCritical]
static void Main(string[] args) {
Console.WriteLine("===== REDIRECT =====");
Console.WriteLine("Native/Unmanaged exception redirected to logfile-unmanaged.");
RedirectStd();
Console.WriteLine("Native/Unmanaged std error redirected.");
Console.WriteLine("");
Console.WriteLine("===== EXCEPTION =====");
DoException();
Console.WriteLine("Waiting for program to crash");
do {
} while (true);
}
EDIT:
C# console application:
program.cs
using System;
using System.Runtime.ExceptionServices;
using System.Runtime.InteropServices;
using System.Security;
namespace ConsoleWithErrors {
class Program {
[DllImport("TestError.dll", CallingConvention = CallingConvention.Cdecl, SetLastError = true)]
internal static extern void RedirectStd();
[DllImport("TestError.dll", CallingConvention = CallingConvention.Cdecl, SetLastError = true)]
internal static extern void DoException();
[HandleProcessCorruptedStateExceptions]
[SecurityCritical]
static void Main(string[] args) {
RedirectStd();
DoException();
Console.WriteLine("Waiting for program to crash");
do {
} while (true);
}
}
}
The console application stays opened like there was no error on the unmanaged side.
This works well if the build is in DEBUG but when the build is in RELEASE the STD logfile-unmanaged does not contain the error
It's probably optimized out, since you don't use the result of the division by zero. Try something like this instead:
extern "C" __declspec(dllexport) int DoException()
{
fprintf(stderr, "Before Error\n");
int a = 0;
int b = 10 / a;
fprintf(stderr, "After Error\n");
return b;
}
This will break the result out of internal linkage and force the compiler to emit the code.
You seem to be new to C# P/Invoke however, so here's a few tips:
the SetLastError attribute instructs the marshaller that the function will use the Win32 API SetLastError to set its error state, and your functions absolutely do not do so. You should remove it, because lying to the marshaller is never a recipe for success.
the SecurityCritical attribute has to do with process elevation between trust levels. Again, your application has nothing to do with that and should be removed.
the HandleProcessCorruptedStateExceptions attribute is deprecated since .Net Core (and including .Net 5). So if you're relying on it, you're nearing the end-of-life of your program, and it seems you barely even started writing it. The good news is that division by 0 isn't one of the exceptions that require this attribute to be processed, so again you should remove it!

Using a C++ DLL in C# when signature includes BYTE**

I am working on a C# project, and am wrapping a C++ DLL for use in the project. I have captured this behaviour in a test project, with function calls renamed to protect the innocent.
Everything seems fine, except for one type of function that I am having a hard time understanding. The signature for that function in the DLL header is:
int32_t DoTheWork(BYTE **dest, BYTE *Src, int32_t szSrc);
My wrapper receives the Src byte array with no problem (easily tested since this is just a char string). The return dest paramater is not quite so simple.
I have tried different ways to pass the dest parameter from C# to the wrapped function, but when I receive it back, either the dest byte array in C# has a length of 1 (instead of the expected 32) bytes, or the return crashes. The instance I have below is a crash. I need to understand how to pass a byte array as reference, copy results into that byte array, and return it with the full complement of bytes without crashing. I have spent more than a day on this looking online and making changes to code, but am still not getting it to work correctly.
Also, would it be better for me to just take the pointer created in the C++ DLL all the way up into the C# calling function, instead of copying the values into the C# byte array in my C++ wrapper? If so, how do I correctly go about cleaning up that memory inside of C#?
I am using VS2010 on Win8. Here's my code:
** OriginalCPPClass.h for OriginalCPPDll.dll
class OriginalCPPClass {
public:
OriginalCPPDLLClass();
virtual ~OriginalCPPDLLClass();
int32_t DoTheWork(BYTE **dest, BYTE *Src, int32_t szSrc);
};
** WrapperDLL.cpp (no accompanying .h file)
#include "CytoCrypto.h"
extern "C"
{
#define WRAPPERCLASS_EXPORT __declspec(dllexport)
WRAPPERCLASS_EXPORT OriginalCPPClass* Wrap_Create()
{
return new OriginalCPPClass();
}
WRAPPERCLASS_EXPORT void Wrap_Destroy(OriginalCPPClass* pObj)
{
delete pObj;
}
WRAPPERCLASS_EXPORT int32_t __cdecl Wrap_DoTheWork(OriginalCPPClass* pObj, BYTE **pDest, BYTE *pSrc, int32_t szSrc)
{
BYTE *result = NULL;
int32_t sz = pObj->DoTheWork(&result, pSrc, szSrc);
*(result+sz) = '\0';
if (sz > 0)
{
memcpy(pDest, result, sz );
}
return (sz >= 0) ? sz : 0;
}
}
** Program.cs
using System;
using System.Text;
using System.Runtime.InteropServices;
namespace ConsoleApplication1
{
class Program
{
[DllImport("OriginalCPPDll.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr Wrap_Create();
[DllImport("OriginalCPPDll.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void Wrap_Destroy(IntPtr pObj);
[DllImport("OriginalCPPDll.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern Int32 Wrap_DoTheWork(IntPtr pObj, out IntPtr pDest, byte[] src, Int32 szSrc);
static void Main(string[] args)
{
string src = "this is the source string";
IntPtr pnt = Marshal.AllocHGlobal(1000);
byte[] bytearray = new byte[1000];
byte[] srcBytes = Encoding.ASCII.GetBytes(src);
Int32 szSrc = srcBytes.Length;
IntPtr obj = Wrap_Create();
Int32 size = Wrap_DoTheWork(obj, out pnt, srcBytes, szSrc);
Marshal.Copy(pnt, bytearray, 0, size);
Wrap_Destroy(obj);
Marshal.Copy(pnt, bytearray, 0, size);
}
}
}
Error dialog says:
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.
I finally found the right way to do what I needed. Turns out that just passing a pre-sized byte array to collect the results is good (unfortunately have to pass a large enough buffer to handle the result, the length of which is unknown ahead of time but will never be greater than twice the original size). Then in the wrapper class, I receive a newly allocated chunk of memory when I call the original C++ library, and copy the contents into my "BYTE* dest" parameter (no longer passed as BYTE**), and delete the chunk received from the library. I just leave it up to automatic marshaling to handle the transfer of the array in both directions. Works perfectly, and the string of bytes I am getting back is proven to be correct. Thanks for all the help.
Here's my final code:
** OriginalCPPClass.h for OriginalCPPDll.dll
class OriginalCPPClass {
public:
OriginalCPPDLLClass();
virtual ~OriginalCPPDLLClass();
int32_t DoTheWork(BYTE **dest, BYTE *Src, int32_t szSrc);
};
** WrapperDLL.cpp
#include "CytoCrypto.h"
extern "C"
{
#define WRAPPERCLASS_EXPORT __declspec(dllexport)
WRAPPERCLASS_EXPORT OriginalCPPClass* Wrap_Create()
{
return new OriginalCPPClass();
}
WRAPPERCLASS_EXPORT void Wrap_DestroyPtr(BYTE* ptr)
{
HeapFree(GetProcessHeap(), 0, ptr);
}
WRAPPERCLASS_EXPORT int32_t __cdecl Wrap_DoTheWork(OriginalCPPClass* pObj, BYTE *pDest, BYTE *pSrc, int32_t szSrc)
{
BYTE *result = NULL;
int32_t sz = pObj->DoTheWork(&result, pSrc, szSrc);
if (sz > 0)
{
memcpy(pDest, result, ret+1);
}
if (result)
pObj->DestroyPtr(result);
return (sz >= 0) ? sz : 0;
}
}
** Program.cs
using System;
using System.Text;
using System.Runtime.InteropServices;
namespace ConsoleApplication1
{
class Program
{
[DllImport("OriginalCPPDll.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr Wrap_Create();
[DllImport("OriginalCPPDll.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void Wrap_Destroy(IntPtr pObj);
[DllImport("OriginalCPPDll.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern Int32 Wrap_DoTheWork(IntPtr pObj, byte[] dest, byte[] src, Int32 szSrc);
static void Main(string[] args)
{
string srcStr = "this is the source string";
byte[] resBytes = new byte[srcStr.Length*2];
byte[] srcBytes = Encoding.ASCII.GetBytes(srcStr);
Int32 srcSize = srcBytes.Length;
IntPtr obj = Wrap_Create();
Int32 size = Wrap_DoTheWork(obj, resBytes, srcBytes, srcSize);
Wrap_Destroy(obj);
}
}
}

Why does accessing unmanaged memory cause a System.AccessViolationException? [duplicate]

This question already has an answer here:
Use XGBoost DLL from c# via p/invoke
(1 answer)
Closed 6 years ago.
I'm trying to use XGBoost's dll (libxgboost.dll) to create a DMatrix (which is like a 2D array) and get how many columns it has. It runs fine until it throws a System.AccessViolationException at the int cols = ... line in the code below:
using System;
using System.Runtime.InteropServices;
namespace basicXgboost
{
class Program
{
[DllImport("../../libs/libxgboost.dll", CharSet = CharSet.Auto)]
public static extern int XGDMatrixCreateFromFile([MarshalAs(UnmanagedType.LPStr)] string file, int silent, IntPtr outputPtr);
[DllImport("../../libs/libxgboost.dll", CharSet = CharSet.Auto)]
public static extern int XGDMatrixNumCol(IntPtr dmatrixPtr, IntPtr dmatrixColumnsPtr);
static void Main(string[] args)
{
IntPtr dmatrixPtr = Marshal.AllocHGlobal(1000000);
IntPtr dmatrixColumnsPtr = Marshal.AllocHGlobal(10);
int result = XGDMatrixCreateFromFile("../../libs/test.txt", 0, dmatrixPtr);
int cols = XGDMatrixNumCol(dmatrixPtr, dmatrixColumnsPtr);
Marshal.FreeHGlobal(dmatrixPtr);
Marshal.FreeHGlobal(dmatrixColumnsPtr);
}
}
}
Why does accessing unmanaged memory allocated with XGDMatrixNumCol(dmatrixPtr, dmatrixColumnsPtr) cause a System.AccessViolationException?
One possibility might be that I'm using pinvoke incorrectly for these functions. Below are the definitions for each dll function I use:
XGDMatrixCreateFromFile()
/*!
* \brief load a data matrix
* \param fname the name of the file
* \param silent whether print messages during loading
* \param out a loaded data matrix
* \return 0 when success, -1 when failure happens
*/
XGB_DLL int XGDMatrixCreateFromFile(const char *fname,
int silent,
DMatrixHandle *out);
XGDMatrixNumCol()
/*!
* \brief get number of columns
* \param handle the handle to the DMatrix
* \param out The output of number of columns
* \return 0 when success, -1 when failure happens
*/
XGB_DLL int XGDMatrixNumCol(DMatrixHandle handle,
bst_ulong *out);
Here is the repo for my project. I'm using Visual Studio Enterprise 2015 . It's built in "Debug" mode (targeting x64) on Windows 10 Pro (64-bit). x64 binaries for libxgboost.dll can be found here. Although the linked repo does contain a copy of libxgboost.dll.
Here's the solution I've got thanks to NineBerry's answer.
using System;
using System.Runtime.InteropServices;
namespace basicXgboost
{
class Program
{
[DllImport("../../libs/libxgboost.dll", CharSet = CharSet.Auto)]
public static extern int XGDMatrixCreateFromFile([MarshalAs(UnmanagedType.LPStr)] string file, int silent, out IntPtr outputPtr);
[DllImport("../../libs/libxgboost.dll", CharSet = CharSet.Auto)]
public static extern int XGDMatrixNumCol(IntPtr dmatrixPtr, out ulong dmatrixColumnsPtr);
static void Main(string[] args)
{
IntPtr dmatrixPtr;
ulong dmatrixColumns;
int result = XGDMatrixCreateFromFile("../../libs/test.txt", 0, out dmatrixPtr);
int cols = XGDMatrixNumCol(dmatrixPtr, out dmatrixColumns);
}
}
}

Need to use a 'block' of C++ code in C# app

I was given a block of c++ code that looks like it was from a c++ app that makes use of
Shared Memory for sending messages to other programs.
The c++ code has no #include or anything yet. I was given the code to use in my C# application and I am pretty stuck. I somewhat understand what the code does, but I don't know it well enough to translate it to C# as I am pretty new to coding.
My question is, what is the easiest way to be able to use the functionality of the code in my project? The end result is to send messages to another program, that will in turn do something that I'm not worried about.
I have tried creating different c++ projects and file types in my solution to link them using a reference later on, but I can never get it to compile properly.
Please let me know if you have some advice or a good place to look. I can always provide more information.
Code (I had to remove comments, sorry):
UINT WM_HELO_ZOOM_XYZ = RegisterWindowMessage("WM_HELO_ZOOM_XYZ");
int HELO_Broadcast_Zoom_Message(
double dbX,
double dbY,
double dbZ,
UINT uMessage=WM_HELO_ZOOM_XYZ) {
#ifndef HELO_
typedef struct {
UINT uMajVersion;
UINT uMinVersion;
DWORD dwReserved;
double dbX;
double dbY;
double dbZ;
} HELOCoordsStruct;
#endif
char *szSharedMemory = "HELO-_Coords";
char szErr[_MAX_PATH*3];
HANDLE hMem = OpenFileMapping(FILE_MAP_WRITE, FALSE, szSharedMemory);
if (NULL == hMem) {
return(0);
}
void *pvHead = MapViewOfFile(hMem, FILE_MAP_WRITE, 0,0,0);
if (NULL == pvHead) {
CloseHandle(hMem);
sprintf(szErr, "Unable to view", szSharedMemory);
AfxMessageBox(szErr, MB_OK|MB_ICONSTOP);
return(0);
}
HELOCoordsStruct *pHELOCoords = (HELOCoordsStruct *)pvHead;
BOOL bVersionOk=FALSE;
if (1 == pHELOCoords->uMajorVersion) {
if (WM_HELO_ZOOM_XYZ==uMessage) {
pHELOCoords->dbX = dbX;
pHELOCoords->dbY = dbY;
pHELOCoords->dbZ = dbZ;
}
bVersionOk=TRUE;
}
else {
sprintf(szErr, "Unrecognized HELO- shared memory version: %d.%d", pHELOCoords->uMajVersion, pHELOCoords->uMinVersion);
AfxMessageBox(szErr, MB_OK);
}
if (NULL != hMem) CloseHandle(hMem);
UnmapViewOfFile(pvHead);
if (bVersionOk) {
PostMessage(HWND_BROADCAST,uMessage,0,0);
return(1);
}
else return(0);
}
EDIT: The feedback has been completely unreal. I must say that the community sure spoils folks around here.
Thanks,
Kevin
I think you have three options:
Create a Managed C++ project of type Class Library, put the code in it, make a reference from your main app to this project.
Create an unmanaged C++ DLL project, put the code in a function (or functions), export the function(s) (using .def file), and build the project. Use the functions from that dll using [DllImport] attribute. (See here and here)
Convert the code to C#. This will require some knowledge of unmanaged code, Win32 and P/Invoke (See here and here). And as I see your code, it is takes a little time!
Here is your converted code (option 3):
using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;
namespace UnmanagedBlock
{
public class ConvertedClass
{
public uint WM_HELO_ZOOM_XYZ = RegisterWindowMessageA("WM_HELO_ZOOM_XYZ"); // Your code uses the ANSI string
int HELO_Broadcast_Zoom_Message(
double dbX, double dbY, double dbZ, uint uMessage) // Porting the default value for 'uMessage' is not possible
{
string szSharedMemory = "HELO-_Coords";
IntPtr hMem = OpenFileMapping(FileMapAccessRights.Write, FALSE, szSharedMemory);
if (IntPtr.Zero == hMem)
return 0;
IntPtr pvHead = MapViewOfFile(hMem, FileMapAccessRights.Write, 0, 0, UIntPtr.Zero);
if (IntPtr.Zero == pvHead)
{
CloseHandle(hMem);
MessageBox.Show(
"Unable to view " + szSharedMemory, // Your code does not concat these two strings.
"Error", MessageBoxButtons.OK, MessageBoxIcon.Stop);
return 0;
}
HELOCoordsStruct pHELOCoords = new HELOCoordsStruct();
Marshal.PtrToStructure(pvHead, pHELOCoords);
int bVersionOk = FALSE;
if (1 == pHELOCoords.uMajVersion) // I think it had a typo (it was uMajorVersion)
{
if (WM_HELO_ZOOM_XYZ == uMessage)
{
pHELOCoords.dbX = dbX;
pHELOCoords.dbY = dbY;
pHELOCoords.dbZ = dbZ;
}
Marshal.StructureToPtr(pHELOCoords, pvHead, false);
bVersionOk = TRUE;
}
else
{
MessageBox.Show(
"Unrecognized HELO- shared memory version: " +
pHELOCoords.uMajVersion.ToString() + "." + pHELOCoords.uMinVersion.ToString());
}
if (IntPtr.Zero != hMem)
CloseHandle(hMem);
UnmapViewOfFile(pvHead);
if (bVersionOk == TRUE)
{
PostMessage(HWND_BROADCAST, uMessage, 0, 0);
return 1;
}
else
return 0;
}
[StructLayout(LayoutKind.Sequential)]
private class HELOCoordsStruct
{
public uint uMajVersion;
public uint uMinVersion;
public uint dwReserved;
public double dbX;
public double dbY;
public double dbZ;
}
[DllImport("user32", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Unicode)]
public static extern uint RegisterWindowMessageW([In]string lpString);
[DllImport("user32", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Ansi)]
public static extern uint RegisterWindowMessageA([In]string lpString);
[DllImport("kernel32", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Auto)]
public static extern IntPtr OpenFileMapping(FileMapAccessRights dwDesiredAccess, int bInheritHandle, [In]String lpName);
[DllImport("kernel32", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Auto)]
public static extern IntPtr MapViewOfFile(IntPtr hFileMappingObject, FileMapAccessRights dwDesiredAccess, uint dwFileOffsetHigh, uint dwFileOffsetLow, UIntPtr dwNumberOfBytesToMap);
[DllImport("kernel32", CallingConvention = CallingConvention.StdCall)]
public static extern int UnmapViewOfFile(IntPtr lpBaseAddress);
[DllImport("kernel32", CallingConvention = CallingConvention.StdCall)]
public static extern int CloseHandle(IntPtr hObject);
[DllImport("user32.dll")]
public static extern IntPtr PostMessage(IntPtr hWnd, uint msg, int wParam, int lParam);
public const int FALSE = 0, TRUE = 1;
public enum FileMapAccessRights : uint
{
Write = 0x2,
Read = 0x4,
Execute = 0x20,
}
public const IntPtr HWND_BROADCAST = (IntPtr)0xffff;
}
}
I've done an exact conversion and I think that it should work fine, however I have not tested it.
Let me know if it works.
You can dump the C++ code into a Visual C++ project and build that. When you build it, go into the project settings and select the option that generates tlb files (It's a proxy class for c++/.net interop, I can't remember the name of the option).
Once you have this, you can add a reference to the tlb interop assembly from a C# project.
Also, look here for a Microsoft example http://msdn.microsoft.com/en-us/library/fx82zhxa.aspx
Chances are that if you post the code here, some nice person will port the code from C++ to C# for you. However, for future reference when dealing with using native C++ code from within a .NET application, you can use the InteropServices of the .NET framework to reference native functions in native dlls.
To do this requires a few steps on both the C++ and C# side of things. Firstly you need to build your entry point as an exported function in C++.
For example, say I wanted to write a trivial C++ function to add 2 numbers together and then call it from a C# app, I would have to do the following:
Step 1: Writing the C++ function.
In order for external sources to locate your functions, you need to let the compiler know that the function is to be 'exported'. A point to note is that if you're calling other functions from within your exported function, you do not need to mark them all as exported.
So let's write the "add" function in C++:
#define DLLEXPORT extern "C" declspec(dllexport)
DLLEXPORT int __cdecl add(int x, int y)
{
return (x + y);
}
The first line defines a macro we'll use to mark exported methods. The extern "C" part tells the compiler to avoid mangling the exported name of the function (so it will always be 'add', and not something like #YZadd_), next comes the function definition marked as a DLLEXPORT. Before I continue there's one more point on the 'name mangling' in exported functions, and that is for functions declared __stdcall or any of its variations (WINAPI..etc). Functions that are marked for exporting and declared with extern "C" with the calling convention __stdcall will always be appended with #X where X is the number of bytes of the function paramaters (so for the above example if add was declared __stdcall then the exported function name would be add#8. Just keep this in mind if C# ever has trouble locating your functions.
Now, the C++ side of things is done, compile that as a DLL and move over to C#.
In C# it's rather straightforward to import external functions. First you'll need to reference the InteropServices namespace:
using System.Runtime.InteropServices;
And then you will need to make a [DllImport] declaration:
[DllImport(#"path to your C++ dll here")]
public static extern int add(int x, int y) //make sure the function definition matches.
Provided the function names match, that should be all that's required to import the function. Now you can call add just as you would any normal function in C#
int x = 5;
int y = 10;
int z = add(x, y); //z should be 10
That about concludes how to simply export C++ functions and call them from a C# application.
If you can't get the C++ code to work as-is then there's no point trying to graft it into your C# app.
Figure out the C++ code first (read MSDN documentation for the APIs used, ask the person that gave you the code, post specific questions). Once you understand it better and can make it work then you'll have a better chance of figuring out the best way to do what's needed in C#.

Can't find SDL_LoadBMP() entry point in 'SDL.DLL' with PInvoke

I'm trying to marshal data between SDL and my C# .NET program. The first few calls that I make into SDL.DLL work fine, inasmuch as I get no error and my Windows console app does open an empty application window:
My_SDL_Funcs.SDL_Init(0x0000FFFF); // SDL_INIT_EVERYTHING
IntPtr scrn = My_SDL_Funcs.SDL_SetVideoMode(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_BPP, 0x00000000); // SDL_SWSURFACE
screen = (SDL_Surface)Marshal.PtrToStructure(scrn, typeof(SDL_Surface));
My_SDL_Funcs.SDL_WM_SetCaption("Hello World", null);
// ...
When I try to call SDL_LoadBMP() however, I get this runtime error:
Unable to find an entry point named 'SDL_LoadBMP' in DLL 'SDL'.
The SDL doc says that SDL_LoadBMP takes a const char* file name and returns a pointer to a SDL_Surface struct.
I first tried declaring the PInvoke as:
[DllImport("SDL", CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr SDL_LoadBMP([MarshalAs(UnmanagedType.LPWStr)] string file);
When this didn't work, I tried:
public static extern IntPtr SDL_LoadBMP(IntPtr file);
and used:
IntPtr fn = Marshal.StringToHGlobalAnsi(filename);
IntPtr loadedImage = My_SDL_Funcs.SDL_LoadBMP(fn);
Assuming that that the function actuall does exist in this library (SDL.DLL version 1.2.14), am I using the wrong invocation for a const char*?
I downloaded the DLL version you are using, and could not find an export for SDL_LoadBMP.
There is a SDL_LoadBMP_RW, though, so you could rig up your own helper call like so:
private const string SDL = "SDL.dll";
[DllImport(SDL, CallingConvention = CallingConvention.Cdecl), SuppressUnmanagedCodeSecurity]
public static extern IntPtr SDL_LoadBMP_RW(IntPtr src, int freesrc);
[DllImport(SDL, CallingConvention = CallingConvention.Cdecl), SuppressUnmanagedCodeSecurity]
public static extern IntPtr SDL_RWFromFile(string file, string mode);
public static IntPtr SDL_LoadBMP(string file)
{
return SDL_LoadBMP_RW(SDL_RWFromFile(file, "rb"), 1);
}
UPDATE:
I had a look through the code, and the call you are looking for is defined as a macro, so that is why you can't call it directly. Using the above code basically does the same thing as the macro defintion:
#define SDL_LoadBMP(file) SDL_LoadBMP_RW(SDL_RWFromFile(file, "rb"), 1)

Categories