.NET Forum / .NET Framework / Interop / March 2006
Obscure marshalling/pinvoke problem with 1, 2, 4, and 8 bytes structures
|
|
Thread rating:  |
whodges - 27 Mar 2006 08:07 GMT Hey all:
I'm having *exactly* the same problem that Torbjørn Vik described in two of his posts titled "PInvoke failure" and "Obscure marshalling problems (NullReferenceException)" (found them on Google Groups). He unfortunately didn't receive a reply, so now I'm trying. :)
Basically, the problem is that I'm calling an exported C++ DLL function from a C# application (I've actually use SWIG to wrap the C++ classes as C# ones, if you're curious). I'm marshalling structures containing primitive types between C++ and C# via these DLL calls. Here's a stripped down example of what I'm doing:
C++ Side -------------------------- CPP.h:
struct TestStruct { public: TestStruct( ) { x = 0; f = 0; } TestStruct(int x0, float f0) { x = x0; f = f0; }
int x; float f; };
class ExportedClass { public: static void SetTest(TestStruct t) { TestStruct test = t; } static TestStruct GetTest() { return TestStruct(); } }
ExportedClass.cxx:
// Here's there's some exported __stdcall DLL functions that call ExportedClass::SetTest // and ExportedClass::GetTest - i'm fairly certain these are set up just fine.
C# Side ---------------------------
TestStruct.cs:
[StructLayout(LayoutKind.Sequential)] public struct TestStruct { public int x; public float f;
public TestStruct(int x0, float f0) { x = x0; f = f0; } }
TestPINVOKE.cs:
// Here's there's some .NET DllImport statements that import the function defined in // ExportedClass.cxx. It only has the EntryPoint attribute.
ExportedClass.cs:
public class ExportedClass { public static TestStruct GetTest( ) { return TestPINVOKE.<exported GetTest function>(); } public static void SetTest(TestStruct t) { TestPINVOKE.<exported SetTest function>(t); } }
TestCode.cs:
ExportedClass.SetTest(new TestStruct(1, 2)); TestStruct t = ExportedClass.GetTest();
------------------------------
Ok, so, that's a messy nutshell of what I'm doing. Now, TestStruct in my example is 8 bytes in size. For some reason, this causes the call to ExportedClass.GetTest() to fail with an "unable to find entry point named..." exception. The ExportedClass.SetTest works fine (checked everything out). Now, GetTest *only* fails if TestStruct is 1, 2, 4, or 8 bytes in size. It works perfectly for *any other structure sizes*. If, for example, I were to insert an additional float into TestStruct on both the C++ and C# side, GetTest would work fine since the structure is now 12 bytes. It's baffling.
To add to the weirdness, if I remove TestStruct's constructors from the C++ side, GetTest works fine. As a side note, both the C++ and C# versions of TestStruct *must* be the same size in bytes for SetTest to work. Otherwise it fails too. The makes sense though.
Now, I'm not at liberty to alter the C++ code, so I just can't go about sticking a char or byte in to bump the struct to 9 bytes and avoid this whole messed. I have a bunch of other 16 byte structures that I'm marshalling back and forth between unmanaged and managed code with absolutely no trouble. It uses exactly the same mechanism as I described above.
If *anybody* out there has seen anything like this, or has some ideas, please, I'm all ears. I've spent about 30 hours on this problem and it's driving my nuts. I've played with a lot of the C++ linking and code generation settings (structure alignment, __cdecl/__stdcall, etc.), along with the Pack and Size attributes of StructLayout and the CallingConventions attribute of DllImport. Suggestions?
Willy Denoyette [MVP] - 27 Mar 2006 12:07 GMT And what is SWIG ?
Willy.
Hey all:
I'm having *exactly* the same problem that Torbjørn Vik described in two of his posts titled "PInvoke failure" and "Obscure marshalling problems (NullReferenceException)" (found them on Google Groups). He unfortunately didn't receive a reply, so now I'm trying. :)
Basically, the problem is that I'm calling an exported C++ DLL function from a C# application (I've actually use SWIG to wrap the C++ classes as C# ones, if you're curious). I'm marshalling structures containing primitive types between C++ and C# via these DLL calls. Here's a stripped down example of what I'm doing:
C++ Side -------------------------- CPP.h:
struct TestStruct { public: TestStruct( ) { x = 0; f = 0; } TestStruct(int x0, float f0) { x = x0; f = f0; }
int x; float f; };
class ExportedClass { public: static void SetTest(TestStruct t) { TestStruct test = t; } static TestStruct GetTest() { return TestStruct(); } }
ExportedClass.cxx:
// Here's there's some exported __stdcall DLL functions that call ExportedClass::SetTest // and ExportedClass::GetTest - i'm fairly certain these are set up just fine.
C# Side ---------------------------
TestStruct.cs:
[StructLayout(LayoutKind.Sequential)] public struct TestStruct { public int x; public float f;
public TestStruct(int x0, float f0) { x = x0; f = f0; } }
TestPINVOKE.cs:
// Here's there's some .NET DllImport statements that import the function defined in // ExportedClass.cxx. It only has the EntryPoint attribute.
ExportedClass.cs:
public class ExportedClass { public static TestStruct GetTest( ) { return TestPINVOKE.<exported GetTest function>(); } public static void SetTest(TestStruct t) { TestPINVOKE.<exported SetTest function>(t); } }
TestCode.cs:
ExportedClass.SetTest(new TestStruct(1, 2)); TestStruct t = ExportedClass.GetTest();
------------------------------
Ok, so, that's a messy nutshell of what I'm doing. Now, TestStruct in my example is 8 bytes in size. For some reason, this causes the call to ExportedClass.GetTest() to fail with an "unable to find entry point named..." exception. The ExportedClass.SetTest works fine (checked everything out). Now, GetTest *only* fails if TestStruct is 1, 2, 4, or 8 bytes in size. It works perfectly for *any other structure sizes*. If, for example, I were to insert an additional float into TestStruct on both the C++ and C# side, GetTest would work fine since the structure is now 12 bytes. It's baffling.
To add to the weirdness, if I remove TestStruct's constructors from the C++ side, GetTest works fine. As a side note, both the C++ and C# versions of TestStruct *must* be the same size in bytes for SetTest to work. Otherwise it fails too. The makes sense though.
Now, I'm not at liberty to alter the C++ code, so I just can't go about sticking a char or byte in to bump the struct to 9 bytes and avoid this whole messed. I have a bunch of other 16 byte structures that I'm marshalling back and forth between unmanaged and managed code with absolutely no trouble. It uses exactly the same mechanism as I described above.
If *anybody* out there has seen anything like this, or has some ideas, please, I'm all ears. I've spent about 30 hours on this problem and it's driving my nuts. I've played with a lot of the C++ linking and code generation settings (structure alignment, __cdecl/__stdcall, etc.), along with the Pack and Size attributes of StructLayout and the CallingConventions attribute of DllImport. Suggestions?
The Real Andy - 27 Mar 2006 13:45 GMT >And what is SWIG ? SWIG is some open source tool to wrap C style functions to scripting and JIT languages/ among others.
>Willy. > [quoted text clipped - 108 lines] >etc.), along with the Pack and Size attributes of StructLayout and the >CallingConventions attribute of DllImport. Suggestions? whodges - 27 Mar 2006 15:15 GMT yup - SWIG stands for Simplified Wrapper and Interface Generator. but SWIG shouldn't have anything to do with it. i can build a simple test app that will give me the same error without using SWIG. the basic problem is that i'm marshalling some small 1, 2, 4, or byte structures back from a C++ DLL to C#, and i wind up with exceptions (unless i remove that structure's C++ constructor). if you check out Torbjørn Vik's posts, he seems to think the data is being offset by 4 bytes or something. haven't really noticed that with mine, but i haven't dove that deep into the stack. i guess i could start with a more basic question: has anyone been able to return a 1, 2, 4, or 8 bytes structure from C++ to C#?
Mattias Sjögren - 27 Mar 2006 18:14 GMT Make sure the exported entry points have non-mangled names (e.g. SetTest instead of _SetTest@8)
or
Set the EntryPoint property of the DllImport attribute to the actual, mangled name with which the function is exported.
Mattias
 Signature Mattias Sjögren [C# MVP] mattias @ mvps.org http://www.msjogren.net/dotnet/ | http://www.dotnetinterop.com Please reply only to the newsgroup.
whodges - 27 Mar 2006 20:20 GMT i wish it were that simple. unfortunately the name-mangling isn't the problem, although i do get a different error message when i use the mangled name. still the same issue though (see below).
ok, here's a complete, super-simple code example of what i'm doing (pardon the length if it seems winded). i've created a C# console app solution using VS 2005 (.NET 2.0), and have added an empty C++ DLL project to it. the C++ DLL contains Structures.h and Exports.cpp. the C# project contains Program.cs and Structure.cs. here are their contents:
-- Structures.h --------------------------------------------------------------------------
#ifndef __STRUCTURES_H__ #define __STRUCTURES_H__
struct Structure { public: Structure() { a = 0; b = 0; c = 0; d = 0; e = 0; f = 0; g = 0; h = 0; }
Structure(unsigned char a0, unsigned char b0, unsigned char c0, unsigned char d0, unsigned char e0, unsigned char f0, unsigned char g0, unsigned char h0) { a = a0; b = b0; c = c0; d = d0; e = e0; f = f0; g = g0; h = h0; }
public: unsigned char a; unsigned char b; unsigned char c; unsigned char d; unsigned char e; unsigned char f; unsigned char g; unsigned char h; };
#endif
-- Exports.cpp ----------------------------------------------------------
#include "Structures.h"
#define DLLEXPORT __declspec(dllexport) #define STDCALL __stdcall
extern "C" {
DLLEXPORT void STDCALL SetStructure(Structure s) { Structure copy = s; return; }
DLLEXPORT Structure STDCALL GetStructure() { Structure s = Structure(8, 7, 6, 5, 4, 3, 2, 1); return s; }
} // extern "C"
-- Structures.cs -----------------------------------------------------------
using System; using System.Runtime.InteropServices;
namespace MarshallingTest { [StructLayout(LayoutKind.Sequential)] public struct Structure { public byte a; public byte b; public byte c; public byte d; public byte e; public byte f; public byte g; public byte h;
public Structure(byte a0, byte b0, byte c0, byte d0, byte e0, byte f0, byte g0, byte h0) { a = a0; b = b0; c = c0; d = d0; e = e0; f = f0; g = g0; h = h0; } } }
-- Program.cs --------------------------------------------------------------------------
using System; using System.Runtime.InteropServices;
namespace MarshallingTest { class Program { static void Main(string[] args) { Structure s = new Structure(1, 2, 3, 4, 5, 6, 7, 8); int size = Marshal.SizeOf(s); Console.Write(String.Format("Structure size in bytes: {0}\n", size));
// 'Set' test
bool setFailed = false; try { SetStructure(s); } catch { setFailed = true; }
if (setFailed) Console.Write("'Set' function failed\n"); else Console.Write("'Set' function passed\n");
// 'Get' test
bool getFailed = false; try { s = GetStructure(); } catch { getFailed = true; }
if (getFailed) Console.Write("'Get' function failed\n"); else Console.Write("'Get' function passed\n"); }
[DllImport("UnmanagedCode.dll", EntryPoint = "SetStructure")] public static extern void SetStructure(Structure s);
[DllImport("UnmanagedCode.dll", EntryPoint = "GetStructure")] public static extern Structure GetStructure(); } }
-- end ----------------------------------------------------------------------------------
now, when i call GetStructure in Program.cs, i should get a C# Structure object 's' where s.a == 8, s.b == 7, etc. all the way down to s.h = 1. however, what i end up with is some form of marshalling error. if i run the code as it is above, i end up with a "cannot find entry point" exception when i call GetStructure. if i modify the code so that the 'Entry Point' attributes in the DllImport statements are set to the mangled names of the exported functions (as per Mattias's suggestion - e.g. _GetStructure@4 and _SetStructure@8), i end up with this error:
"Managed Debugging Assistant 'PInvokeStackImbalance' has detected a problem in 'C:\Projects\MarshallingTest\MarshallingTest\bin\Debug\MarshallingTest.exe'. Additional Information: A call to PInvoke function 'MarshallingTest!MarshallingTest.Program::GetStructure' has unbalanced the stack. This is likely because the managed PInvoke signature does not match the unmanaged target signature. Check that the calling convention and parameters of the PInvoke signature match the target unmanaged signature."
i think the same problem is at work in both cases. now, if i use a .def file to export the functions instead, i run into the same issue, only now it says that the memory has been corrupted. again, same problem, slightly different error message.
here's where it gets interesting: if i comment out the 'h' member from both the C# and C++ Structure struct (i.e. so now it's 7 bytes in size rather than 8), then everything works fine. both SetStructure and GetStructure return successfully with the proper results. same thing if i were to add a member 'i' to both Structures in addition to 'h' so that i had a 9 byte structure. that works too. it works fine for any size structure *except* a 1, 2, 4, or 8 byte one. btw, 16 bytes and 32 bytes work fine as well, so the 'power of two' pattern doesn't continue past 8.
and to add to the weirdness that i mentioned before: if i remove the constructors from Structures.h on the C++ side, everything works fine again. 1, 2, 4, or 8 bytes - it all works as long as the constructors are gone and i just manually initialize the public members.
i'd love it if someone actually gave this code a try. i'm fairly certain you'll run into exactly the same problem. i'm open to *any* ideas or suggestions.
> Make sure the exported entry points have non-mangled names (e.g. > SetTest instead of _SetTest@8) [quoted text clipped - 10 lines] > http://www.msjogren.net/dotnet/ | http://www.dotnetinterop.com > Please reply only to the newsgroup. The Real Andy - 27 Mar 2006 22:33 GMT You are not actually doing any marshalling of the struct. A struct is a non-blittable type so you need to provide PInvoke on the exact way to marshal it.
[DllImport("UnmanagedCode.dll", EntryPoint = "SetStructure")] public static extern void SetStructure( [MarshalAs(UnmanagedType.Struct)] Structure s);
I dont have time now, but i will try and knock up an example tonight.
>i wish it were that simple. unfortunately the name-mangling isn't the >problem, although i do get a different error message when i use the [quoted text clipped - 236 lines] >> http://www.msjogren.net/dotnet/ | http://www.dotnetinterop.com >> Please reply only to the newsgroup. whodges - 27 Mar 2006 22:27 GMT i wish it were that simple. unfortunately the name-mangling isn't the problem, although i do get a different error message when i use the mangled name. still the same issue though (see below).
ok, here's a complete, super-simple code example of what i'm doing (pardon the length if it seems winded). i've created a C# console app solution using VS 2005 (.NET 2.0), and have added an empty C++ DLL project to it. the C++ DLL contains Structures.h and Exports.cpp. the C# project contains Program.cs and Structure.cs. here are their contents:
-- Structures.h --------------------------------------------------------------------------
#ifndef __STRUCTURES_H__ #define __STRUCTURES_H__
struct Structure { public: Structure() { a = 0; b = 0; c = 0; d = 0; e = 0; f = 0; g = 0; h = 0; }
Structure(unsigned char a0, unsigned char b0, unsigned char c0, unsigned char d0, unsigned char e0, unsigned char f0, unsigned char g0, unsigned char h0) { a = a0; b = b0; c = c0; d = d0; e = e0; f = f0; g = g0; h = h0; }
public: unsigned char a; unsigned char b; unsigned char c; unsigned char d; unsigned char e; unsigned char f; unsigned char g; unsigned char h;
};
#endif
-- Exports.cpp ----------------------------------------------------------
#include "Structures.h"
#define DLLEXPORT __declspec(dllexport) #define STDCALL __stdcall
extern "C" {
DLLEXPORT void STDCALL SetStructure(Structure s) { Structure copy = s; return;
}
DLLEXPORT Structure STDCALL GetStructure() { Structure s = Structure(8, 7, 6, 5, 4, 3, 2, 1); return s;
} } // extern "C"
-- Structures.cs -----------------------------------------------------------
using System; using System.Runtime.InteropServices;
namespace MarshallingTest { [StructLayout(LayoutKind.Sequential)] public struct Structure { public byte a; public byte b; public byte c; public byte d; public byte e; public byte f; public byte g; public byte h;
public Structure(byte a0, byte b0, byte c0, byte d0, byte e0, byte f0, byte g0, byte h0) { a = a0; b = b0; c = c0; d = d0; e = e0; f = f0; g = g0; h = h0; } }
}
-- Program.cs --------------------------------------------------------------------------
using System; using System.Runtime.InteropServices;
namespace MarshallingTest { class Program { static void Main(string[] args) { Structure s = new Structure(1, 2, 3, 4, 5, 6, 7, 8); int size = Marshal.SizeOf(s); Console.Write(String.Format("Structure size in bytes: {0}\n", size));
// 'Set' test
bool setFailed = false; try { SetStructure(s); } catch { setFailed = true; }
if (setFailed) Console.Write("'Set' function failed\n"); else Console.Write("'Set' function passed\n");
// 'Get' test
bool getFailed = false; try { s = GetStructure(); } catch { getFailed = true; }
if (getFailed) Console.Write("'Get' function failed\n"); else Console.Write("'Get' function passed\n"); }
[DllImport("UnmanagedCode.dll", EntryPoint = "SetStructure")] public static extern void SetStructure(Structure s);
[DllImport("UnmanagedCode.dll", EntryPoint = "GetStructure")] public static extern Structure GetStructure(); }
}
-- end ----------------------------------------------------------------------------------
now, when i call GetStructure in Program.cs, i should get a C# Structure object 's' where s.a == 8, s.b == 7, etc. all the way down to s.h = 1. however, what i end up with is some form of marshalling error. if i run the code as it is above, i end up with a "cannot find entry point" exception when i call GetStructure. if i modify the code so that the 'Entry Point' attributes in the DllImport statements are set to the mangled names of the exported functions (as per Mattias's suggestion - e.g. _GetStructure@4 and _SetStructure@8), i end up with this error:
"Managed Debugging Assistant 'PInvokeStackImbalance' has detected a problem in 'C:\Projects\MarshallingTest\MarshallingTest\bin\Debug\MarshallingTest.exe'. Additional Information: A call to PInvoke function 'MarshallingTest!MarshallingTest.Program::GetStructure' has unbalanced the stack. This is likely because the managed PInvoke signature does not match the unmanaged target signature. Check that the calling convention and parameters of the PInvoke signature match the target unmanaged signature."
i think the same problem is at work in both cases. now, if i use a .def file to export the functions instead, i run into the same issue, only now it says that the memory has been corrupted. again, same problem, slightly different error message.
here's where it gets interesting: if i comment out the 'h' member from both the C# and C++ Structure struct (i.e. so now it's 7 bytes in size rather than 8), then everything works fine. both SetStructure and GetStructure return successfully with the proper results. same thing if i were to add a member 'i' to both Structures in addition to 'h' so that i had a 9 byte structure. that works too. it works fine for any size structure *except* a 1, 2, 4, or 8 byte one. btw, 16 bytes and 32 bytes work fine as well, so the 'power of two' pattern doesn't continue past 8.
and to add to the weirdness that i mentioned before: if i remove the constructors from Structures.h on the C++ side, everything works fine again. 1, 2, 4, or 8 bytes - it all works as long as the constructors are gone and i just manually initialize the public members.
i'd love it if someone actually gave this code a try. i'm fairly certain you'll run into exactly the same problem. i'm open to *any* ideas or suggestions.
> Make sure the exported entry points have non-mangled names (e.g. > SetTest instead of _SetTest@8) [quoted text clipped - 5 lines] > > Mattias Willy Denoyette [MVP] - 27 Mar 2006 23:19 GMT You can't return UDT's from functions with C linkage. You do get a compiler warning don't you? You have to create the struct on the heap and return the address of the struct to C# as an IntPtr, in C# you have to marshal the structure from unmanaged memory to managed memory using Marshal.PtrToStructure.
IntPtr ptr = IntPtr.Zero; ptr = GetStructure(); Structure st = (Structure)Marshal.PtrToStructure(ptr, typeof(Structure)); Console.Write(st.a); }
[DllImport("csub.dll", EntryPoint = "SetStructure")] public static extern void SetStructure(Structure s);
[DllImport("csub.dll", EntryPoint = "GetStructure")] public static extern IntPtr GetStructure();
... DLLEXPORT Stru* STDCALL GetStructure() { // Heap allocated, someone will have to delete this structure!! Stru *s = new Stru(8, 7, 6, 5, 4, 3, 2, 1); return s; }
Willy.
|i wish it were that simple. unfortunately the name-mangling isn't the | problem, although i do get a different error message when i use the [quoted text clipped - 176 lines] | | [DllImport("UnmanagedCode.dll", EntryPoint = "GetStructure")]
| public static extern Structure GetStructure(); | } [quoted text clipped - 16 lines] | "Managed Debugging Assistant 'PInvokeStackImbalance' has detected a | problem in 'C:\Projects\MarshallingTest\MarshallingTest\bin\Debug\MarshallingTest.exe'.
| Additional Information: A call to PInvoke function | 'MarshallingTest!MarshallingTest.Program::GetStructure' has unbalanced [quoted text clipped - 38 lines] | > | > Mattias Willy Denoyette [MVP] - 27 Mar 2006 23:36 GMT Note that this only shows how to marshall from unmanaged to managed, but you also need to do the same in the other direction. That is, you need to create the structure in C# and marshal to unmanaged memory using Marshal.StructureToPtr, the unmanaged memory can be allocated from C# using AllocHGlobal and de-allocated using FreeHGlobal. However,if you allocate unmanaged memory in C++ you will have to de-allocate in C++ as well.
Following is the complete code that should work. .. class Program { static void Main(string[] args) { Structure s = new Structure(1, 2, 3, 4, 5, 6, 7, 8); int size = Marshal.SizeOf(s); IntPtr pnt = IntPtr.Zero; Console.Write(String.Format("Structure size in bytes: {0}\n",size)); try { pnt = Marshal.AllocHGlobal(size); // Copy to unmanaged memory Marshal.StructureToPtr(s, pnt, false); SetStructure(pnt); IntPtr ptr = IntPtr.Zero; ptr = GetStructure(); Structure st = (Structure)Marshal.PtrToStructure(ptr, typeof(Structure)); Console.Write(st.a); } finally { Marshal.FreeHGlobal(pnt); } }
[DllImport("csub.dll", EntryPoint = "SetStructure")] public static extern void SetStructure(IntPtr s);
[DllImport("csub.dll", EntryPoint = "GetStructure")] public static extern IntPtr GetStructure(); }
#include "structures.h" #define DLLEXPORT __declspec(dllexport) #define STDCALL __stdcall
extern "C" { DLLEXPORT void STDCALL SetStructure(void *s) { Structure *copy = static_cast<Structure*>(s); return;
}
DLLEXPORT Structure* STDCALL GetStructure() { Structure *s = new Structure(8, 7, 6, 5, 4, 3, 2, 1); return s;
} } // extern "C"
Willy.
| You can't return UDT's from functions with C linkage. You do get a compiler | warning don't you? [quoted text clipped - 228 lines] || "Managed Debugging Assistant 'PInvokeStackImbalance' has detected a || problem in 'C:\Projects\MarshallingTest\MarshallingTest\bin\Debug\MarshallingTest.exe'.
|| Additional Information: A call to PInvoke function || 'MarshallingTest!MarshallingTest.Program::GetStructure' has unbalanced [quoted text clipped - 38 lines] || > || > Mattias whodges - 27 Mar 2006 23:54 GMT yes, you're right - in the example i provided, you get the 'C linkage' warning. my mistake. but if i were to remove the 'extern "C"' declarations and, let's say, use a .def file instead to export SetStructure and GetStructure so that I can return actual objects from my DLL, would I be able to return Structure objects to C#? or do i *have* to return pointers to Structure objects allocated on the heap?
i tried modifying GetStructure as you suggested to return a pointer to a Structure, and it works, but i'd really just like to return a copy of the Structure object itself without having to resort to using new or pointerss. is this possible? again, it seems to work for *all* structures that aren't 1, 2, 4, or 8 bytes in size.
thanks for your help so far - i really appreciate it.
Wes
> You can't return UDT's from functions with C linkage. You do get a compiler > warning don't you? [quoted text clipped - 272 lines] > | > > | > Mattias Willy Denoyette [MVP] - 28 Mar 2006 15:28 GMT | yes, you're right - in the example i provided, you get the 'C linkage' | warning. my mistake. but if i were to remove the 'extern "C"' declarations [quoted text clipped - 8 lines] | is this possible? again, it seems to work for *all* structures that aren't | 1, 2, 4, or 8 bytes in size. No, there is simply no way to retrun a 'copy' of a structure, a function return is always a pointer or a value that fits in a register or a couple of registers (32 or 64 value). Note that this is true whenever you are using "C language" binding, the return of a function is by convention an int value, so it's up to you to marshal the struct from managed to unmanaged memory and back.
Willy.
whodges - 28 Mar 2006 22:43 GMT ok, thanks Willy, but i have to make absolutely sure we're understanding each other correctly, because if what you say is true, i'm in for a lot grief. bear with me for a bit longer. i'll split this into two cases -- passing parameters from managed code to unmanaged code (1), and accepting return values from unmanaged code into managed code (2):
1) for passing UDT parameters to a DLL via PInvoke, are you saying that i *have* to marshal them to an unmanaged struct before i pass them? does that mean the following code is insufficient?:
Structure s = new Structure(); //C# struct w/ [StructLayout(LayoutKind.Sequential)] SetStructure(s);
where SetStructure comes from:
[DllImport("UnmanagedCode.dll", EntryPoint = "SetStructure")] public static extern void SetStructure([MarshalAs(UnmanagedType.Struct)]Structure s);
doesn't the [MarshalAs(UnmanagedType.Struct)] attribute in the DllImport statement 'automatically' handle the marshalling for me? it certainly seems too, and is consistent with what i've been reading. haven't run into any problems in this area. i've tried passing output parameters with the 'ref' keyword to exported functions too. that also works fine - they end up populated properly.
2) return values seem to be a different story. ignoring the whole C linkage thing, what exactly do you mean by "a function return is always a pointer or a value that fits in a register or a couple of registers"? are you talking about return values at the low-level assembly level? or are you talking about return values from exported DLL functions? my knowledge of return values from DLLs is fuzzy for sure - i *do* know that there's some restrictions and trickiness associated with them (reaching back to my MFC days, i also remember running into issues with allocating objects on a DLL's heap and then using or deleting those objects from within the main application). so, clean slate: let's say i have a DLL that exports C++ functions (*not* C functions, and no 'extern "C"' statements):
a) on the C++ side, can those exported C++ DLL functions return UDTs that aren't pointers? e.g. could the following function be exported:
Structure GetStructure(); // Structure is a C++ UDT
b) on the C# side, assuming i can do what i described in a), shouldn't it be possible to import the function like so:
[DllImport("UnmanagedCode.dll", EntryPoint = "GetStructure"] [return: MarshalAs(UnmanagedType.Struct)] public static extern Structure GetStructure();
where 'Structure' is once again a C# struct with the [StructLayout(LayoutKind.Sequential)] attribute and matches the layout of the C++ Structure type?
sorry for my persistence, but i just find it really, really odd that 1) works all the time regardless of the size of 'Structure', and 2) works all the time *unless* 'Structure' is 1, 2, 4, or 8 bytes in size and has a constructor on the C++ side. so, either i'm totally backwards on the whole thing and what i deem to be 'working' has really just a fluke (entirely possible - there's no sarcasm here), or there's some bug in the CLR or marshalling or pinvoke, or something. if it happens that i'm wrong, could you point me towards some decent pinvoke/marshalling documentation? books or online, it doesn't matter. i've gotta sort this out.
thanks again for your time.
Wes
> No, there is simply no way to retrun a 'copy' of a structure, a function > return is always a pointer or a value that fits in a register or a couple of [quoted text clipped - 4 lines] > > Willy. Willy Denoyette [MVP] - 29 Mar 2006 18:49 GMT | ok, thanks Willy, but i have to make absolutely sure we're understanding each | other correctly, because if what you say is true, i'm in for a lot grief. [quoted text clipped - 22 lines] | keyword to exported functions too. that also works fine - they end up | populated properly. No, this is sufficient for simple structures holding 'blitable' types, once you include non blitables like arrays or embedded structures, you'll have to 'custom marshal', sorry if I wasn't clear. Note that I don't see why you would pass a structure like this to C++, you would pass an array of bytes don't you?
| 2) return values seem to be a different story. ignoring the whole C linkage | thing, what exactly do you mean by "a function return is always a pointer or | a value that fits in a register or a couple of registers"? are you talking | about return values at the low-level assembly level? a "C function" is always compiled into a assembly code, and the "C linkage" (on X86) defines (by convention) that return values are stored in eax or eax and edx upon return (amongst other things). That means that a 32 bit value (can be a pointer), returns in eax, while 64 bit values (long in C#) returns in eax (lower 32bits) and edx (higher 32 bits). I'm sure you understand why such convention is needed, it's not possible to pass types between modules that are the result of different compiler (or tools) without respecting a convention (also called an ABI), so that each party knows exactly how values are passed and how values are returned, that's also why you have the choice between different calling conventions (cdecl, stdcall, ...) when using PInvoke.
or are you talking
| about return values from exported DLL functions? my knowledge of return | values from DLLs is fuzzy for sure - i *do* know that there's some [quoted text clipped - 3 lines] | application). so, clean slate: let's say i have a DLL that exports C++ | functions (*not* C functions, and no 'extern "C"' statements): Well, that's the whole point, C# or any other managed language, except C++/CLI (or ME C++), cannot call C++ member methods, it can only call non member functions exported with C linkage. Note that this is not a language limitation, it's due to: 1. the difference between the object models used by .NET and the different C++ compiler implementations and run-times. 2. that C++ does not define a "binary interface" (see above for the C linkage convention).
| a) on the C++ side, can those exported C++ DLL functions return UDTs that | aren't pointers? e.g. could the following function be exported: [quoted text clipped - 11 lines] | [StructLayout(LayoutKind.Sequential)] attribute and matches the layout of the | C++ Structure type? Well, this is moot because you can't call class methods this way, you can only call "C style" functions using PInvoke.
| sorry for my persistence, but i just find it really, really odd that 1) | works all the time regardless of the size of 'Structure', and 2) works all [quoted text clipped - 5 lines] | you point me towards some decent pinvoke/marshalling documentation? books or | online, it doesn't matter. i've gotta sort this out. Ok, lets see.
Case1: This works, irrespective the size of the structure. The interop layer is happy to marshal the structure from managed to unmanaged, no problem (things are different with more complex structs however).
extern "C" { DLLEXPORT void STDCALL SetStructure(Structure s) { Structure copy = s; return; }
Case2 : However, Compiling this will show you this warning: Warning C4190: 'GetStructure' has C-linkage specified, but returns UDT 'Structure' which is incompatible with C
And this will fail.
DLLEXPORT Structure STDCALL GetStructure() { Structure s = Structure( 8, 7, 6, 5, 4, 3, 2, 1); return s; }
and following run-time error:
System.EntryPointNotFoundException: Unable to find an entry point named 'GetStructure' in DLL .....
and you tried to outsmart the system by specifying the mangled name _GetStucture@4. While it seems to work for some structs, it destroys the stack in all cases, because the marshaler doesn't expect the UDT, and can't correctly deal with it , the return value is not respecting C linkage convention), and that's exactly why the run-time did not exposed the entrypoint.
So, the only option is to return a pointer to the structure and apply custom marshaling on the returned pointer, else you will need to use C++/CLI in mixed mode. More info about custom marshaling can be found in MSDN and in Adam Nathan's ".NET and COM. - The complete Interoparability Guide". http://www.amazon.com/gp/product/067232170X/103-3033892-8117418?v=glance&n=283155
Willy.
whodges - 30 Mar 2006 07:26 GMT hey Willy:
just wanted to say 'thanks' again for taking the time to help me, especially on that last post. i've got everything working properly now. couldn't have done it without your help. i appreciate it.
Wes
Free MagazinesGet these publications absolutely FREE for up to 12 months. There are no hidden fees and no obligation. Simply choose a title, complete the application form and submit it. Read more ...
|
|
|