Re: Returning structures by value in .NET with P/Invoke

View: New views
3 Messages — Rating Filter:   Alert me  

Parent Message unknown Re: Returning structures by value in .NET with P/Invoke

by David Piepgrass :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

Maybe I'm the only one dealing with this issue, but I'll post it anyway
for the record.

I'd just like to add a new wrinkle to this old thread. It seems that if
the structure you return has a constructor, then the C++ compiler
decides to add 4 to the number at the end of the exported name, and thus
the C++ and C# exported names match. See, in the example below I suggest
declaring an alias in your .def file:

   _CSharp_CreateSquareBox@16 = _CSharp_CreateSquareBox@12

BUT if the Box structure is given a constructor, the C++ compiler will
(or at least might) decide to use the name _CSharp_CreateSquareBox@16 in
the first place. Consequently the line in the .def file is not only
unnecessary but illegal and you get this linker output:
 
warning LNK4197: export '_CSharp_CreateSquareBox@16' specified multiple
times; using first specification
error LNK2001: unresolved external symbol _CSharp_CreateSquareBox@12

Gee whiz, Microsoft does weird things. Also, when you give your
structure a constructor, the C++ compiler will give you a warning

'CSharp_CreateSquareBox' has C-linkage specified, but returns UDT 'Box'
which is incompatible with C

I'm not sure whether this warning is anything to be concerned about.

> To recap a previous thread: Somebody might want to export a function
> that returns a structure:
>
> typedef struct {
>   int width;
>   int height;
>   int length;
> } Box;
> Box CreateSquareBox(int width, int height, int length) {
>   Box b;
>   b.width = width;
>   b.height = height;
>   b.length = length;
>   return b;
> }
>
> (Typemaps are listed below)
>
> Now the compiler exports the wrapper as _CSharp_CreateSquareBox@12,
> where 12 refers to the size (in bytes) of the 3 arguments.
>
> SWIG correctly imports into C# like this:
>
>   [DllImport("NaviMapping", EntryPoint="CSharp_CreateSquareBox")]
>   public static extern Box CreateSquareBox(int jarg1, int jarg2, int
> jarg3);
>
> It doesn't work and I have now confirmed the reason why. My
> understanding is that structures returned by value are automatically
> transformed into pass by reference, something like this (I have not
> found any documentation on it, so this is just a guess):
>
>   void CreateSquareBox(Box* box, int width, int height, int length);
>
> Now when you tell the .NET framework that the entry point is
> CSharp_CreateSquareBox, it searches the DLL for two names: (1) it
looks
> for CSharp_CreateSquareBox and doesn't find it. (2) It computes the
size
> of the arguments to form the alternate name
_CSharp_CreateSquareBox@16.
> It looks for this and doesn't find it, so
> 'System.EntryPointNotFoundException' is thrown. The C++ compiler is
much
> older so it is likely that _CSharp_CreateSquareBox@12 is the correct
> name, and the .NET framework just has a bug in it. It must be hard to
> get right given that there seems to be no documentation anywhere on
the

> subject... for example if you look at
>
> http://msdn2.microsoft.com/EN-US/library/awbckfbz.aspx
>
> You'll notice that Microsoft only considers passing structures as
> arguments, not as return values.
>
> There is an important exception: there is no problem if the structure
> being returned is only 4 or 8 bytes. I assume that this is because a 4
> or 8-byte structure can be returned in registers, so the hidden
argument

> is not required and the but in .NET doesn't show up.
>
> I know of three solutions that let you call the function from C#.
>
> 1. Don't return structures; pass by reference.
>
> 2. Create a .def file in your C++ project in which you alias the
> function:
>
> LIBRARY "Example"
>
> EXPORTS
>   CSharp_CreateSquareBox
>   _CSharp_CreateSquareBox@16 = CSharp_CreateSquareBox@12
>
> 3. Somehow tell SWIG to tell P/Invoke the correct name (not sure how):
>
>   DllImport("NaviMapping", EntryPoint="_CSharp_CreateSquareBox@12")]
>   public static extern Box CreateSquareBox(int jarg1, int jarg2, int
> jarg3);
>
> P.S. When using SWIG with C#, you need to redefine the same struct in
C#

