Pious Posted May 3, 2007 Posted May 3, 2007 (edited) 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): ... 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): ... 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 } } Edited May 3, 2007 by PlausiblyDamp Quote
Administrators PlausiblyDamp Posted May 3, 2007 Administrators Posted May 3, 2007 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 StringBuilder lpszServerName = new StringBuilder("127.0.0.1"); //would be better as string lpszServerName = "127.0.0.1"; Quote Posting Guidelines FAQ Post Formatting Intellectuals solve problems; geniuses prevent them. -- Albert Einstein
Pious Posted May 3, 2007 Author Posted May 3, 2007 Thank you for your answer. Unfortunately the problem is still the same. Quote
Administrators PlausiblyDamp Posted May 3, 2007 Administrators Posted May 3, 2007 IIRC 1008 as an error is permission related - what permissions does your user have and where are you running the app from? Quote Posting Guidelines FAQ Post Formatting Intellectuals solve problems; geniuses prevent them. -- Albert Einstein
Pious Posted May 3, 2007 Author Posted May 3, 2007 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. Quote
Administrators PlausiblyDamp Posted May 3, 2007 Administrators Posted May 3, 2007 Have you tried changing the CallingConvention e.g. DllImport("trdp.dll", SetLastError = true, CallingConvention=CallingConvention.StdCall) or whatever calling convention the DLL uses (my C knowledge is getting rusty...) Quote Posting Guidelines FAQ Post Formatting Intellectuals solve problems; geniuses prevent them. -- Albert Einstein
MrPaul Posted May 3, 2007 Posted May 3, 2007 (edited) 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: public static extern uint TrdpCreateLink( [MarshalAs(UnmanagedType.LPStr)] string pszRemoteIPAddress, ushort wRemotePortNum, ushort wLocalPortNum, uint dwTimeout, ref ushort pLinkID ); Good luck :cool: Edited May 3, 2007 by MrPaul Quote Never trouble another for what you can do for yourself.
Pious Posted May 4, 2007 Author Posted May 4, 2007 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? Quote
MrPaul Posted May 4, 2007 Posted May 4, 2007 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: static extern ... TrdpRecv( ..., [MarshalAs(UnmanagedType.LPStr)] StringBuilder buff, ..., ... ); Good luck :) Quote Never trouble another for what you can do for yourself.
Pious Posted May 5, 2007 Author Posted May 5, 2007 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: 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") 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); Quote
MrPaul Posted May 5, 2007 Posted May 5, 2007 Use function aliases I would recommend creating function aliases specifically for sending/receiving the structs. Something like this, perhaps: (untested) [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: Quote Never trouble another for what you can do for yourself.
Pious Posted May 5, 2007 Author Posted May 5, 2007 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. Quote
MrPaul Posted May 7, 2007 Posted May 7, 2007 (edited) Byte arrays The above code shouldn't 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) [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 I'm not sure how else you would do it in C# - possibly with Marshal but it's 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 :) Edited May 7, 2007 by MrPaul Quote Never trouble another for what you can do for yourself.
Pious Posted May 20, 2007 Author Posted May 20, 2007 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)! Quote
Recommended Posts