Avatar billede rrm Nybegynder
18. august 2008 - 18:36 Der er 12 kommentarer og
2 løsninger

Kald af function med en null struktur

Jeg har to funktioner:

public static int WPGrabSubmitJob(string urlStr)
public static int WPGrabSubmitJob(string urlStr, WPGrabOptionsStruct options)

Fra den første vil jeg gerne kalde den anden med anden argument som "null". Kan det lade sig gøre??? Ala i vb

WPGrabSubmitJob(urlStr, nothing)

Er lidt grøn i C#....
Avatar billede arne_v Ekspert
18. august 2008 - 18:43 #1
C# null svarer til VB.NET Nothing, men en struct kan ikke vaere null, saa ...
Avatar billede driis Nybegynder
18. august 2008 - 18:52 #2
Med andre ord, nej det kan ikke lade sig gøre. (Med mindre WPGrabOptionsStruct er erklæret som en referencetype, hvilket dens navngivning ikke tyder på :-)
Avatar billede aaberg Nybegynder
18. august 2008 - 19:20 #3
Hvis du laver parameteren nullable kan det lade sig gøre

WPGrapStpmitJob(string urlstring, WPGrapOptionsStruct? options);
Avatar billede rrm Nybegynder
18. august 2008 - 20:27 #4
hmmm... ok... Er der så en anden måde jeg skal bruge. Jeg en dll funktion, hvor parameteren er en pointer en en struktur (WPGrabOptionsStruct), så det jeg gerne vil opnå, er at kunne kalde den med enten en struktur eller null/nothing/... hvis den ikke behøves....
Avatar billede driis Nybegynder
18. august 2008 - 20:39 #5
Hvis din DLL tager en pointer til en struct som parameter, gætter jeg umiddelbart på at du kan erklære:
public static int WPGrabSubmitJob(string urlStr, [MarshalAs(UnmanagedType.LPStruct)] WPGrabOptionsStruct? options)

