Calling unmanaged c++ dll with c#

Pious

Member
Joined
May 3, 2007
Messages
7
Hello

I have an old c++ dll (trdp.dll) inclusive .lib file.
With an Visual Studio 2005 C++ Project, I can use it implicit.
Now, I want to use the dll with a C# Project.
It doesn`t work.
I tryed two ways, calling the function (TrdpCreateLink(...))
1. Using C# conform Variables as parameters for the dll functions
2. Using "unsafe" and "fixed" functionality
Both times the same error occurs:
The function will not be executed and the Last Error = 1008
I don`t know what`s that kind of error and how to avoid it.
I added the code below.

Thanks for any help!

Michael


Function declaration in c++ lib:
[highlight=cpp]
extern "C" _declspec(dllexport) unsigned long far TrdpCreateLink (char* pszRemoteIPAddress,
unsigned short wRemotePortNum, unsigned short wLocalPortNum, unsigned long dwTimeout,
unsigned short* pLinkID);
[/highlight]

//1. try(conform variables):
...
C#:
using System.Runtime.InteropServices;

namespace DllCall
{
  public partial class Form1 : Form
  {

    [DllImport("trdp.dll", SetLastError = true)]
    public static extern ulong TrdpCreateLink(StringBuilder pszRemoteIPAddress, ushort wRemotePortNum, ushort wLocalPortNum, ulong dwTimeout, ref ushort pLinkID);
 
    ...
    private void button1_Click(object sender, EventArgs e)
    {

      StringBuilder lpszServerName = new StringBuilder("127.0.0.1");

      ushort LinkId = 0;
      ulong res = TrdpCreateLink(lpszServerName, 5001, 5002, 4294967295, ref LinkId);
      int LastErr = Marshal.GetLastWin32Error();
      // LastErr == 1008

    }
  }
}

//2. Try(unsafe, fixed):
...
C#:
using System.Runtime.InteropServices;


namespace DllCall
{
 
  public partial class Form1 : Form
  {
    [DllImport("trdp.dll", SetLastError = true)]
    public static unsafe extern ulong TrdpCreateLink(char* pszRemoteIPAddress, ushort wRemotePortNum, ushort wLocalPortNum, ulong dwTimeout, ushort* pLinkID);

    ...
    private void button1_Click(object sender, EventArgs e)
    {
      unsafe
      {
        String str =  "127.0.0.1";
        char[] ServerName = new char[20];
        
        str.CopyTo(0, ServerName, 0, 9);
                
        ushort b = 0;
        ushort* LinkId = &b;
      
        fixed (char* lpszServerName = ServerName)
        {
          ulong res = TrdpCreateLink(lpszServerName, 5001, 5002, 4294967295, LinkId);
        }
      }
      int LastErr = Marshal.GetLastWin32Error();
      // LastErr == 1008
    }
}
 
Last edited by a moderator:
You might try specifying the first parameter as string rather than a StringBuilder and see if that fixes the problem (it would be unusual to expect a native C / C++ routine to have any knowledge of the StringBuilder class).

Similarly
C#:
StringBuilder lpszServerName = new StringBuilder("127.0.0.1");

//would be better as 
string lpszServerName  = "127.0.0.1";
 
The computer is in a domain of a company network.
I`m in the group of administrators.
I`m developing on a local drive.
If I use a Visual Studio 2005 C++ Project under the same
conditions it works with that dll.
 
Have you tried changing the CallingConvention

e.g.
C#:
DllImport("trdp.dll", SetLastError = true, CallingConvention=CallingConvention.StdCall)
or whatever calling convention the DLL uses (my C knowledge is getting rusty...)
 
C++ long vs C# long

it would be unusual to expect a native C / C++ routine to have any knowledge of the StringBuilder class

MSDN actually suggests using a StringBuilder when the function expects a char/wchar buffer to write to - you initialize the StringBuilder to the size of the buffer.

To Pious however, I suggest using string unless you expect the function to write to the buffer, and apply a MarshalAs attribute to specify that it should be passed as a char* and not wchar*. The problem might also be that the unsigned long in C/C++ is a 32 bit data type, and ulong in C# is 64 bit. Certainly the prefix dw in dwTimeout suggests DWORD which is 32 bit - try using uint instead:

C#:
public static extern uint TrdpCreateLink(
    [MarshalAs(UnmanagedType.LPStr)] string pszRemoteIPAddress,
    ushort wRemotePortNum,
    ushort wLocalPortNum,
    uint dwTimeout,
    ref ushort pLinkID
);

Good luck :cool:
 
Last edited by a moderator:
Thank you very much, it works with your function declaration :)
The reason for the Last Error 1008 was not the CallingConvention
and also not the first parameter(String).
The problem was the difference between the type unsigned long of C++
and ulong of C#.
The first parameter works also with the types String and StringBuilder.

But, I tested another dll function with a parameter char *pBuffer.
There the function want to write a struct to the buffer.


...
StringBuilder strb = new StringBuilder(4096);
ushort BytesReceived = 0;
ushort MaxBytes = 4096;

TrdpRecv(pLinkID, strb, MaxBytes, ref BytesReceived);
int LastErr = Marshal.GetLastWin32Error();
//Last Error == 0


The function returns successful.
In my example there should be 26 bytes in the strb.
But there are just 2 bytes of scrap inside.

Do you have any ideas?
 
MarshalAs attribute

Always with strings, it is important to use the MarshalAs attribute to specify marshalling behaviour. The default marshalling behaviour is BStr which is certainly not what you want. Your function declaration (not shown) should be something like this:

C#:
static extern ... TrdpRecv(
    ...,
    [MarshalAs(UnmanagedType.LPStr)] StringBuilder buff,
    ...,
    ...
);

Good luck :)
 
Thanks, that works.
If I put a string into one side I can read the string out on the other side.
That leads me to the next problem.
I want to transfer a struct. So I want to put the struct into a StringBuilder object,
transfer it and put it out on the other side.
Please note, I searched hours in the internet for advices about that problem.
I agree with your annotation "Never trouble another...", but I found nothing.

It`s a little bit more complex.
Several structs shall be transfered.
A example struct:
Code:
struct CE_SILENT_PRINT_PLANE_PRINTED
{
   struct CD_FRAMEHEAD  frameHeader;
   ushort               wPgId[2];
   ushort               dEStation[2];
}

