In order to define a PInvoke method the you need the following information:
In order to pass a struct to through a PInvoke call you need the following informationfor the struct
The following metadata API's are used to provide this information when defining Pinvoke methods and structs:
To define a PInvoke method you would use:
DefinePinvokeMap: used to set the target dll, the name or ordinal of the method in that dll, flags for controling name mangling, character set, calling converntion and SetLastError support (pmNoMangle, pmCharSetAnsi, pmCharSetUnicode, pmCharSetAuto, pmSupportSetLastError, )
To define a PInvoke struct you would use:
DefineTypeDef: used to indicate swhether layout is sequential or explicit and to set the flags that control the default marshaling behavior of struct members (tdAnsi, tdUnicode, tdAutoClass)
SetClassLayout: used to set the packing size of the struct and the offset of the individual fields within the struct
Compilers have varying ways of expressing this information within their respective languages
C#:
[dllimport(KERNEL32, EntryPoint="MoveFile", SetLastError=true, CharSet=CharSet.Auto, ExactSpelling=true, CallingConvention=StdCall)] public static extern void MoveFile(String src, String dst); [structlayout(LayoutKind.Sequential, CharSet=CharSet.Unicode), Pack=4] public struct TypeDesc { public int lpValue; public WORD vt; } [structlayout(LayoutKind.Explicit, CharSet=CharSet.Unicode)] public struct TypeDesc { [structureOffset(0)]public int lpValue; [structureOffset(4)]public WORD vt; }
Note: dllimport and structlayout are C# specific keywords not custom attributes
MC++:
[sysimport(dll="user32", name="MessageBox", charset=unicode)] int MessageBox(DWORD hWnd, String strMessage, String strCaption, unsigned int uiType); Struct supported not defined yet
Note: sysimport is a MC++ specific keyword not a custom attribute
VB:
Declare Sub MessageBeep Lib "User" Alias "SomeBeep"(ByVal N As Integer) Declare Function GetWinFlags Lib "Kernel" Alias "#132"() As Long Struct supported not defined yet
Note: Declare is a VB specific keyword
The BCL currently has the following enums defined in the System.Runtime.InteropServices namespace for use in the languages as shown above:
namespace System.Runtime.InteropServices { public enum CallingConvention { Winapi = 1, Cdecl = 2, StdCall = 3, ThisCall = 4, FastCall = 5, } public enum LayoutKind { Sequential; Union; Explicit; } public enum CharSet { None = 1, // User didn't specify how to marshal strings. Ansi = 2, // Strings should be marshalled as ANSI 1 byte chars. Unicode = 3, // Strings should be marshalled as Unicode 2 byte chars. Auto = 4, // Marshal Strings in the right way for the target system. } }
Proposed New attributes
The following new attributes would give us a more consistent mechanism for attributing PInvoke classes. It would allow all compiler that support custom attributes to define PInvoke methods without changing their language syntax. I'm proposing that we add these new custom attributes and that we do the appropriate attribute to metadata mapping within the DefineCustomAttribute API.
namespace System.Runtime.InteropServices { public class DllImportAttribute { public DllImportAttribute(String dllName); // used to set the dll name passed to DefinePinvokeMap public String EntryPoint; // used to set the entry point passed to DefinePinvokeMap public CharSet CharSet; // used to set the pmCharSetAnsi, pmCharSetUnicode, pmCharSetAuto on DefinePinvokeMap public boolean SetLastError; // used to set the pmSupportSetLastError flag on DefinePinvokeMap public boolean ExactSpelling; // used to set the pmNoMangle flag on DefinePinvokeMap public CallingConvention CallingConvention; // used to set the pmNoMangle flag on DefinePinvokeMap // accessors public String DllName } public class StructLayoutAttribute { public StructLayoutAttribute(LayoutKind kind); // used to set the Layout kind passed to DefineTypeDef public int Pack; // used to set the packing size passed to SetClassLayout public CharSet CharSet; // used to set the charset passed to DefineTypeDef public boolean CheckFastMarshal; // Not sure how this is used? // accessors public LayoutKind StructLayoutKind; } }
Scrapbook
Explicitly specifying an unmanaged type is optional because every managed type is marshaled to a corresponding unmanaged type by default. So, for example, there’s no need to explicitly supply the unmanaged type for a method parameter typed as a long because a long is automatically marshaled to a long in the unmanaged space. The unmanaged type attribute is only needed to explicitly control the unmanaged type.
The marshaling done by the runtime’s marshaling service is limited. That is, it is not meant to be a generic marshaler mechanism that can marshal any type to any other type. Therefore, only a very restricted set of unmanaged types is allowed for each managed type. For example, a System.String class can be marshaled to BStr, LPStr or an LPWStr but not to something like a long or a Date.
As mentioned above, each managed type has a corresponding unmanaged data type. This corresponding type can be different depending on where the data type is used and what the ambient default settings are. There are three “places” marshaling attributes can be applied: on parameters & return values of P/Invoke functions, parameters & return values of functions defined in interfaces (for COM Interop) and fields of classes and value types. The ambient setting that can affect marshaling behavior are listed in sections toward the end of this document. An example is the CharSet attribute that controls whether managed strings map to ANSI, Unicode, or BStr, etc unmanaged strings