Så har du erklæret strukturen som nullable, og compileren lader dig sende null med som parameter. Samtidig har du i din DLLImport erklæret, at parameteren er en pointer til en struct.
Avatar billede rrm Nybegynder
19. august 2008 - 16:53 #6
ok, men kan ikke få det til at virke :(

Jeg har prøvet med:
[DllImport(DLL_NAME, CallingConvention = DLL_CC, CharSet = CharSet.Ansi, EntryPoint = "WPGrabSubmitJob")]
private static extern int WPGrabSubmitJobInt(string urlStr, [MarshalAs(UnmanagedType.LPStruct)] WPGrabOptionsStruct? options);

og kalder den med

public static int WPGrabSubmitJob(string urlStr, WPGrabOptionsStruct? options)
{
  return WPGrabSubmitJobInt(urlStr, options);
}

Men når jeg kører det får jeg:

An unhandled exception of type 'System.Runtime.InteropServices.MarshalDirectiveException' occurred in wpGrab.exe

Additional information: Cannot marshal 'parameter #2': Generic types cannot be marshaled.
Avatar billede rrm Nybegynder
19. august 2008 - 16:54 #7
Har også sat den til at debugge unmanaged kode.... Det kan den ikke lig, da vs2008, går ned i forsøget :(
Avatar billede driis Nybegynder
19. august 2008 - 19:07 #8
OK, jeg troede faktisk at marshalling funktionen var "smart nok" til at "gøre det rigtige" med en nullable type. En anden måde at opnå det på, kunne være (bemærk, jeg har ikke mulighed for at teste)

Erklær den eksterne funktion som:
[DllImport(DLL_NAME, CallingConvention = DLL_CC, CharSet = CharSet.Ansi, EntryPoint = "WPGrabSubmitJob")]
private static extern int WPGrabSubmitJobInt(string urlStr, IntPtr options);

Og lav din funktion der kalder den om til:
public static int WPGrabSubmitJob(string urlStr, WPGrabOptionsStruct? options)
{
        IntPtr ptr = IntPtr.Zero;
        try
        {
            if ( options != null )
            {
                ptr = Marshal.AllocHGlobal(Marshal.SizeOf(options.Value));
                Marshal.StructureToPtr(options.Value,ptr,false);
            }
            return WPGrabSubmitJobInt(urlStr,ptr);
        }
        finally
        {
            if ( ptr != IntPtr.Zero )
                Marshal.FreeHGlobal(ptr);
        }
}

Det "føles" mer besværligt end det burde være, så måske er der en nemmere vej. Efter min bedste overbevisning, burde ovenstående dog virke. Du skal selvfølgelig sikre dig at din C funktion rent faktisk tager en pointer til en struktur som parameter.
Avatar billede rrm Nybegynder
19. august 2008 - 23:13 #9
Mange tak, det virker, men du har helt ret i, at det er noget omstændigt :(. Kan man lave macroer i C# ala #define T(code) noget_kode code noget_kode_efter som er mulig i c++??

Altså:
#define test(ting) cout << ting << endl

og så kalde den med test("dette" << " er en " << "test") og så få c++ koden:

cout << "dette" << " er en " << "test" << endl ???
Avatar billede driis Nybegynder
20. august 2008 - 18:35 #10
Nej man kan ikke lave makroer i C#. Men du kunne jo lave en generel utility funktion som wrapper konverteringen fra struct til IntPtr; hvis du skal bruge det andre steder end i WPGrabSubmitJob. Evt. lave en container klasse der implementerer IDisposable, så du kan lave en pæn syntax, hvor du kun har brug for en using blok "ekstra" der hvor den skal anvendes.
Avatar billede rrm Nybegynder
20. august 2008 - 21:32 #11
Ok, det lyder som en god ide. Jeg har bikset følgende sammen:

using System;
using System.Runtime.InteropServices;

namespace wpGrabDotNet.DllCode
{
  public class StructToPointer : IDisposable
  {

    public IntPtr ptr = IntPtr.Zero;

    public StructToPointer(wpGrabDll.WPGrabOptionsStruct? inputStruct)
    {
      if (inputStruct != null)
      {
        ptr = Marshal.AllocHGlobal(Marshal.SizeOf(inputStruct.Value));
        Marshal.StructureToPtr(inputStruct.Value, ptr, false);
      }
    }

    public void Dispose()
    {
      if (ptr != IntPtr.Zero)
      {
        Marshal.FreeHGlobal(ptr);
      }
    }
  }
}

og så kan jeg bruge

using(StructToPointer sPointer = new StructToPointer(options))
{
return WPGrabSubmitJobInt(urlStr, sPointer.ptr);
}

Hvad synes du om den konstruktion??? For tiden bruger jeg public StructToPointer(wpGrabDll.WPGrabOptionsStruct? inputStruct) i konstruktøren, er der en måde til at gøre den mere generisk på???

Har forsøgt lidt med:

class test<T>
{
public void test(T t)
{
  ....
}
}

Men kan ikke rigtig få det til at fungere....
Avatar billede driis Nybegynder
21. august 2008 - 20:53 #12
For at få en pæn kode uden gentagelser, kunne du lave en generisk StructPointer f.eks. således:

using System;
using System.Runtime.InteropServices;

public class StructPointer<T> : IDisposable
    where T : struct
{
    private IntPtr ptr = IntPtr.Zero;

    public StructPointer(Nullable<T> value)           
    {
        if ( value != null )
        {
            ptr = Marshal.AllocHGlobal(Marshal.SizeOf(value.Value));
            Marshal.StructureToPtr(value.Value,ptr,false);
        }
    }

    ~StructPointer()
    {
        Dispose();
    }

    public void Dispose()
    {
        if(ptr != IntPtr.Zero)
        {
            try
            {
                Marshal.FreeHGlobal(ptr);
            }
            finally
            {
                ptr = IntPtr.Zero;
            }

        }
    }

    public static implicit operator IntPtr(StructPointer<T>  value)
    {
        return value.ptr;           
    }
}

public static class StructPointerExtension
{
    public static StructPointer<T> ToIntPtr<T>(this T value)
        where T : struct
    {
        return new StructPointer<T>(value);
    }

    public static StructPointer<T> ToIntPtr<T>(this System.Nullable<T> value)
        where T : struct
    {
        return new StructPointer<T>(value);
    }
}

Så kan du skrive:
using(var pOptions = options.ToIntPtr())
  return WPGrabSubmitJobInt(urlStr, pOptions);

Her udnyttes en implicit operator + en extension method, og vi får således en meget kort kodestump hvor StructToPointer bruges; som stadig klart udtrykker formålet med koden, når den læses. Du kan bare erstatte extension method'en med en alm. new hvis ikke du bruger C#3.0.
Avatar billede rrm Nybegynder
21. august 2008 - 21:16 #13
Mange tak... Er dog ikke med på hvad StructPointerExtension gør??

Jeg tror jeg holder mig til:

using System;
using System.Runtime.InteropServices;

namespace wpGrabDotNet.DllCode
{
  public class StructToPointer<Type> : IDisposable
    where Type : struct
  {

    private IntPtr ptr = IntPtr.Zero;

    public IntPtr ToPtr
    {
      get
      {
        return ptr;
      }
    }

    public StructToPointer(Nullable<Type> inputStruct)
    {
      if (inputStruct != null)
      {
        ptr = Marshal.AllocHGlobal(Marshal.SizeOf(inputStruct.Value));
        Marshal.StructureToPtr(inputStruct.Value, ptr, false);
      }
    }

    public void Dispose()
    {
      if (ptr != IntPtr.Zero)
      {
        try
        {
          Marshal.FreeHGlobal(ptr);
        }
        finally
        {
          ptr = IntPtr.Zero;
        }
      }
    }
  }
}

Og så kalde den ved:

using(StructToPointer<WPGrabOptionsStruct> sPointer = new StructToPointer<WPGrabOptionsStruct>(options))
      {
        return WPGrabSubmitJobInt(urlStr, sPointer.ptr);
      }

eller

      using (var t = new StructToPointer<WPGrabOptionsStruct>(options))
      {
        return WPGrabSubmitJobInt(urlStr, t.ToPtr);
      }
Avatar billede rrm Nybegynder
30. august 2008 - 22:06 #14
Men tak for hjælpen
Avatar billede Ny bruger Nybegynder

Din løsning...

Tilladte BB-code-tags: [b]fed[/b] [i]kursiv[/i] [u]understreget[/u] Web- og emailadresser omdannes automatisk til links. Der sættes "nofollow" på alle links.

Loading billede Opret Preview
Kategori
IT-kurser om Microsoft 365, sikkerhed, personlig vækst, udvikling, digital markedsføring, grafisk design, SAP og forretningsanalyse.

Log ind eller opret profil

Hov!

For at kunne deltage på Computerworld Eksperten skal du være logget ind.

Det er heldigvis nemt at oprette en bruger: Det tager to minutter og du kan vælge at bruge enten e-mail, Facebook eller Google som login.

Du kan også logge ind via nedenstående tjenester