> (manually) and you need typemaps like this:
>
> %ignore Box;
> %typemap(in) Box %{ $1 = $input; %}
> %typemap(out, null="Box()", outattribute="return:
> MarshalAs(ValueStruct)") Box %{ $result = $1; %}
> // TODO, need a better null attribute, also do it in C
> %typemap(cstype) Box "Box"
> %typemap(imtype) Box "Box"
> %typemap(ctype) Box "Box"
> %typemap(csout, excode=SWIGEXCODE) Box {
>     Box ret = $imcall;$excode
>     return ret;
>   }
> %typemap(csin) Box "$csinput"
>
> // In C#:
> [StructLayout(LayoutKind.Sequential)]
> public struct Box
> {
> public int width;
> public int height;
> public int length;
> }
>
> Bonus: here's how to marshall System.Drawing.Point, an 8-byte
structure

> which does not exhibit the problem:
>
> %ignore Point;
> %typemap(in) Point %{ $1 = $input; %}
> %typemap(out, null="System.Drawing.Point()",
>   outattribute="return: MarshalAs(ValueStruct)") Point
>   %{ $result = $1; %}
> %typemap(cstype) Point "System.Drawing.Point"
> %typemap(imtype) Point "System.Drawing.Point"
> %typemap(ctype) Point "Point"
> %typemap(csout, excode=SWIGEXCODE) Point {
>     System.Drawing.Point ret = $imcall;$excode
>     return ret;
> }
> %typemap(csin) Point "$csinput"
>
> %inline
> %{
> typedef struct {
>  int x,y;
> } Point;
> Point CreatePoint(int x, int y) {
> Point p = {x,y};
> return p;
> }
> %}
>
> // Later, in C#...
>   System.Drawing.Point p = Example.CreatePoint(2, 3);
>   Debug.WriteLine(string.Format("Point: {0}, {1}", p.X, p.Y));
>
> ------------------------------
>
> One little thing I forgot to mention:
>
> EXPORTS
>   ; Either of these two lines will work; you don't need both.
>   CSharp_CreateSquareBox
>   _CSharp_CreateSquareBox@16 = CSharp_CreateSquareBox@12


-------------------------------------------------------------------------
This SF.net email is sponsored by: Microsoft
Defy all challenges. Microsoft(R) Visual Studio 2008.
http://clk.atdmt.com/MRT/go/vse0120000070mrt/direct/01/
_______________________________________________
Swig-user mailing list
Swig-user@...
https://lists.sourceforge.net/lists/listinfo/swig-user

Re: Returning structures by value in .NET with P/Invoke

by wsfulton :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

David Piepgrass wrote:

> Maybe I'm the only one dealing with this issue, but I'll post it anyway
> for the record.
>
> I'd just like to add a new wrinkle to this old thread. It seems that if
> the structure you return has a constructor, then the C++ compiler
> decides to add 4 to the number at the end of the exported name, and thus
> the C++ and C# exported names match. See, in the example below I suggest
> declaring an alias in your .def file:
>
>    _CSharp_CreateSquareBox@16 = _CSharp_CreateSquareBox@12
>
> BUT if the Box structure is given a constructor, the C++ compiler will
> (or at least might) decide to use the name _CSharp_CreateSquareBox@16 in
> the first place. Consequently the line in the .def file is not only
> unnecessary but illegal and you get this linker output:
>  
> warning LNK4197: export '_CSharp_CreateSquareBox@16' specified multiple
> times; using first specification
> error LNK2001: unresolved external symbol _CSharp_CreateSquareBox@12
>
> Gee whiz, Microsoft does weird things. Also, when you give your
> structure a constructor, the C++ compiler will give you a warning
>
This all strikes me as a bug in the CLR. Maybe you discuss this on a
Microsoft .NET mailing list for confirmation and let us know the
outcome? It is nearly impossible to auto generate code for this kind of
weirdness and it would be nice to get support for pass by value into
swig. There isn't this kind of weirdness on Linux btw, I'm sure I got
this working a few years ago with mono and Linux, but never took it any
further as it doesn't work on the MS platform.

William

-------------------------------------------------------------------------
This SF.net email is sponsored by: Microsoft
Defy all challenges. Microsoft(R) Visual Studio 2008.
http://clk.atdmt.com/MRT/go/vse0120000070mrt/direct/01/
_______________________________________________
Swig-user mailing list
Swig-user@...
https://lists.sourceforge.net/lists/listinfo/swig-user

Re: Returning structures by value in .NET with P/Invoke

by David Piepgrass :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

> David Piepgrass wrote:
> > Maybe I'm the only one dealing with this issue, but I'll post it
anyway
> > for the record.
> >
> > I'd just like to add a new wrinkle to this old thread. It seems that
if
> > the structure you return has a constructor, then the C++ compiler
> > decides to add 4 to the number at the end of the exported name, and
thus
> > the C++ and C# exported names match. See, in the example below I
suggest
> > declaring an alias in your .def file:
> >
> >    _CSharp_CreateSquareBox@16 = _CSharp_CreateSquareBox@12
> >
> > BUT if the Box structure is given a constructor, the C++ compiler
will
> > (or at least might) decide to use the name
_CSharp_CreateSquareBox@16 in
> > the first place. Consequently the line in the .def file is not only
> > unnecessary but illegal and you get this linker output:
> >
> > warning LNK4197: export '_CSharp_CreateSquareBox@16' specified
multiple
> > times; using first specification
> > error LNK2001: unresolved external symbol _CSharp_CreateSquareBox@12
> >
> > Gee whiz, Microsoft does weird things. Also, when you give your
> > structure a constructor, the C++ compiler will give you a warning
> >
> This all strikes me as a bug in the CLR. Maybe you discuss this on a
> Microsoft .NET mailing list for confirmation and let us know the
> outcome? It is nearly impossible to auto generate code for this kind
of
> weirdness and it would be nice to get support for pass by value into
> swig. There isn't this kind of weirdness on Linux btw, I'm sure I got
> this working a few years ago with mono and Linux, but never took it
any
> further as it doesn't work on the MS platform.

I'm not suggesting that SWIG attempt to generate code for this; I just
wanted to complete the picture on this issue. If I may correct a
misconception in your comment: while there may be a bug in the CLR, the
issue I talk about here is entirely on the C++ side (how would the CLR
know if my C++ class has a constructor or not?). And IIRC, there is no
problem with passing structs by value--only with returning them.

I just put a bug report on Microsoft Connect:

https://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?Fe
edbackID=345096

But in my experience, Microsoft fixes things rather slowly.


-------------------------------------------------------------------------
This SF.net email is sponsored by: Microsoft
Defy all challenges. Microsoft(R) Visual Studio 2008.
http://clk.atdmt.com/MRT/go/vse0120000070mrt/direct/01/
_______________________________________________
Swig-user mailing list
Swig-user@...
https://lists.sourceforge.net/lists/listinfo/swig-user