I need to take a C++ program, load CLR and call a function in a C# library. The function I need to call takes in a COM interface as the parameter.
My problem is, the CLR hosting interface only seems to let you call a method with this signature:
int Foo(String arg)
Example, this C++ code loads CLR and runs the P.Test function in "test.exe":
ICLRRuntimeHost *pClrHost = NULL;
HRESULT hrCorBind = CorBindToRuntimeEx(NULL, L"wks", 0, CLSID_CLRRuntimeHost, IID_ICLRRuntimeHost, (PVOID*)&pClrHost);
HRESULT hrStart = pClrHost->Start();
DWORD retVal;
HRESULT hrExecute = pClrHost->ExecuteInDefaultAppDomain(L"C:\\Test.exe", L"P", L"Test", L"", &retVal);
What I need to do is call a function with this method signature (note I own the C# code, so I can change it):
void SomeFunction(IFoo interface)
Where IFoo is a com interface. I could even do what I need to if I could call a function like this:
IntPtr SomeFunction();
I could have SomeFunction construct a correct delegate then use Marshal.GetFunctionPointerForDelegate. However, I can't figure out how to make the hosting interfaces do anything other than call a function with an int func(string) signature.
Does anyone know how to call a C# function from C++ code with a different signature?
(Note I cannot use C++/CLI for this. I need to use the hosting APIs.)
Edit: I promised to update my answer to include the code for passing 64-bit values, so here's goes..
I left the original answer if someone's interested in a less complicated solution for a 32-bit system.
Note: Since you're using CorBindToRuntimeEx, which is obsolete in .net 4.0, I'll assume a .net 2.0 compliant solution using good old Win32 API.
So, in order to pass data between C# and C++ (in our case - the IntPtr of a delegate), we'll create a small Win32 DLL project, named SharedMem, with two straight-forward methods.
SharedMem.h
#pragma once
#ifdef SHAREDMEM_EXPORTS
#define SHAREDMEM_API __declspec(dllexport)
#else
#define SHAREDMEM_API __declspec(dllimport)
#endif
#define SHAREDMEM_CALLING_CONV __cdecl
extern "C" {
SHAREDMEM_API BOOL SHAREDMEM_CALLING_CONV SetSharedMem(ULONGLONG _64bitValue);
SHAREDMEM_API BOOL SHAREDMEM_CALLING_CONV GetSharedMem(ULONGLONG* p64bitValue);
}
Now for the implementation file:
SharedMem.cpp
#include "stdafx.h"
#include "SharedMem.h"
HANDLE hMappedFileObject = NULL; // handle to mapped file
LPVOID lpvSharedMem = NULL; // pointer to shared memory
const int SHARED_MEM_SIZE = sizeof(ULONGLONG);
BOOL CreateSharedMem()
{
// Create a named file mapping object
hMappedFileObject = CreateFileMapping(
INVALID_HANDLE_VALUE,
NULL,
PAGE_READWRITE,
0,
SHARED_MEM_SIZE,
TEXT("shmemfile") // Name of shared mem file
);
if (hMappedFileObject == NULL)
{
return FALSE;
}
BOOL bFirstInit = (ERROR_ALREADY_EXISTS != GetLastError());
// Get a ptr to the shared memory
lpvSharedMem = MapViewOfFile( hMappedFileObject, FILE_MAP_WRITE, 0, 0, 0);
if (lpvSharedMem == NULL)
{
return FALSE;
}
if (bFirstInit) // First time the shared memory is accessed?
{
ZeroMemory(lpvSharedMem, SHARED_MEM_SIZE);
}
return TRUE;
}
BOOL SetSharedMem(ULONGLONG _64bitValue)
{
BOOL bOK = CreateSharedMem();
if ( bOK )
{
ULONGLONG* pSharedMem = (ULONGLONG*)lpvSharedMem;
*pSharedMem = _64bitValue;
}
return bOK;
}
BOOL GetSharedMem(ULONGLONG* p64bitValue)
{
if ( p64bitValue == NULL ) return FALSE;
BOOL bOK = CreateSharedMem();
if ( bOK )
{
ULONGLONG* pSharedMem = (ULONGLONG*)lpvSharedMem;
*p64bitValue = *pSharedMem;
}
return bOK;
}
Note that for simplicity I'm just sharing a 64-bit value, but this is a general way of sharing memory between C# and C++. Feel free to enlarge SHARED_MEM_SIZE and/or add functions in order to share other data types as you see fit.
This is how we'll consume the above methods: we'll use SetSharedMem() on the C# side in order to set the delegate's IntPtr as a 64-bit value (regardless if the code runs on a 32 or a 64 bit system).
C# Code
namespace CSharpCode
{
delegate void VoidDelegate();
static public class COMInterfaceClass
{
[DllImport( "SharedMem.dll" )]
static extern bool SetSharedMem( Int64 value );
static GCHandle gcDelegateHandle;
public static int EntryPoint(string ignored)
{
IntPtr pFunc = IntPtr.Zero;
Delegate myFuncDelegate = new VoidDelegate( SomeMethod );
gcDelegateHandle = GCHandle.Alloc( myFuncDelegate );
pFunc = Marshal.GetFunctionPointerForDelegate( myFuncDelegate );
bool bSetOK = SetSharedMem( pFunc.ToInt64() );
return bSetOK ? 1 : 0;
}
public static void SomeMethod()
{
MessageBox.Show( "Hello from C# SomeMethod!" );
gcDelegateHandle.Free();
}
}
}
Note the use of GCHandle in order to prevent the delegate from being garbage-collected.
For good measures, we'll use the return value as a success/failure flag.
On the C++ side we'll extract the 64-bit value using GetSharedMem(), convert it to a function pointer and invoke the C# delegate.
C++ Code
#include "SharedMem.h"
typedef void (*VOID_FUNC_PTR)();
void ExecCSharpCode()
{
ICLRRuntimeHost *pClrHost = NULL;
HRESULT hrCorBind = CorBindToRuntimeEx(
NULL,
L"wks",
0,
CLSID_CLRRuntimeHost,
IID_ICLRRuntimeHost,
(PVOID*)&pClrHost
);
HRESULT hrStart = pClrHost->Start();
DWORD retVal;
HRESULT hrExecute = pClrHost->ExecuteInDefaultAppDomain(
szPathToAssembly,
L"CSharpCode.COMInterfaceClass",
L"EntryPoint",
L"",
&retVal // 1 for success, 0 is a failure
);
if ( hrExecute == S_OK && retVal == 1 )
{
ULONGLONG nSharedMemValue = 0;
BOOL bGotValue = GetSharedMem(&nSharedMemValue);
if ( bGotValue )
{
VOID_FUNC_PTR CSharpFunc = (VOID_FUNC_PTR)nSharedMemValue;
CSharpFunc();
}
}
}
The Original Answer - Good for 32-bit Systems
Here's a solution that is based on using IntPtr.ToInt32() in order to convert the delegate func. ptr. to the int which is returned from the static C# EntryPoint method.
(*) Note the use of GCHandle in order to prevent the delegate from being garbage-collected.
C# Code
namespace CSharpCode
{
delegate void VoidDelegate();
public class COMInterfaceClass
{
static GCHandle gcDelegateHandle;
public static int EntryPoint(string ignored)
{
IntPtr pFunc = IntPtr.Zero;
Delegate myFuncDelegate = new VoidDelegate( SomeMethod );
gcDelegateHandle = GCHandle.Alloc( myFuncDelegate );
pFunc = Marshal.GetFunctionPointerForDelegate( myFuncDelegate );
return (int)pFunc.ToInt32();
}
public static void SomeMethod()
{
MessageBox.Show( "Hello from C# SomeMethod!" );
gcDelegateHandle.Free();
}
}
}
C++ Code
We'll need to convert the returned int value to a function pointer, so we'll start off by defining a void function ptr. type:
typedef void (*VOID_FUNC_PTR)();
And the rest of the code looks pretty much like your original code, with the addition of converting and executing the function ptr.
ICLRRuntimeHost *pClrHost = NULL;
HRESULT hrCorBind = CorBindToRuntimeEx(
NULL,
L"wks",
0,
CLSID_CLRRuntimeHost,
IID_ICLRRuntimeHost,
(PVOID*)&pClrHost
);
HRESULT hrStart = pClrHost->Start();
DWORD retVal;
HRESULT hrExecute = pClrHost->ExecuteInDefaultAppDomain(
szPathToAssembly,
L"CSharpCode.COMInterfaceClass",
L"EntryPoint",
L"",
&retVal
);
if ( hrExecute == S_OK )
{
VOID_FUNC_PTR func = (VOID_FUNC_PTR)retVal;
func();
}
A Little Bit of Extra
You can also make use of the string input in order to choose which method to execute:
public static int EntryPoint( string interfaceName )
{
IntPtr pFunc = IntPtr.Zero;
if ( interfaceName == "SomeMethod" )
{
Delegate myFuncDelegate = new VoidDelegate( SomeMethod );
gcDelegateHandle = GCHandle.Alloc( myFuncDelegate );
pFunc = Marshal.GetFunctionPointerForDelegate( myFuncDelegate );
}
return (int)pFunc.ToInt32();
}
You can get even more generic by using reflection in order to find the correct method according to the given string input.
Related
I am looking for some guidance when it comes to call DeviceIoControl from C#, knowing that its generic aspect of accepting pointer parameters isn't always easy to express in C#.
Here are two examples and explanations laid out below.
Example 1:
This works but is cumbersome, you have a disposable scope but you have to pass the parameters to the function and at the end assign the output buffer value back to the variable.
var toc = new CDROM_TOC(); // non blittable
var code = NativeConstants.IOCTL_CDROM_READ_TOC;
using (var scope = new UnmanagedMemoryScope<CDROM_TOC>(toc))
{
if (!UnsafeNativeMethods.DeviceIoControl(Handle, code, IntPtr.Zero, 0, scope.Memory, scope.Size, out _))
return Array.Empty<ITrack>();
toc = scope.Value; // this is weird
}
Example 1 helper:
internal struct UnmanagedMemoryScope<T> : IDisposable where T : struct
{
private bool IsDisposed { get; set; }
public uint Size { get; }
public IntPtr Memory { get; }
public T Value
{
get => Marshal.PtrToStructure<T>(Memory);
set => Marshal.StructureToPtr(value, Memory, true);
}
public UnmanagedMemoryScope(T value)
{
var size = Marshal.SizeOf<T>();
Memory = Marshal.AllocHGlobal(size);
Marshal.StructureToPtr(value, Memory, false);
Size = (uint)size;
IsDisposed = false;
}
public void Dispose()
{
if (IsDisposed)
return;
if (Memory != default)
Marshal.FreeHGlobal(Memory);
IsDisposed = true;
}
}
Example 2:
This one is already much more friendly, wrappers do marshalling and the value passed is ref.
var toc = new CDROM_TOC(); // non blittable
var code = NativeConstants.IOCTL_CDROM_READ_TOC;
var ioctl = DeviceIoControl(Handle, code, ref toc);
// ...
Example 2 helper 1:
private static bool DeviceIoControl<TTarget>(
SafeFileHandle handle, uint code, ref TTarget target)
where TTarget : struct
{
var sizeOf = Marshal.SizeOf<TTarget>();
var intPtr = Marshal.AllocHGlobal(sizeOf);
Marshal.StructureToPtr(target, intPtr, false);
var ioctl = UnsafeNativeMethods.DeviceIoControl(
handle,
code,
IntPtr.Zero,
0u,
intPtr,
(uint)sizeOf,
out var lpBytesReturned
);
target = Marshal.PtrToStructure<TTarget>(intPtr);
Marshal.FreeHGlobal(intPtr);
return ioctl;
}
Example 2 helper 2:
private static bool DeviceIoControl<TTarget, TSource>(
SafeFileHandle handle, uint code, ref TTarget target, ref TSource source)
where TSource : struct
where TTarget : struct
{
var sizeOf1 = Marshal.SizeOf(source);
var sizeOf2 = Marshal.SizeOf(target);
var intPtr1 = Marshal.AllocHGlobal(sizeOf1);
var intPtr2 = Marshal.AllocHGlobal(sizeOf2);
Marshal.StructureToPtr(source, intPtr1, false);
Marshal.StructureToPtr(target, intPtr2, false);
var ioctl = UnsafeNativeMethods.DeviceIoControl(
handle,
code,
intPtr1,
(uint)sizeOf1,
intPtr2,
(uint)sizeOf2,
out var lpBytesReturned
);
Marshal.PtrToStructure(intPtr1, source);
Marshal.PtrToStructure(intPtr2, target);
Marshal.FreeHGlobal(intPtr1);
Marshal.FreeHGlobal(intPtr2);
return ioctl;
}
But I feel that I might be missing something and maybe there's a better approach...
Question:
What are some good tricks when it comes to call DeviceIoControl from C#?
Knowing that,
want to avoid the use of unsafe keyword
there are non-blittable types so fixed is out of question for them
the function accepts arbitrary types, it's only buffers for it in the end
Of course there's the C++/CLI route but well, it's not C# anymore...
Hope that makes sense to you, else let me know.
I usually do it like this.
Parameters structure:
ref struct CDROM_TOC
{
const int MAXIMUM_NUMBER_TRACKS = 100;
public const int sizeInBytes = 4 + MAXIMUM_NUMBER_TRACKS * 8;
readonly Span<byte> buffer;
public CDROM_TOC( Span<byte> buffer )
{
if( buffer.Length != sizeInBytes )
throw new ArgumentException();
this.buffer = buffer;
}
/// <summary>Fixed header of the structure</summary>
public struct Header
{
public ushort length;
public byte firstTrack, lastTrack;
}
/// <summary>Fixed header</summary>
public ref Header header =>
ref MemoryMarshal.Cast<byte, Header>( buffer.Slice( 0, 4 ) )[ 0 ];
public struct TRACK_DATA
{
byte reserved;
public byte controlAndAdr;
public byte trackNumber;
byte reserved2;
public uint address;
}
/// <summary>Tracks collection</summary>
public Span<TRACK_DATA> tracks =>
MemoryMarshal.Cast<byte, TRACK_DATA>( buffer.Slice( 4 ) );
// Make this structure compatible with fixed() statement
public ref byte GetPinnableReference() => ref buffer[ 0 ];
}
Usage example:
CDROM_TOC toc = new CDROM_TOC( stackalloc byte[ CDROM_TOC.sizeInBytes ] );
unsafe
{
fixed( byte* buffer = toc )
{
// Here you have unmanaged pointer for that C interop.
}
}
// If you want to return the tracks, need to copy to managed heap:
var header = toc.header;
return toc.tracks
.Slice( header.firstTrack, header.lastTrack - header.firstTrack + 1 )
.ToArray();
Couple more notes.
The answer assumes you have a modern C#, i.e. .NET 5 or newer, or any version of .NET Core.
The example does use unsafe, but only on the lowest level. If you absolutely don’t want that, use GCHandle instead. With GCHandleType.Pinned, it’s an equivalent to unsafe keyword, only slower.
Unlike your code, this method does not use any heap memory for the interop, neither managed nor native.
The instance of the structure is stack allocated, and it exposes higher-level API to access the fields of that structure. The complete stack is already fixed in memory, the fixed keyword going to nothing for that code, just return the address. Doing nothing is free performance-wise.
I'm currently working on an C# (.NET Framework 4.7.2) application using some business logic from an unmanaged C++ library. I try to pass data (interop) back and forth from C# to C++. I may not use C++/CLI, no common language runtime allowed in my project.
It works fine for int. Unfortunately as soon as I try to send another datatype I'm getting an conversion error e.g. float 4.2f becomes 1 and string "fourtytwo" turns into -1529101360.
My C# code looks like this:
// works fine, creates an instance of TestClass
var test = TestProxy.Wrapper_Create("test");
// int, works fine, a = 42
var a = TestProxy.TryInt(test, 42);
// float, problem, b = 1
var b = TestProxy.TryFloat(test, 4.2f);
// string, problem, c = -159101360
var c = TestProxy.TryString(test, "fourtytwo");
My C# Interop Proxy class to call the native (unmanaged) C++ code looks like this:
public static class TestProxy
{
private const string coreDLL = "test.core.dll";
[DllImport(coreDLL, CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr Wrapper_Create(string name);
[DllImport(coreDLL, EntryPoint = "?TryInt#TestClass##XXX#X", CallingConvention = CallingConvention.ThisCall)]
public static extern int TryInt(IntPtr instance, int n);
[DllImport(coreDLL, EntryPoint = "?TryFloat#TestClass##XXX#X", CallingConvention = CallingConvention.ThisCall)]
public static extern int TryFloat(IntPtr instance, float n);
[DllImport(coreDLL, EntryPoint = "?TryString#TestClass##XXX#X", CallingConvention = CallingConvention.ThisCall)]
public static extern int TryString(IntPtr instance, string n);
}
My native (unmanaged) C++ looks like that:
the header file:
#ifdef TESTCORE_EXPORTS
#define TESTCORE_API __declspec(dllexport)
#endif
#pragma once
extern "C"
{
class TESTCORE_API TestClass
{
private:
char* name;
public:
TestClass(char*);
int TryInt(int);
float TryFloat(float);
char* TryString(char*);
};
TESTCORE_API TestClass* Wrapper_Create(char* name);
}
the implementation file:
#include "stdafx.h"
#include "TESTCore.h"
TestClass::TestClass(char* n)
{
name = n;
}
int TestClass::TryInt(int n)
{
return n; // works fine
}
float TestClass::TryFloat(float n)
{
return n; // something goes wrong here
}
char* TestClass::TryString(char* n)
{
return n; // something goes wrong here
}
extern "C"
{
TESTCORE_API TestClass * Wrapper_Create(char* name)
{
return new TestClass(name);
}
TESTCORE_API int TryInt(TestClass * instance, int n)
{
if (instance != NULL)
{
return instance->TryInt(n);
}
}
TESTCORE_API float TryFloat(TestClass * instance, float n)
{
if (instance != NULL)
{
return instance->TryFloat(n);
}
}
TESTCORE_API char* TryString(TestClass * instance, char* n)
{
if (instance != NULL)
{
return instance->TryString(n);
}
}
}
Do you know how to correctly marshal float, string from C# to C++ and back?
Thank you!
C++ doesn't have standard ABI. It's rarely a good idea to use C++ classes across DLLs, even when you have same language on both sides.
There're better ways.
Replace your __thiscall class methods with global functions, cdecl or stdcall whichever you like (but note C# and C++ have different defaults, if you'll do nothing C++ will use cdecl, C# will import as stdcall). You can pass "this" pointer of the class in the first argument, IntPtr in C#, just like you're doing now. Also if you'll write extern "C" or use a module definition file, they will have human-readable names.
If you want objects, use COM. Declare an interface that inherits from IUnknown, implement it in C++ (I usually use ATL), and export a global function to create an instance of that object (2 lines in ATL, CComObject<T>::CreateInstance followed by AddRef). No need to register, type libraries, you just need to implement IUnknown (but see this if you want to use them from multiple threads)
Update: strings are indeed harder. Apply [MarshalAs(UnmanagedType.LPTStr)] to the argument. Apply [return: MarshalAs(UnmanagedType.LPTStr)] to the function. Specify PreserveSig=true in your DllImport. Finally, modify the C++ code to return a copy of the string, i.e. call strlen then CoTaskMemAlloc (don't forget about the '\0') then strcpy.
Easier way to deal with strings is like this:
HRESULT TryString( TestClass *instance, BSTR i, BSTR *o )
At least there're CComBSTR and _bstr_t built-in classes to deal with memory management.
I'm trying to do some basic C#\C interop which returns strange results. These are definitions on the C side :
typedef struct _RESULT {
BOOL success;
char *err;
} RESULT;
typedef struct _INPUT_DATA {
char *message;
} INPUT_DATA;
API int execute_out(IN INPUT_DATA *input, OUT RESULT *result);
With simple implementation:
API int execute_out(INPUT_DATA *input, RESULT *result){
result = (RESULT*)malloc(sizeof RESULT);
result->err = (char*)malloc(sizeof 128);
strcpy(result->err, "Result");
result->success = TRUE;
return 0;
}
Now, my C# definitions are as follows :
[StructLayout(LayoutKind.Sequential)]
public struct INPUT_DATA
{
[MarshalAs(UnmanagedType.LPStr)]
public string message;
}
[StructLayout(LayoutKind.Sequential)]
public struct RESULT
{
public bool success;
public IntPtr err;
}
[DllImport("unmanaged.dll", CallingConvention = CallingConvention.Cdecl)]
public extern static int execute_out(INPUT_DATA input, out RESULT result);
When I setup code like this on the managed side :
Unmanaged.INPUT_DATA input = new Unmanaged.INPUT_DATA();
input.message = "Test";
Unmanaged.RESULT result;
Unmanaged.execute_out(input, out result);
I receive empty data for err variable defined in RESULT struct and also my success flag is not set correctly (yields false). Can anyone tell me what I'm missing here?
Also, what would be the best practice similar to this case :
Should caller (managed code) allocate memory for the RESULT struct and later free it, or should be there another call to free the allocated memory on the unmanaged side?
Based on the suggestions from #HansPassant and #Olaf, this is the actual code that works for me :
API int execute_out(INPUT_DATA *input, RESULT *result){
//result = (RESULT*)malloc(sizeof RESULT); //this malloc is redundat which effectively replaces original pointer
result->err = (char*)malloc(sizeof 128);
strcpy(result->err, "Result");
result->success = TRUE;
return 0;
}
Malloc is commented, as it replaces original pointer passed from the managed code.
On the managed side, functions need to be declared like this :
[DllImport("unmanaged.dll", CallingConvention = CallingConvention.Cdecl)]
public extern static int execute_out(ref INPUT_DATA input, [Out] out RESULT result);
Notice the ref keyword to properly pass INPUT_DATA pointer and [Out] attribute before RESULT pointer to tell the marshaller we need something back.
In the end, complete call from managed side looks like this :
Unmanaged.INPUT_DATA input = new Unmanaged.INPUT_DATA();
input.message = "Test";
Unmanaged.RESULT result;
Unmanaged.execute_out(ref input, out result);
string error = Marshal.PtrToStringAnsi(result.err);
Hope someone will find this useful.
I'm trying to create a C# wrapper for a C .lib that contains functions take take a void pointer using SWIG.
int inputPointExample(void* input);
int outputPointerExample(void* output);
By default SWIG doesn't handle void pointer conversions, you have to use typemaps somehow. I found this page -> http://www.nickdarnell.com/2011/05/swig-and-a-miss/
Number 9 says to use the following typemaps to handle void pointers...
%typemap(ctype) void * "void *"
%typemap(imtype) void * "IntPtr"
%typemap(cstype) void * "IntPtr"
%typemap(csin) void * "$csinput"
%typemap(in) void * %{ $1 = $input; %}
%typemap(out) void * %{ $result = $1; %}
%typemap(csout) void * { return $imcall; }
When I try that I get a compile error in my exampleVectorType.cs in this function...
public IntPtr pData {
set {
examplePINVOKE.ExampleVectorType_pData_set(swigCPtr, value);
}
get {
global::System.IntPtr cPtr = examplePINVOKE.ExampleVectorType_pData_get(swigCPtr);
SWIGTYPE_p_void ret = (cPtr == global::System.IntPtr.Zero) ? null : new SWIGTYPE_p_void(cPtr, false);
return ret; //Compile error occurs here
}
}
I got-
Cannot implicitly convert type 'SWIGTYPE_p_void' to 'System.IntPtr'
From what I was able to find, many others are having problems with this as well and there are only a few poor examples on how to fix this. Can someone help me out here?
I've tried this with
// c++
void* GetRawPtr()
{
return (void*)_data;
}
which swigs to
// swig generated c#
// Example.cs
IntPtr GetRawPtr()
{
return examplePINVOKE.Example_GetRawPtr(swigCPtr);
}
// examplePINVOKE.cs
[global::System.Runtime.InteropServices.DllImport("example", EntryPoint="CSharp_Example_GetRawPtr")]
public static extern IntPtr Example_GetRawPtr(global::System.Runtime.InteropServices.HandleRef jarg1);
if the following line is removed, then i get the code you had:
%typemap(csout) void * { return $imcall; }
perhaps SWIG doesn't support typemapping well with properties? If you don't use get/set properties for your pData, it should work (as I've gotten it to work for me)
I had a similar issue, but my case included struct fields:
struct foo {
void* bar;
};
I was able to get Swig to work by using this:
%typemap(ctype) void* "void *"
%typemap(imtype) void* "System.IntPtr"
%typemap(cstype) void* "System.IntPtr"
%typemap(csin) void* "$csinput"
%typemap(in) void* %{ $1 = $input; %}
%typemap(out) void* %{ $result = $1; %}
%typemap(csout, excode=SWIGEXCODE) void* {
System.IntPtr cPtr = $imcall;$excode
return cPtr;
}
%typemap(csvarout, excode=SWIGEXCODE2) void* %{
get {
System.IntPtr cPtr = $imcall;$excode
return cPtr;
}
%}
I don't totally understand this stuff, but I believe the excode portion only matters if you're using exceptions. Overriding the property's get accessor via csvarout seems to be the key.
I have to pass from my C++ dll array to the C# program.
Here is the C++ code:
__declspec(dllexport) std::vector<std::string> __stdcall
getFilesByDirs
(
string directory,
std::string fileFilter,
bool recursively = true
)
{
int __ID = 0;
std::vector<std::string> filesArray;
if (recursively)
getFilesByDirs(directory, fileFilter, false);
directory += "\\";
WIN32_FIND_DATAA FindFileData;
HANDLE hFind = INVALID_HANDLE_VALUE;
std::string filter = directory + (recursively ? "*" : fileFilter);
hFind = FindFirstFileA(filter.c_str(), &FindFileData);
if (hFind == INVALID_HANDLE_VALUE)
{
return filesArray;
}
else
{
if (!recursively)
{
if(isGoodForUs(directory + std::string(FindFileData.cFileName)))
{
filesArray[__ID] = directory + std::string(FindFileData.cFileName);
__ID ++;
}
}
while (FindNextFileA(hFind, &FindFileData) != 0)
{
if (!recursively)
{
if(!isGoodForUs(directory + std::string(FindFileData.cFileName))) continue;
filesArray[__ID] = directory + std::string(FindFileData.cFileName);
}
else
{
if ((FindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)>0 && FindFileData.cFileName[0]!='.')
{
if(!isGoodForUs(directory + std::string(FindFileData.cFileName))) continue;
getFilesByDirs(directory + std::string(FindFileData.cFileName), fileFilter);
}
}
__ID ++;
}
DWORD dwError = GetLastError();
FindClose(hFind);
}
return filesArray;
}
As you can see, there is a function that scanning for files by type.
It's saved in the string array and returning out.
Now, here is the C# code:
[DllImport(#"files.dll", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)]
public static extern IntPtr getFilesByDirs(string path, string exns, bool rec=true);
and then i am calling to this method:
IntPtr a = getFilesByDirs("C:\\", "*.docx");
The problem is that nothing pass to my C# program from the Dll.
Anyone can help with my issue?
You need to return them not as a vector but as a primitive type, such as a char**. The problem here is that, in order to consume it you need to know how many are in the collection. As Ed. S said in the comments, you may find it easier to work with CLI (if you can compile your C++ with /clr on). But if you can do that, then you can use .Net types in C++ (such as List) and marshall your char*'s to Strings on the C++ side.
Otherwise, you can return the raw char*'s an handle it on the .Net side. To consume the char*'s in C# (and maybe having done C++, you will feel comfortable with this feature) you can use the unsafe keyword so you can convert those char*'s to System::String's in unsafe and add them to your .Net collections.