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

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);
}
}
}

Related

Sending string from C# to C++ dll file

I have a function in socket.dll library as below:
__declspec(dllexport) string GetPib(const wchar_t* pib_key_chars)
{
wstring ws(pib_key_chars);
std::string pib_key(ws.begin(), ws.end());
cout << "In GetPib" << " and pib_key in string=" << pib_key << endl;
Pib pb;
return std::to_string(pb.Get(phyFskTxPacket, 0));
}
When i use "dumpbin /exports socket.dll" to check the GetPib function of socket.dll signature it returns
1 0 00011370 ?GetPib##YA?AV?$basic_string#DU?$char_traits#D#std##V?
$allocator#D#2##std##V12##Z = #ILT+875(?GetPib##YA?AV?$basic_string#DU?
$char_traits#D#std##V?$allocator#D#2##std##V12##Z)
I am using the same signature in the C# project(WindowsFormsApp1) to call/invoke GetPib function.
Below is the C# source code to invoke GetPib function:
namespace WindowsFormsApp1
{
public partial class Form1 : Form
{
[DllImport("socket.dll", EntryPoint = "?GetPib##YA?AV?$basic_string#DU?$char_traits#D#std##V?$allocator#D#2##std##PB_W#Z", CallingConvention = CallingConvention.Cdecl)]
public static extern string GetPib([MarshalAs(UnmanagedType.LPStr)] string pib_key);
public Form1()
{
InitializeComponent();
}
private void GetPib_button_Click(object sender, EventArgs e)
{
String str = pib_id_tbox.Text;
pib_uvalue_tbox.Text = GetPib(str);
}
}
When I invoke GetPib function like GetPib(0x1004xxx4), it invokes the socket.dll's GetPib function but the value is different with special characters
str=0x10040004 tr=268697604-----> in C# file
In GetPib and pib_key in string=h䤰„------> in C++ .dll file
How to fix this issue.
First of all, wrap the unmanaged function declaration in extern "C" to eliminate the mangling and rewrite the unmanaged function to something more sensible for interop with managed code.
extern "C" {
__declspec(dllexport) int GetPib(const char* pib_key_chars, char *result, int len) {
cout << "In GetPib" << " and pib_key in string=" << pib_key_chars << endl;
Pib pb;
string packet = std:to_string(pb.Get(phyFskTxPacket, 0);
int n = (int)packet.length;
if (n < len) {
packet.copy(result, n, 0);
result[n] = '\0';
}
return n + 1;
}
}
In this rewrite, we are passing the managed string, an allocated buffer to hold the result, and the length of that buffer. This version handles char* strings. If you need to use wchar_t* wide strings, it should be trivial to convert it. The main thing to note is that we are not using any C++ types as parameters to the function, since the interop marshaller has no way of knowing how to deal with those types. We are only using language primitives.
The managed P/Invoke signature would look like this:
[DllImport("socket.dll", EntryPoint="GetPib")]
public static extern int GetPib(([MarshalAs(UnmanagedType.LPStr)] string keyChars, IntPtr buffer, int len);
To call it:
private void GetPib_button_Click(object sender, EventArgs e)
{
int needed = GetPib(pib_id_tbox.Text, IntPtr.Zero, 0);
IntPtr p = Marshal.AllocHGlobal(needed);
GetPib(pib_id_tbox.Text, p, needed);
pib_uvalue_tbox.Text = Marshal.PtrToStringAuto(p);
Marshal.FreeHGlobal(p);
}
Here we are allocating unmanaged memory so that the GC won't move it around while we are interoperating with unmanaged code. The unmanaged function will fill the buffer with the resulting char*, which we convert back into a managed string with PtrToStringAuto(). Then we free the unmanaged memory.
I followed this link
https://answers.unity.com/questions/142150/passing-strings-to-and-from-a-c-dll.html
I modified GetPib api in C++
__declspec(dllexport) string GetPib(const wchar_t* pib_key_chars)
to
extern BSTR __declspec(dllexport) GetPib(BSTR pib_key_chars)
Modified C# file from
[DllImport("socket.dll", EntryPoint = "?GetPib##YA?AV?$basic_string#DU?$char_traits#D#std##V?$allocator#D#2##std##PB_W#Z", CallingConvention = CallingConvention.Cdecl)]
public static extern string GetPib([MarshalAs(UnmanagedType.LPStr)] string pib_key);
to
[DllImport("socket.dll", EntryPoint = "?GetPib##YAPA_WPA_W#Z", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
[return: MarshalAs(UnmanagedType.BStr)]
public static extern string GetPib(string str);
and it solved my problem!!!!!!

C# Marshalling (C# call C++ DLL)

Could you guys please help me solve the following issue? I have a C++ function dll, and it will be called by another C# application. One of the functions I needed is as follow:
unsigned long makeArray(unsigned char* sendArr, unsigned long sendArrLen, unsigned char *recvArr, unsigned long *recvArrLen);
I wrote the following code in C#:
[DllImport("test.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern ulong makeArray(byte[] sendArr, ulong sendArrLen, byte[] recvArr, ulong recvArrLen);
private byte[] MakeArray()
{
byte[] arrSend = new byte[] { 0x00, 0x12, 0x34 };
ulong nRecvArrLen = 0;
byte[] arrRecv = null; // assign in c++ dll function (variable size)
if(makeArray(arrSend, (ulong)arrSend.Length, arrRecv, nRecvArrLen) == 1)
{
return arrRecv;
}
return null;
}
Unfortunately, the above code is not working...
May I know how can I pass a pointer-to-pointer to the C++ func? If it is not possible, is there any workaround?
Thank you.
unsigned long in MSVC is a 32-bit unsigned integer, so you should map it to the System.UInt32 .NET type, corresponding to the uint keyword in C#.
C# ulong is an unsigned 64-bit integer, corresponding to MSVC's unsigned __int64 or unsigned long long.
The unsigned long *recvArrLen parameter should me mapped using ref in the C# PInvoke declaration, as you have a level of indirection via pointer.
It also seems that the arrRecv array parameter should be allocated by the caller (C# in your case), and filled by the DLL function.
If the DLL function allocates the buffer, you should add another level of indirection (unsigned char **recvArr), and you should provide a way to release the allocated memory (e.g. the DLL should export a release function as well).
I would try with something like this for PInvoke:
[DllImport("test.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern uint makeArray(
byte[] sendArr,
uint sendArrLen,
[Out] byte[] recvArr,
ref uint recvArrLen
);
Where is your 'test.dll'? I think it is a path problem...
The file must be located at the one of following directories..
[%SystemRoot%] (Windows directory)
[%SystemRoot%]\system32\(32 bit) or
[%SystemRoot%]\sysWOW64\(64 bit)
The same location with your executable file
PATH variable
Or it can be a type mismatch ... refer to the [site].
I matched the ulong type of csharp to unsigned __int64 in c/c++ on windows.
Declaration of the C# code is a little bit changed.
[DllImport(#"testdll.dll", CallingConvention = CallingConvention.Cdecl)]
static extern ulong makeArray
(
byte[] sendArr,
ulong sendArrLen,
[Out] byte[] recvArr,
ref ulong recvArrLen
);
Here are the testdll.cpp abd testdll.h i tested
#include "testdll.h"
unsigned __int64 makeArray(
unsigned char* sendArr,
unsigned __int64 sendArrLen,
unsigned char *recvArr,
unsigned __int64 *recvArrLen
)
{
int i;
for(i=0; i < sendArrLen; i++)
{
recvArr[i] = sendArr[i];
}
memcpy(recvArrLen, &sendArrLen, sizeof(unsigned __int64));
return i;
}
testdll.h code.
#pragma once
#ifdef EXPORT_TESTDLL
#define TESTDLL_API __declspec(dllexport)
#else
#define TESTDLL_API __declspec(dllimport)
#endif
extern "C" TESTDLL_API unsigned __int64 makeArray(
unsigned char* sendArr,
unsigned __int64 sendArrLen,
unsigned char *recvArr,
unsigned __int64 *recvArrLen
);
Finally, C# code of console application as follows, call the native dll function in c++ - testdll.dll
print items on the console.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApplication2
{
class Program
{
[DllImport(#"testdll.dll", CallingConvention = CallingConvention.Cdecl)]
static extern ulong makeArray(byte[] sendArr, ulong sendArrLen, [Out] byte[] recvArr, ref ulong recvArrLen);
static byte[] MakeArray()
{
byte[] arrSend = new byte[] { 0x00, 0x12, 0x34 };
ulong nRecvArrLen = 0;
ulong ret = 0;
byte[] arrRecv = new byte[3]; // assign in c++ dll function (variable size)
try
{
if ((ret = makeArray(arrSend, (ulong)arrSend.Length, arrRecv, ref nRecvArrLen)) > 0)
{
if(arrRecv != null)
Console.WriteLine("nRecvArrLen2============>" + arrRecv.Length);
return arrRecv;
}
}
catch (DllNotFoundException dne)
{
Console.WriteLine("============> dll not found....");
}
return null;
}
static void Main(string[] args)
{
byte[] retbytes = MakeArray();
if (retbytes != null)
{
Console.WriteLine("=====LEN=======>" + retbytes.Length);
for (int i = 0; i < retbytes.Length; i++)
Console.WriteLine("====ITEM========>" + retbytes[i]);
}
else
Console.WriteLine("=====NULL=======>");
}
}
}

Passing a StringBuilder and getting back a result from a c++ dll to c#

I have the following code in C#:
[DllImport("ClassLibrary2.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.Cdecl)]
public static extern void GetString(StringBuilder buffer, ref int bufferSize);
void get()
{
openFileDialog1.ShowDialog();
int bufferSize = 32;
StringBuilder buffer = new StringBuilder(bufferSize);
GetString(buffer, ref bufferSize);
string currentString = buffer.ToString();
MessageBox.Show(currentString);
}
and the following in c++:
extern "C" __declspec(dllexport) void GetString(char* buffer, int* bufferSize){
MD5 md5;
char *returnChar = md5.digestFile(buffer);
cout << returnChar << endl;
strcpy(buffer, returnChar);
}
I'm trying to figure out why it doesn't work.. I've tried many many many things and neither seem to work. This is probably the only thing that doesn't crash my C# program directly so I am left to using it. I need to get a string but I can do with a StringBuilder too.
Yes, my program does work as the cout<<returnChar<<endl; works and displays what I need to see.
Yes, I have tried marshaling it using the calling convention stdcall and many others... what is the simplest way to pass and get a string back?
CharSet = CharSet.Auto is not compatible with void GetString(char* buffer, int* bufferSize). Use CharSet = CharSet.Ansi to make it work. CharSet.Auto is only for importing system library functions that exist in both Ansi and Unicode variants and having .net pick the right one to call.
Why not just do this?
public string CalculateMD5Hash(byte[] input)
{
MD5 md5 = System.Security.Cryptography.MD5.Create();
byte[] hash = md5.ComputeHash(input);
// Convert to hex string
return string.Join("", hash.Select(h => h.ToString("X2")));
}
And call it with var hash = CalculateMD5Hash(File.ReadAllBytes("path-goes-here"))?

Importing C dll function to C#

this is my first stackoverflow post. I have been stucking in this issue for days. I try to import the usbi2cio.dll which is a C Dll to a C# based project. I went through most of the similar posts within the site, while still I couldn't fix my issue, since my case might be little different.
So here is the original definition of the API and related struct as a parameter:
LONG _stdcall DAPI_ReadI2c(HANDLE hDevInstance, I2C_TRANS * TransI2C);
typedef struct _I2C_TRANS {
BYTE byTransType;
BYTE bySlvDevAddr;
WORD wMemoryAddr;
WORD wCount;
BYTE Data[256];
}I2C_TRANS, *PI2C_TRANS;
//In my C# code, I did the translation like this:
[StructLayoutAttribute(LayoutKind.Sequential), Serializable]
public struct I2C_TRANS
{
public byte byTransType;
public byte bySlvDevAddr;
public ushort wMemoryAddr;
public ushort wCount;
[MarshalAsAttribute(UnmanagedType.ByValArray, SizeConst = 256, ArraySubType = UnmanagedType.I1)]
public byte[] Data;
public I2C_TRANS(int size)
{
Data = new byte[size];
this.byTransType = 0x00;
this.bySlvDevAddr = 0x00;
this.wMemoryAddr = 0;
this.wCount = 0;
}
};
public I2C_TRANS TransI2C = new I2C_TRANS(256);
public IntPtr[] hDevice = new IntPtr[DAPI_MAX_DEVICES];
...
TransI2C.byTransType = byTransType;
TransI2C.bySlvDevAddr = bySlvDevAddr;
TransI2C.wMemoryAddr = wMemoryAddr;
TransI2C.wCount = wCount;// no larger than 64
...
if((hDevice[0] = DAPI_OpenDeviceInstance(devName, 0)) != INVALID_HANDLE_VALUE)
//the returned lReadCnt should be equal to wCount.
Public int lReadCnt = DAPI_ReadI2c(hDevice[0], ref TransI2C);
For some reason, the struct in the read I2C transaction can't be well passed through, As a result, the function returns 0 value without errors(what I expect is the same value with wCount). For some other similar API and struct, it works well. So what might be the cause for this issue?
//Here is the P/Invoke declaration:
[DllImportAttribute("UsbI2cIo.dll", EntryPoint = "DAPI_ReadI2c", CallingConvention = CallingConvention.StdCall)]
public static extern int DAPI_ReadI2c(IntPtr hDevInstance, ref I2C_TRANS TransI2C);
I had a similar problem, and I fixed it by writing my own C library called Bridge, that would deal with the complex C API but expose simple methods that could be interfaced with C# easily.
For example in the method below I could pass a byte array to my C code.
From a C# point of view I would only deal with byte, int16 or int32 or byte array.
[DllImport(DLL)]
private static extern System.Int32 __SPI_Helper_Write(IntPtr lpBuffer, System.Int32 len);

Calling a C DLL from a C# Program

I need to pass a pointer to a structure to my DLL, any ideas how would I go about doing that?
In my C DLL:
typedef struct
{
int length;
unsigned char *value;
} Sample;
__declspec(dllexport) void __stdcall helloWorld( Sample *sample );
In my C# code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
namespace CSharpConsole
{
class Program
{
[StructLayout(LayoutKind.Sequential, Pack = 1)]
private struct Sample
{
public Int32 length;
// What Should I Declare Here?
}
[DllImport("C:\\CTestDLL.dll")]
private static extern void helloWorld( Sample sample ); // How would I make this a pointer?
void HelloWorld()
{
Sample sample = new Sample();
sample .length = 20;
// How can I fill up the values of value?
helloWorld( sample ); // How should I pass it inside here
return;
}
static void Main(string[] args)
{
Program program = new Program();
program.HelloWorld();
}
}
}
To pass a pointer to a value type into a P/Invoke function just declare the parameter as a ref or out. This implicitly takes a reference to the parameter and passes that:
[DllImport("C:\\CTestDLL.dll")]
private static extern void helloWorld(ref Sample sample);
Since your structure has an array in it, you'll have to take care to declare it properly for this to work. I strongly recommend, if possible, that you turn it into a fixed-length array, as it will make the marshaler much, much happier:
typedef struct
{
int length;
unsigned char value[MAX_LENGTH];
} Sample;
This becomes:
public struct Sample
{
int length;
[MarshalAs(UnmanagedType.LPArray, SizeConst = MAX_LENGTH)] byte[] value;
}
If that is not possible, then the runtime has no way of knowing how much data to marshal back; in that case, you probably will have to resort to manual marshaling of your data:
public struct Sample
{
int length;
IntPtr value;
}
var sample = new Sample();
helloWorld(ref sample);
byte[] value = new byte[sample.length];
Marshal.Copy(sample.value, value, 0, sample.Length);
However, based on your comment to another answer, it looks like you just need to get a block of bytes out of the C DLL into C#. For that you don't really need a structure at all, and eliminating it would simplify things a lot. If you just want to pass in an array and have it filled in and returned to you, try something like this:
(This assumes you have control over both C and C# code bases; if not then the ref suggestion is the way to accomplish what you need.)
// In C# code:
[DllImport(#"C:\CTestDll.dll")]
private static extern void helloWorld(
int length,
[MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0)] byte[] buffer);
byte[] buffer = new byte[1024 * 8];
helloWorld(1024 * 8, buffer);
// In C:
__declspec(dllexport) void __stdcall helloWorld(int, unsigned char *);
void helloWorld(int cb, unsigned char *buf)
{
memcpy(buf, DATASRC, cb);
}
In C, an unsigned char is, by definition, the same size as a C# byte - 8 bits, no sign. In C#, a char is actually two bytes. The runtime will automatically "convert" an unsigned char * to a byte[] for you. (There's not really a conversion at all; they are just different names for the same type.)
Default Marshaling for Value Types - gives some information on marshalling structs.
Calling Win32 DLLs in C# with P/Invoke - a little over half way down the page there's a table showing the type conversions between the standard unmanaged and managed types.
There are a few ways to pass around and convert the types.
As Michael Edenfield pointed out you can generally just use the ref keyword to indicate that a parameter should by passed by reference which is basically a pointer.
However sometimes things don't cooperate, particularly when it comes to strings or complex data types so this is where the IntPtr type comes in.
You have a couple of options for using IntPtrs.
You can create an IntPtr to a block of unmanaged memory of a specified size using:
IntPtr pointer = Marshal.AllocHGlobal(sizeOfBufferInBytes);
//Do stuff with the pointer
Marshal.FreeHGlobal(pointer); //Don't forget to release the memory
This is obviously a bit dangerous because you're manually allocating unmanaged memory.
You'll need to Marshal the data from the buffer back into a managed type, using something like Marshal.Copy(), Marshal.PtrToStructure(), Buffer.BlockCopy(), etc.
Alternatively you can create a managed object and pin it in memory while you need to, get a pointer to it and pass that to your method.
MyObject instance = new MyObject();
GCHandle gch = GCHandle.Alloc(instance, GCHandleType.Pinned);
importedMethod(gch.AddrOfPinnedObject()); //AddrOfPinnedObject() gives you an IntPtr
gch.Free(); //Release the pinned memory so the garbage collector can deal with it
This avoids the necessity for manually marshalling back to the correct data type but this is not always an option depending on the type of MyObject and whether it's blittable.
This works for me:
DLL:
typedef struct
{
int length;
unsigned char *value;
} Sample;
extern "C" __declspec(dllexport) void __stdcall helloWorld( Sample *sample )
{
MessageBoxA(NULL, (LPCSTR) sample->value, (LPCSTR) sample->value, 0);
}
C#:
class Program
{
[StructLayout(LayoutKind.Sequential, Pack = 1)]
private class Sample
{
public Int32 length;
public String value;
}
[DllImport("C:\\Users\\Kep\\Documents\\Visual Studio 2010\\Projects\\SODLL\\Debug\\DLL.dll")]
private static extern void helloWorld(Sample sample);
static void Main(string[] args)
{
Sample s = new Sample();
s.length = 10;
s.value = "Huhu";
helloWorld(s);
}
}
Important thing is to mark it as a class, not a struct in C#.
With this, you could also use constructors etc. in C#:
class Program
{
[StructLayout(LayoutKind.Sequential, Pack = 1)]
private class Sample
{
public Int32 length;
public String value;
public Sample(String s)
{
length = s.Length;
value = s;
}
}
[DllImport("C:\\Users\\Kep\\Documents\\Visual Studio 2010\\Projects\\SODLL\\Debug\\DLL.dll")]
private static extern void helloWorld(Sample sample);
static void Main(string[] args)
{
Sample s = new Sample("Huhu");
helloWorld(s);
}
}

Categories