Shamtam
Shamtam

Reputation: 113

Marshaling strings, pointers in C# (Writing a wrapper for C DLL in C# with P/Invoke)

I'm currently trying to write a wrapper for a USB camera that uses an unmanaged C DLL to interface. The supplied software is written in C++ using Qt, and I'm trying to integrate this camera in a C# application I've already written (the application already uses a different camera that is supplied with a C# interface). I've read a bunch about unmanaged/managed interfacing, but I still am getting stuck with this specific DLL.

I'm having trouble dealing with P/Invoking the DLL. I've done a bunch of reading on ways to deal with managed/unmanaged code, and it seems that since I only have the functions/parameters (and nothing else), I can't write a C++/CLI wrapper and am forced to stick to P/Invoke. If this is untrue, some help with an alternate method would be appreciated, if more efficient.

Anyhow, I'm currently having trouble calling some (read: all) of the functions. For examples sake, let's pick this function (taken from dijSDK.h, the header file to be used when using this interface in unmanaged C++ in Qt):

/** \brief Find cameras currently connected to the host
  *
  * \param[out]      pGuidList   List of unique identifiers of connected cameras; memory is allocated by user
  * \param[in,out]   pNumGuids   Pointer to the number of elements in pGuidList to limit the search
  * \n                           Pointer to the number of cameras found
  * \param[in]       mask        optional mask to limit the results in pGuidList
  *
  * \note
  * -    the function lists all supported cameras that are connected to the host having a driver installed
  * -    the cameras are identified by a string in a defined style:
  * \n  <i>[Name of the camera library resp. camera class]:[camera name]:[Serial number of the camera]</i>
  * -    the optional parameter mask may be used to limit the results returned in pGuidList; 
  * \n   mask is in the same style as the results in pGuidList 
  *      therefore the results can be limited to certain camera classes, camera types,
  *      and cameras with a given serial number */

DIJSDK_EXPORT error_t DijSDK_FindCameras(DijSDK_CamGuid* pGuidList, unsigned int* pNumGuids, const DijSDK_CamGuid mask = NULL);

DIJSDK_EXPORT is a define as follows:

#ifndef DIJSDK_EXPORT
 #define DIJSDK_EXPORT externC DLLEXPORT
#endif

And the two typedefs used in the above function:

/// Return value of all DijSDK functions
/// \note All return values of type error_t can be casted to the enum <b>DijSDK_EErrorCodeList</b>, see header errorlistinstall.h
typedef int error_t;

// unique DijSDK types
/// Globally unique identifier for all supported cameras. It is used to establish a relation between <b>physical</b> and <b>logical</b> cameras.
typedef char   DijSDK_CamGuid[64];

So looking at the function, the user passes in an allocated string array and the number of allocated strings in that array, and the function should return a bunch of strings and the number of strings it returns. I have absolutely no idea how to implement this in C#. I've tried different DllImport declarations of the function, like passing pGuidList as a...

Where the variable I passed in was initialized with var = new StringBuilder(64) and the uint passed in was just 1.

Where the variable I passed in was initialized with var = new StringBuilder[length], where length was the uint passed to the function.

And then passed in pNumGuids as ref uint. I don't hand in an argument for mask since it is optional, and I don't need to use it.

I constantly get a PInvokeStackImbalance since the signature of the managed import and the unmanaged DLL don't match, but I can't figure out what the correct signature is.

I've got questions about all of the functions I'm dealing with, but I'll be dealing with them one at a time. The first function I use is an "init" function that takes in a function pointer argument and a void pointer argument for a callback and it's associated data, but I can call it with no arguments if I don't need to use the callback and that is P/Invoking just fine. The function in this question is the one I need to call after to get a list of attached devices. I think if I can get this one working, I'll be able to figure out most of the rest of the functions.

The SDK and documentation I'm working with isn't publicly available online from the company that supplies it. If anyone would like it to see all of the code/documentation, I can post it on a fileserver and post a link to it.

Any help would be much appreciated! If there's anything that I left out, make sure to heckle me about it =P

Solution: After the help, I managed to get it working:

The DllImport:

[DllImport(DLL,CallingConvention=CallingConvention.Cdecl)]
        public static extern error_t DijSDK_FindCameras([Out] DijSDK_CamGuid[] pCamGuid, ref uint pNumGuids, string mask);

The struct to use for DijSDK_CamGuid:

[StructLayout(LayoutKind.Sequential,Size=64)]
public struct DijSDK_CamGuid
{
    [MarshalAs(UnmanagedType.ByValArray,SizeConst=64)]
    public char[] id;
}

And the findCameras() function:

private void findCameras()
{
     uint cameralistlen = 5;
     DijSDK_CamGuid[] cameralist = new DijSDK_CamGuid[cameralistlen];
     result = Jenoptik.DijSDK_FindCameras(cameralist, ref cameralistlen, null);
}

Upvotes: 1

Views: 1722

Answers (2)

someone else
someone else

Reputation: 321

Try declaring this way:

    [DllImport("whatever.dll", CallingConvention = CallingConvention.Cdecl)]
    extern public static int DijSDK_FindCameras(byte[] pGuidList, ref int pNumGuids, byte[] mask);

and using it like this:

    byte[] CamGuids = new byte[64 * 32];    // Up to 32 cameras
    int GuidsCount = CamGuids.Length / 64;
    int result = DijSDK_FindCameras(CamGuids, ref GuidsCount, null);

Upvotes: 1

jlew
jlew

Reputation: 10591

Do you need to deal with the id in managed code as a string? If not, perhaps something like this would work, treating the bytes as an opaque byte array:

[StructLayout(LayoutKind.Sequential)]
public struct DijSDK_CamGuid
{
  [MarshalAs(UnmanagedType.ByValAray,SizeConst=64)]
  public byte[] id;
}

Then declare your p/invoke signature to use take a parameter of type:

DijSDK_CamGuid* 

Upvotes: 1

Related Questions