11

I have a C++ function that produces a list of rectangles that are interesting. I want to be able to get that list out of the C++ library and back into the C# application that is calling it.

So far, I'm encoding the rectangles like so:

struct ImagePatch{ 
   int xmin, xmax, ymin, ymax;
}

and then encoding some vectors:

void MyFunc(..., std::vector<int>& rectanglePoints){
   std::vector<ImagePatch> patches; //this is filled with rectangles
   for(i = 0; i < patches.size(); i++){
       rectanglePoints.push_back(patches[i].xmin);
       rectanglePoints.push_back(patches[i].xmax);
       rectanglePoints.push_back(patches[i].ymin);
       rectanglePoints.push_back(patches[i].ymax);
   }
}

The header for interacting with C# looks like (and works for a bunch of other functions):

extern "C" {
    __declspec(dllexport) void __cdecl MyFunc(..., std::vector<int>& rectanglePoints);
}

Are there some keywords or other things I can do to get that set of rectangles out? I found this article for marshalling objects in C#, but it seems way too complicated and way too underexplained. Is a vector of integers the right way to do this, or is there some other trick or approach?

mmr
  • 14,781
  • 29
  • 95
  • 145
  • 1
    This isn't really "marshalling". Marshalling would be taking your C++ object, writing some binary data that represents it, and having C# read that binary data to construct a corresponding object in the other environment. You're trying to pass an argument from C# code into C++ code, and have the C++ code modify it. – Steve Jessop Apr 30 '10 at 22:08
  • OK, then how can I do this in such a way that what I get from the C++ side is of arbitrary length? – mmr Apr 30 '10 at 22:17
  • Well, if you want to avoid writing a "proper" managed .NET object in C++, as described in that MSDN article, either: return a malloced buffer from C++ (and free it from C# when you're done with it over ther); re-design your API so that C# can pass in a buffer to be filled, and a length, and the C++ code can somehow tell C# how big that buffer needs to be; redesign the API so that you call back from C++ into C# once for each value, and add it to a C# collection. I don't know enough about C# (or interfacing with other languages from C#) to judge which is easier/better. – Steve Jessop Apr 30 '10 at 22:39
  • Or use one of the .NET containers from both C# and C++, if your C++ code can run in .NET. – Steve Jessop Apr 30 '10 at 22:52
  • Yeah, I've already decided to pass in a large buffer to C++ and hope that it's enough for practical purposes. This does seem like a pretty big hole right now, though, for interop services between .NET and native code. – mmr Apr 30 '10 at 23:23
  • Have you considered using C++/CLI for this? It really is the easiest, most reliable, and most maintainable solution to problems like this. – Ben Voigt Apr 15 '13 at 04:13
  • @BenVoigt-- no. I need to be able to compile with the intel compiler, and so cannot use a Microsoft-specific language. I need things to be fast on the C++ side; otherwise, I wouldn't even bother using C++ in the first place. I also need to be able to port the C++ code to other platforms (unix, etc), for the UIs that I have there. So C++/CLI is not an acceptable solution to my problem, as it cannot be readily compiled outside of the Microsoft environment. – mmr Apr 15 '13 at 17:39
  • @mmr: Doesn't the Intel compiler generate MS-compatible object files? C++/CLI can be linked with normal C++ object code. If you want portability though, I strongly recommend using a C-compatible interface (no classes passed across the C#/C++ boundary). – Ben Voigt Apr 16 '13 at 06:06
  • @BenVoigt-- that's ultimately what I ended up doing. Classes across the boundary are just too painful. – mmr Apr 16 '13 at 16:05

3 Answers3

5

The STL is a C++ specific library, so you cant directly get it across as one object to C#.

The one thing that is guaranteed about std::vector is that &v[0] points to the first element and all the elements lie linearly in memory (in other words, its just like a C array in terms of memory layout)

So marshal as array of int... which shouldn't be hard - There are lot of examples on the web.

Added

Assuming you only pass the data from C++ to C# :

C# cannot handle a C++ vector object, so do not try passing it by reference : Instead your C++ code must return a pointer to an array of ints...

If you are not going to be using this function from multiple threads, you can use static storage :

int *getRects(bool bClear)
{
    static vector<int> v; // This variable persists across invocations
    if(bClear)
    {
        v.swap(vector<int>());
    }
    else
    {
        v.clear();
        // Fill v with data as you wish
    }

    return v.size() ? &v[0] : NULL;
}

call getRects(true) if the returned data is significant in size, so you release the memory in v.

For simplicity, instead of passing out the size of the vector data too, just put a sentinel value at the end (like say -1) so the C# code can detect where the data ends.

rep_movsd
  • 6,675
  • 4
  • 30
  • 34
  • But how would that work? If I marshal as an array, how does the array grow on the C++ side by push-back? That memory hasn't been allocated, so any element beyond the first one fails. If I preallocate the array on the C# side, then I might as well use an array rather than a vector, and could be bad if I have lots of rectangles. – mmr Apr 30 '10 at 21:17
  • @rep_movsd would this work equally well with something that isn't an int, like a struct? – WWZee May 30 '17 at 15:22
  • Yes indeed - structs are value types that are treated just like a primitive type. – rep_movsd May 30 '17 at 17:01
3

Yes. You can. Actually, not just std::vector, std::string, std::wstring, any standard C++ class or your own classes can be marshaled or instantiated and called from C#/.NET.

Wrapping a std::vector<any_type> in C# is indeed possible with just regular P/Invoke Interop, it is complicated though. even a std::map of any type can be done in C#/.NET.

public class SampleClass : IDisposable
{    
    [DllImport("YourDll.dll", EntryPoint="ConstructorOfYourClass", CharSet=CharSet.Ansi,          CallingConvention=CallingConvention.ThisCall)]
    public extern static void SampleClassConstructor(IntPtr thisObject);

    [DllImport("YourDll.dll", EntryPoint="DestructorOfYourClass", CharSet=CharSet.Ansi,          CallingConvention=CallingConvention.ThisCall)]
    public extern static void SampleClassDestructor(IntPtr thisObject);

    [DllImport("YourDll.dll", EntryPoint="DoSomething", CharSet=CharSet.Ansi,      CallingConvention=CallingConvention.ThisCall)]
    public extern static void DoSomething(IntPtr thisObject);

    [DllImport("YourDll.dll", EntryPoint="DoSomethingElse", CharSet=CharSet.Ansi,      CallingConvention=CallingConvention.ThisCall)]
    public extern static void DoSomething(IntPtr thisObject, int x);

    IntPtr ptr;

    public SampleClass(int sizeOfYourCppClass)
    {
        this.ptr = Marshal.AllocHGlobal(sizeOfYourCppClass);
        SampleClassConstructor(this.ptr);  
    }

    public void DoSomething()
    {
        DoSomething(this.ptr);
    }

    public void DoSomethingElse(int x)
    {
        DoSomethingElse(this.ptr, x);
    }

    public void Dispose()
    {
        if (this.ptr != IntPtr.Zero)
        {
            // The following 2 calls equals to "delete object" in C++
            // Calling the destructor of the C++ class will free the memory allocated by the native c++ class.
            SampleClassDestructor(this.ptr);

            // Free the memory allocated from .NET.
            Marshal.FreeHGlobal(this.ptr);

            this.ptr = IntPtr.Zero;
        }
    }
}

Please see the below link,

C#/.NET PInvoke Interop SDK

(I am the author of the SDK tool)

Once you have the C# wrapper class for your C++ class ready, it is easy to implement ICustomMarshaler so that you can marshal the C++ object from .NET.

http://msdn.microsoft.com/en-us/library/system.runtime.interopservices.icustommarshaler.aspx

Community
  • 1
  • 1
xInterop
  • 277
  • 3
  • 9
  • 1
    You are way into the realm of undefined behavior here. Just do it right, use C++/CLI. – Ben Voigt Apr 15 '13 at 04:11
  • Thanks for the comments. But once you know the steps to wrap a C++ class, it is not that complicated at all, I added a simple example to complete my answer. C++/CLI is not that developer-friendly, especially the syntax. – xInterop Apr 15 '13 at 04:36
  • Congratulations, you wrote a class with potential memory leak, potential double free, and calling internal functions without the right support system. Bad bad idea. Besides, a C++/CLI solution which is *correct* would be 1/3 as much code. – Ben Voigt Apr 15 '13 at 04:48
  • Thanks a lot for comments. A potential memory leak? Can you point out where and how? When dealing with P/Invoke, you will have to pay attention to difference of native and .NET anyway. What the internal functions are you talking about? Those functions are exported in my example and this is just example to show how it works, it is not some class with production quality code. Double free? You could make the same mistake in the C++ code itself, right? That is also a common problem when bridging between C++ and .NET world when garbage collection is involved. – xInterop Apr 15 '13 at 05:26
  • I added calling Destructor of the sample class since you are concerning about the sample code instead of the whole solution itself. Again, when calling into a native dll, potential memory leak is already there. – xInterop Apr 15 '13 at 05:39
  • In .NET, you should clean up native resources from the finalizer, because C# doesn't provide a good way of calling `Dispose` on non-local objects during exception handling. Also you forgot to mention that the calling convention needs to be explicitly declared on the C++ side for any of this to work. It's very fragile -- just use C++/CLI. – Ben Voigt Apr 15 '13 at 14:23
  • Thanks for commenting again. Again, this is a sample class to show how it works. It does not mean to be perfect. Calling convention does not need to be explicitly declared on the C++ side. It is NOT fragile at all, it is much better than C++/CLI if you are able to automate the code generation. With C++/CLI, you will have to write a concrete wrapper class for each C++ class, it is really time-consuming, it is like re-inventing the wheel. – xInterop Apr 15 '13 at 22:31
  • 1
    Of course the C++ side calling convention matters, since there's more than one convention for calling member functions. `thiscall`, `stdcall`, and `cdecl` are all possible. Relying on the default convention used by the makefile is fragile. The mangled names you've so conveniently omitted from the example are fragile, since they change with every compiler version. And then you claim this works well with Standard library classes? Those also change. If you're doing code generation, generate C++/CLI and no one will care about the weird syntax. – Ben Voigt Apr 15 '13 at 22:36
  • Calling a C++ DLL from C++ would also need all the stuff you mentioned, right? :) A C++ DLL linking with specific C++ run time has to be used with that run time any way. So, your argument seems valid, but it does not make the solution bad. It is a very good solution because we can automate it. No C++/CLI weird code. That is my story, You may disagree though. Thanks! – xInterop Apr 15 '13 at 23:29
  • But with C++/CLI there's no potential for mixing incompatible versions of the two sides, because they're compiled into a single DLL and distributed as a unit. And the compiler uses the native headers to extract the type information, which it also checks against the implementation to prevent ODR violations. – Ben Voigt Apr 16 '13 at 03:31
  • Our SDK tool does use VC compiler to retrieve the class type information of C++ DLL. All I am saying it is doable, and we have done it and the tool works very well with 30+ C++ DLLs so far. – xInterop Apr 16 '13 at 05:36
  • In our beta testing, I myself was surprised to see I was able to generate C# wrapper DLL for very complicated DLL in less than a minute and it worked under both x64 and x86, especially when I was able to build C# wrapper class for those template classes used in the C++ interfaces, which are not exported in the C++ DLL. with C++/CLI, you would not be able to write a wrapper DLL for C++ DLL having 20+, 30+ or more complicated classes in a minute, right? Thank you very much for your comments. – xInterop Apr 16 '13 at 05:38
  • And we are going to add mono support in the near future. So this solution will be able to work on other OS as well. It is doable on other OS as well. But all your concerns are valid when writing a tool to generate a .NET wrapper DLL for C++ DLL, a lot of things have to be considered. Thanks again! – xInterop Apr 16 '13 at 05:50
  • A tool to automatically generate the C++/CLI wrapper would be simpler than a tool to generate C# p/invoke wrappers, and the resulting wrappers would be much easier to debug, as well as continuing to work on future compiler versions. But I appreciate that these are hypothetical advantages, while the C# wrapper tool already exists (at least twice -- yours and also SWiG). – Ben Voigt Apr 16 '13 at 06:05
  • Thanks for saying that. C++/CLI language is not really friendly to developers, I am not getting used to its syntax, I guess others won't be neither so easily, I would want like it it it was. Also as you can see, it is only available to windows OS. C# wrapper should be much better, developers should be more comfortable with modifying the auto-generated source code if they do want to. All we are trying to do is to make it completely automatic without any human involvement.(I hope so) – xInterop Apr 16 '13 at 06:37
  • Please don't put words in my mouth. C++/CLI is far more friendly IMO. But you seemed to be struggling with it. And the generated C# wrappers are anything but human-maintainable, I promise. No human can tell whether the mangled names are correct. – Ben Voigt Apr 16 '13 at 12:44
  • Dave, I was just specking of average developers, not you specificly. I am really sorry for the confusion. If you are happy with C++/CLI, please continue using it. – xInterop Apr 16 '13 at 15:36
  • The whole point of using a wrapper generator is to avoid human-maintaining of the wrapper, isn't it? Allowing developers to change the generated code is just for convenience, it is not meant to be maintained, it is just like any other auto-generated code. – xInterop Apr 16 '13 at 15:43
  • Our tool uses ordinal instead of mangled names which are just too long to put in the source code, if we did, we have to put in both x86 and x64 version of the mangled name, that was too much extra characters in the source code, the ordinal or mangled name will get updated if it does change, developers do not have to maintain them. the generated C# code is really human-friendly to me, it is just what I am supposed to write for a wrapper, I was even able to put whatever comments I like to if I found any potential issue with the wrapping at any place. – xInterop Apr 16 '13 at 15:44
  • Do you think the ordinal or mangled names are not used when using C++/CLI? Think again. And with C++/CLI to wrap the code, you add additional project to maintain, if you change the public interface in your C++ code, You don't need to change the wrapper and the C++/CLI wrapper still work with the new C++ DLL? – xInterop Apr 16 '13 at 15:45
  • No, C++/CLI doesn't require any mangled names in the source code. Since it is part of the same DLL, you can't change the C++ code without also recompiling the C++/CLI at the same time. And the compiler will find the new mangled names for you, with no changes to the wrapper. If the public interface changes, rerun the wrapper generator. If you want to use a new version of the C++ compiler (better optimization for the native code, perhaps) just load the project and rebuild all, the same wrapper code still works. – Ben Voigt Apr 16 '13 at 18:54
  • If you add additional classes in the C++ DLL, would C++/CLI automatically pick up the new classes and generate a wrapper classes for you? – xInterop Apr 16 '13 at 20:15
  • What I was saying is that the mangled names and the ordinals are used internally, you just don't know they are being used. I was not saying developers have to enter the mangled name manually or they appear in the source code. Our C+ wrapper generator does the same thing, it will retrieve the mangled names automatically. – xInterop Apr 16 '13 at 20:19
  • You cant wrap template classes like this - you'd have to wrap every single instantiation – rep_movsd Jul 25 '20 at 15:49
0

I'm pretty sure you can't do this. You have to be able to translate the C++ code directly to a C# class, so you would at least have to replicate the internals of the vector class to marshall it correctly. I'm also pretty sure you won't be able to move references across the boundary, you'll have to use IntPtr (raw pointers). The approach that i know works is to marshall a raw array of the structs.

Steve
  • 11,763
  • 15
  • 70
  • 103
  • I know that I can move references across the boundary-- I do it in other functions with int& and the like. On the C# side, just put the 'ref' keyword in the function signature when using dllimport. – mmr Apr 30 '10 at 20:53
  • 1
    Also note that C++ objects like vector should not cross a module boundary - there is no guarantee that different DLL's or exe's use the exact same version of the STL. – Michael Apr 30 '10 at 21:09
  • Then how do I get a bunch of ints (the exact amount of which I don't know) back into C#? – mmr Apr 30 '10 at 21:16