All structs have as first entry the struct "frameHeader".
In the struct "frameHeader" are information to identify the struct
around( in this case "CE_SILENT_PRINT_PLANE_PRINTED").

So the questions are:
How to put a struct into a StringBuilder object?
How to get the struct "frameHeader" out of the StringBuilder object?
How to get the full struct "CE_SILENT_PRINT_PLANE_PRINTED" out of the StringBuilder object? (if it works not similar to extraction of "frameHeader")

Code:
struct CD_FRAMEHEAD {
      BYTE bCmdCl;     /* Frame-Classs*/
      BYTE bCmdId;     /* Frame-Subclass*/
      BYTE bCmdFg;     /* Flag*/
      BYTE bEmulation; /* Flag for Emulation*/
   };


//Function declarations:

[DllImport("trdp.dll", SetLastError = true)]
public static extern uint TrdpSend(ushort wLinkID, [MarshalAs(UnmanagedType.LPStr)] StringBuilder pBuffer,
  ushort wNumOfBytesToSend, ref ushort pNumOfBytesSent);

[DllImport("trdp.dll", SetLastError = true)]
public static extern uint TrdpRecv(ushort wLinkID, [MarshalAs(UnmanagedType.LPStr)] StringBuilder pBuffer,
  ushort wNumOfBytesToRecv, ref ushort pNumOfBytesRecvd);
 
Use function aliases

I would recommend creating function aliases specifically for sending/receiving the structs. Something like this, perhaps:

(untested)
Code:
[DllImport("trdp.dll", SetLastError=true, EntryPoint="TrdpSend")]
public static extern uint TrdpSendStruct(
    ushort wLinkID,
    [MarshalAs(UnmanagedType.LPArray)] SilentPrintPlanePrinted[] sppp,
    ushort wNumOfBytesToSend,
    ref ushort pNumOfBytesSent
);

