All Marvin Test Solutions driver packages such as GxPS, GxSW, etc. come with a Windows DLL that can be called from a user created Windows application. Each driver package also includes examples and interface files (.vb and .h) for Visual Basic and C/C++ projects.
Before referencing such a DLL in a C# .NET project, a few steps must be taken to bridge the gap between the Unmanaged code in the DLL and the Managed code in the .NET project.
This can be accomplished by using a concept called Marshaling where unmanaged data types are converted to managed .NET CLR (Common Language Run-time) data types. Each .NET datatype has a default unmanaged data type that the CLR will use to marshal data back and forth between managed code and a unmanaged function. As an example, the C# data type string has a default unamnaged data type of LPTSTR. Note that the default marshaled datatype can be changed in the C# code. The figure below illustrates the relationship between the .NET classes and the Marvin Test Solutions DLL. Generally, the application class will reference function prototypes that are defined in a marshalling class which actually links to the DLL functions.
Follow these steps to use the DLL from C#:
1. Create a new .cs file (GxFpga.cs in this example) to contain a class that will import and define all the functions and constants in a Marvin Test Solutions DLL. This class should be contained in a namespace that will later be referenced in your main program with a "using" statement.
- Use the DllImport attritube within your C# code, to specify the name of the DLL that needs to be loaded.
- Use the static and extern C# keywords to declare a function prototype and name for the imported DLL function along with the correct parameter datatypes.
In this example the GxFpgaInitialize, GxFpgaGetDriverSummary and GxFpgaWriteMemory C functions along with some constants, will be imported into a C# .NET project:
- Use the ref keyword to delcare pointer parameters (data passed by reference) and the MarshalAs keyword to change the default marshaled datatype.
GxFpgaInitialize (SHORT nSlot, PSHORT pnHandle, PSHORT pnStatus);
GxFpgaGetDriverSummary (PSTR pszSummary , SHORT nSummaryMaxLen, PDWORD pdwVersion, PSHORT pnStatus);
GxFpgaWriteMemory(SHORT nHandle, DWORD dwOffset, PVOID pvData, DWORD dwSize, PSHORT pnStatus);
#define GXFPGA_LOAD_TARGET_VOLATILE 0
#define GXFPGA_LOAD_TARGET_EEPROM 1
The following is an example of the above header file converted to c# declaration:
public static extern void GxFpgaInitialize(short nSlot, ref short pnHandle, ref short pnStatus);
public static extern void GxFpgaGetDriverSummary([MarshalAs(UnmanagedType.LPStr)] StringBuilder sSummary, short nMaxLength, ref long pdwVersion, ref short pnStatus);
public static extern void GxFpgaWriteMemory(short nHandle, UInt32 dwOffset, UInt32 pvData, UInt32 dwSize, ref short pnStatus);
public static extern void GxFpgaReadMemory(short nHandle, UInt32 dwOffset, UInt32 pvData, UInt32 dwSize, ref short pnStatus);
public static extern void GxFpgaReset(short nHandle, ref short pnStatus);
// … more functions and definitions here
public const short GXFPGA_LOAD_TARGET_VOLATILE = 0;
public const short GXFPGA_LOAD_TARGET_EEPROM = 1;
// … more definitions here
Note that in the example above, [MarshalAs(UnmanagedType.LPStr)] was used to override the default marshaled datatype of LPTSTR (Unicode). The default marshaled datatype for the .NET data types short and long work correctly and do not need to be modified. The StringBuilder class should be used for passing strings by reference as shown in the GxFpgaGetDriverSummary function prototype above.
2. Add the .cs file created above to your C# .NET Project.
3. Include the marshalling class created in step 1, within your main project code with a "using" statement. Then call the imported DLL functions.
static void Main(string args)
short nHandle = 0, nStatus = 0;
long dwVersion = 0;
StringBuilder sSummary = new StringBuilder(256);
adwReadData = new UInt32;
adwWriteData = new UInt32;
for(i=0; i<10; i++)
adwWriteData[i] = i+2;
GxFpga.GxFpgaInitialize(35, ref nHandle, ref nStatus);
GxFpga.GxFpgaGetDriverSummary(sSummary, 256, ref dwVersion, ref nStatus);
GxFpga.GxFpgaReset(nHandle, ref nStatus);
GxFpga.GxFpgaWriteMemory(nHandle, 0, adwWriteData, 4*10, ref nStatus);
GxFpga.GxFpgaReadMemory(nHandle, 0, adwReadData, 4*10, ref nStatus);
Note that the ref keyword is also used when passing non-string parameters by reference. The StringBuilder class is used so that the buffer can be allocated before passing it to the GxFpgaGetDriverSummary. String would not work in this case because it is immutable.