-3

I want to use API from Omron V4KU, the documentation described like this : API Struct

Original c# code :

    const string DllLocation = @"..\..\Libs\Omron\OMCR.dll";

    [DllImport(DllLocation)]
    public static extern LPCOMCR OMCR_OpenDevice(string lpcszDevice, LPCOMCR_OPTION lpcOption);

    public void Start()
    {
        var lpcOption = new LPCOMCR_OPTION();
        var result = OMCR_OpenDevice(null, lpcOption); // error method's type signature is not pinvoke compatible
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct LPCOMCR
    {
        public string lpcszDevice;
        public IntPtr hDevice;
        public uint lpcDevice;
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct LPCOMCR_OPTION
    {
        public uint dwReserved0;
        public uint dwReserved1;
        public uint dwReserved2;
        public uint dwReserved3;
    }

if I missed or wrong in writing code? sorry, my english is bad. thanks for help.

ypcryz
  • 172
  • 2
  • 15
  • 1
    The `OMCR_OPTION` is a union, you only declared the last (`USB`) part in your C# code. – vgru Feb 18 '15 at 09:15
  • yes i only declared USB, but in documentation structures only describe 1 struct. Should I use both? @Groo – ypcryz Feb 18 '15 at 09:18
  • 1
    Of course you should. If you looked at `sizeof(OMCR_OPTION)` in C you would see it's 7 DWORDS long (presuming your LPVOID is also 32-bit). Use `[StructLayout(LayoutKind.Explicit)]` and `FieldOffset` attributes to arrange your C# struct to match the native one. – vgru Feb 18 '15 at 09:22
  • im refer from this link https://social.msdn.microsoft.com/Forums/en-US/60150e7b-665a-49a2-8e2e-2097986142f3/c-equivalent-to-c-union?forum=csharplanguage, but there are properties that have the same name like 'dwReserved1' 'dwReserved2' 'dwReserved3'. how to make it in order to avoid errors? can u give me example please @Groo :) – ypcryz Feb 18 '15 at 09:32
  • Field names are not important, you can change them to anything you like (but you can also nest structures to avoid naming collisions and still get union-like behavior while marshaling). I've added an example in the answer below. – vgru Feb 18 '15 at 09:57

3 Answers3

3

Start by defining the union structure correctly:

// OMCR_OPTION.COM
[StructLayout(LayoutKind.Sequential)]
public struct OmcrCom
{
    public IntPtr Reserved0;
    public uint BaudRate;
    public uint Reserved1;
    public uint Reserved2;
    public uint Reserved3;
    public IntPtr Reserved1;
    public IntPtr Reserved2;
}

// OMCR_OPTION.USB
[StructLayout(LayoutKind.Sequential)]
public struct OmcrUsb
{
    public uint Reserved0;
    public uint Reserved1;
    public uint Reserved2;
    public uint Reserved3;
}

// OMCR_OPTION (union of COM and USB)
[StructLayout(LayoutKind.Explicit)]
public struct OmcrOptions
{
    [FieldOffset(0)]
    public OmcrCom Com;

    [FieldOffset(0)]
    public OmcrUsb Usb;
}

// OMCR
[StructLayout(LayoutKind.Sequential)]
public struct OmcrDevice
{
    public string Device;
    public IntPtr DeviceHandle;
    public IntPtr DevicePointer;
}

[DllImport(dllName: DllLocation, EntryPoint = "OMCR_OpenDevice"]
public static extern IntPtr OmcrOpenDevice(string type, ref OmcrOptions options);

And then call the method, something like:

var options = new OmcrOptions();
options.Com.BaudRate = 115200; // or whatever you need to set

var type = "COM"; // is this USB/COM? not sure

OmcrDevice device;

var devicePtr = OmcrOpenDevice(type, ref options);
if (devicePtr == IntPtr.Zero)
    device = (OmcrDevice)Marshal.PtrToStructure(devicePtr, typeof(OmcrDevice));
vgru
  • 49,838
  • 16
  • 120
  • 201
  • oh field names are not important, I think should be the same :D ok i try to write code like your example, but with same field names :D i attach code in edit section. When i run it, appear error "Unable to find an entry point named 'OMCR_OpenDevice' in DLL '..\\..\\Libs\\Omron\\OMCR.dll'" :( @Groo – ypcryz Feb 18 '15 at 10:09
  • @yovierayz: Oh, ok, I changed the function name also, you need to specify the entry point in the `DllImport` attribute then, I'll edit in a moment. – vgru Feb 18 '15 at 10:25
  • Wait, it says 'OMCR_OpenDevice' cannot be found? Then the function name is different? Post the entire header file with function signatures. – vgru Feb 18 '15 at 10:33
  • yes error message like that. i added function in interface section @Groo – ypcryz Feb 18 '15 at 10:40
  • 1
    @yovierayz: use [dumpbin](https://msdn.microsoft.com/en-us/library/b06ww5dd.aspx) and [undname](https://msdn.microsoft.com/en-us/library/5x49w699.aspx) to check if dll names are mangled, you will need to specify the exact decorated name as the entry point. – vgru Feb 18 '15 at 11:23
  • You should use `[In]` on the first argument. And perhaps `[MarshalAs(UnmanagedType.LPStr)]` too (and in the `OmcrDevice` struct as well), to be sure. And it's weird, why would the name be mangled when it seems to be a C function? @yovierayz, can you add the the OMCR_API define as well? – Luaan Feb 18 '15 at 16:16
  • @Groo i removed `CallingConvention = CallingConvention.Cdecl` and it's work and get return, but `lpcOmcr == IntPtr.Zero` and LPCOMCR.lpcszDevice value is null. @Luaan i'll try it. if want to see the complete file OMCR.H can be downloaded at https://www.dropbox.com/s/x8thshkjdzw9gns/OMCR.H?dl=0 @Groo @Luaan thanks for all your help. – ypcryz Feb 19 '15 at 03:54
  • @yovierayz Don't just *try* different calling conventions, they're very different, and can cause (managed) memory corruption for your whole process! The header file is specifying `cdecl`, so that's the correct convention. The problem is somewhere else. Is the DLL 32-bit? If so, and your application is 64-bit, `IntPtr` will not map correctly. Target 32-bit explicitly in your application, and it migth start working. You might even try changing all the `IntPtr`s to `uint`s, but be careful with that. – Luaan Feb 19 '15 at 08:43
  • finally it's done :) the final code in edited section. it's work if i run visual studio as administrator, i dont know why it could affects. if don't run as administrator, then `lpcOmcr` always `IntPtr.Zero`. Thanks for your help @Groo @Luaan :) – ypcryz Feb 20 '15 at 03:49
  • @yovierayz That sounds like the DLL is doing something it shouldn't be, or simply something your non-elevated user doesn't have permissions for. Personally, I don't think I've ever run Visual Studio in non-administrator mode :D In any case, you might want to capture the last error, if it's set - http://blogs.msdn.com/b/adam_nathan/archive/2003/04/25/56643.aspx This will allow you to get an error message (if any) when the function returns `IntPtr.Zero`. Give it a try with non-elevated VS and it might give you some pointers :) – Luaan Feb 20 '15 at 08:21
  • @yovierayz: if this is not a windows service app, you will have to add a [manifest file](http://stackoverflow.com/a/6413024/69809) to your app to make sure it runs elevated. – vgru Feb 20 '15 at 08:31
2

Well, for one, the documentation is asking you to pass LPCOMCR_OPTION as a pointer - you're passing it as a value. Using ref should help. There's another problem, though, and that's the return value - again, you're trying to interpret it as a value, while the docs say it's a pointer. However, this is a lot trickier than the first error - as far as I'm aware, your only options are using a C++/CLI interop library, or expecting IntPtr as a return value. In any case, you need to handle proper deallocation of the memory you get this way.

Luaan
  • 62,244
  • 7
  • 97
  • 116
  • i'm edited c# code, what does it mean like that? and I added code struct in omcr.dll @Luaan – ypcryz Feb 18 '15 at 09:09
  • @yovierayz Yeah, Groo is right - that's a union, so you need to implement it as such in C#. Unfortunately, C# doesn't really support unions, so your only option is just using a struct with `LayoutKind.Explicit` and manage the different fields manually, as Groo suggested. You still need to return `IntPtr`, though - David's code should work fine. Do include the header file's declaration of the methods if you want more detailed information. – Luaan Feb 18 '15 at 09:43
  • i've added header declaration @Luaan – ypcryz Feb 18 '15 at 10:14
2

You need to do it like this:

[StructLayout(LayoutKind.Sequential)]
public struct OMCR
{
    [MarshalAs(UnmanagedType.LPStr)]
    public string lpcszDevice;
    public IntPtr hDevice;
    public IntPtr lpcDevice;
}

[StructLayout(LayoutKind.Sequential)]
public struct OMCR_OPTION
{
    public uint dwReserved0;
    public uint dwReserved1;
    public uint dwReserved2;
    public uint dwReserved3;
}

[DllImport(DllLocation, CallingConvention = CallingConvention.???,
    SetLastError = true)]
public static extern IntPtr OMCR_OpenDevice(string lpcszDevice, 
    ref OMCR_OPTION lpcOption);

You need to replace CallingConvention.??? with the appropriate calling convention. We cannot tell from the question what that is. You will have to find out by reading the header file.

The return value is a pointer to OMCR. You need to hold on to this pointer and pass it to OMCR_CloseDevice when you are finished with it.

In order to obtain an OMCR value you would do the following:

OMCR_OPTION Option = new OMCR_OPTION(); // not sure how to initialize this
IntPtr DevicePtr = OMCR_OpenDevice(DeviceType, ref Option);
if (DevicePtr == IntPtr.Zero)
    throw new Win32Exception();
OMCR Device = (OMCR)Marshal.PtrToStructure(DevicePtr, typeof(OMCR));
David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • i added header declaration in code. is that CallingConvention.Cdecl? @David Heffernan – ypcryz Feb 18 '15 at 10:12
  • The question keeps changing. I'm no longer interested in attempting to help you. There's no fun here for me. Sorry. – David Heffernan Feb 18 '15 at 10:14
  • ok no problem, thanks for your help :) sorry if it makes you unhappy @David Heffernan – ypcryz Feb 18 '15 at 10:19
  • Why are you changing the question all the time? Don't you realise that we are giving you help? It is ungrateful to expect us to keep up with your entire debugging session. Why don't you ask a question and learn from the answers? – David Heffernan Feb 18 '15 at 10:30
  • Sorry, i still don't understand when I change the question? or maybe I misunderstand english into my language. if it's like that, I apologize once again. @David Heffernan – ypcryz Feb 18 '15 at 10:36
  • All those edits where you copy my answer into your question. So, what's the point of my answer now? I'm just repeating what's in the question. What happens next is that we keep debugging all the other problems in your code that come to the surface, and eventually somebody fixes the last bug and you declare them the winner. No thanks. – David Heffernan Feb 18 '15 at 10:38
  • Oh i now understand, because i'm added `CallingConvention = CallingConvention.Cdecl` in my question. I dont know if the rules like that. thanks for letting me know :) – ypcryz Feb 18 '15 at 10:55
  • No, you added all the IntPtr, PtrToStructure stuff etc. and still want more. There were no unions when I started, and now my answer makes me look like I'm an idiot. Yet it is the answer to the question that you originally asked. – David Heffernan Feb 18 '15 at 10:57