[DllImport("trdp.dll", SetLastError=true, EntryPoint="TrdpRecv")]
public static extern uint TrdpRecvStruct(
    ushort wLinkID,
    [MarshalAs(UnmanagedType.LPArray)] SilentPrintPlanePrinted[] sppp,
    ushort wNumOfBytesToRecv,
    ref ushort pNumOfBytesRecvd
);


[StructLayout(LayoutKind.Sequential)]
struct FrameHead
{
    byte bCmdCl;     /* Frame-Classs*/
    byte bCmdId;     /* Frame-Subclass*/
    byte bCmdFg;     /* Flag*/
    byte bEmulation; /* Flag for Emulation*/
}

[StructLayout(LayoutKind.Sequential)]
struct SilentPrintPlanePrinted
{
    FrameHead frameHeader
    [MarshalAs(UnmanagedType.ByValArray, SizeConst=2)] ushort[] wPgId;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst=2)] ushort[] dEStation;   
}

Good luck :cool:
 
That`s a good idea.
Unfortunately I have fixed conditions.
I write my software just for one side.
I cannot do any changes on the other side or the dll.

Something about the process:
In my program I start a thread which calls the dll function TrdpRecv.
Now the thread waits for any message.
I don`t know what kind of struct has arrived until I extract the FrameHead.
 
Byte arrays

The above code shouldnt require changes to the other application or the DLL, but it does not allow the header to be fetched before the rest of the struct, so is clearly not useful in this case.

Whenever working with binary data it is best to avoid string data types when possible. Here I would recommend using plain byte arrays, with code to pack and unpack the structures.

(untested)
Code:
[DllImport("trdp.dll", SetLastError=true)]
public static extern uint TrdpSend(
    ushort wLinkID,
    [MarshalAs(UnmanagedType.LPArray, SizeParamIndex=2)] byte[] pBuffer,
    ushort wNumOfBytesToSend,
    ref ushort pNumOfBytesSent
);

[DllImport("trdp.dll", SetLastError=true)]
public static extern uint TrdpRecv(
    ushort wLinkID,
    [MarshalAs(UnmanagedType.LPArray, SizeParamIndex=2)] byte[] pBuffer,
    ushort wNumOfBytesToRecv,
    ref ushort pNumOfBytesRecvd
);

//Structures defined as in previous post

public FrameHead UnpackFrameHead(byte[] pBuffer, int index)
{
    FrameHead fhOut = new FrameHead();

    fhOut.bCmdCl = pBuffer[index];
    fhOut.bCmdId = pBuffer[index + 1];
    fhOut.bCmdFg = pBuffer[index + 2];
    fhOut.bEmulation = pBuffer[index + 3];

    return fhOut;
}

public SilentPrintPlanePrinted UnpackSPPP(byte[] pBuffer, int index)
{
    SilentPrintPlanePrinted spppOut = new SilentPrintPlanePrinted();

    spppOut.frameHeader = UnpackFrameHead(pBuffer, index);
    spppOut.wPgId[0] = BitConverter.ToUInt16(pBuffer, index + 4);
    spppOut.wPgId[1] = BitConverter.ToUInt16(pBuffer, index + 6);
    spppOut.dEStation[0] = BitConverter.ToUInt16(pBuffer, index + 8);
    spppOut.dEStation[1] = BitConverter.ToUInt16(pBuffer, index + 10);

    return spppOut;
}

Admittedly, this seems like a long-winded way to do things, but Im not sure how else you would do it in C# - possibly with Marshal but its not obvious. The two methods could be moved into the structs themselves, either as constructors or static factory methods. You could also use unions to facilitate creation of the struct as described in this thread.

One thing to note is that if your structures are different sizes, you may end up with only part of a struct left at the end of your byte array, meaning you will need to preserve this part and concatenate it with the next set of bytes received in order to obtain the entire struct.

Good luck :)
 
Last edited by a moderator:
Now I tested it with the byte array.
The combination of unmanaged byte[] and BitConverter is exactly what I need :)
Now I can build my architecture with classes and inheritance and put the information immediately before sending into the byte[].
Thanks again MrPaul (and also PlausiblyDamp)!
 
Back
Top