none
C# pinvoke: how to access exported constants/variables without using GetProcAddress

    Pregunta

  • Hi to all,
    I need to access some exported constants/variables from some unmanaged dll. Most of the times they're just instances of structs with already filled some internal data.
    The problem is that I fail to access them with every kind of pinvoke signature. I tried so many combinations and I've always failed:

    c++
    __declspec(dllexport) extern
    const TheInternalStruct TheConstant;
    
    C#
    [DllImport("dll.dll", CallingConvention = CallingConvention.Cdecl)]
    internal extern static IntPtr TheConstant();

    After playing a little with some native windows functions, I realized that I can retrieve exported constants and variables with the following sample code:

    IntPtr Dll = LoadLibrary(@"mydll.dll");
    IntPtr constAddr = GetProcAddress(Dll, "TheConstant");
    IntPtr theConstantPtr = Marshal.ReadIntPtr(constAddr); //The constant value I want!
    FreeLibrary(Dll);

    Now, from what I understand, once I have the address of the constant, I can get the actual value using the common techniques offered from the marshal class (for example ReadIntPtr as showed in the above sample).
    Loading/unloading the entire library only to get an address is really too much. I can always avoid that by storing the addresses I need in an enum, but this is a bad solution because my code will break as soon as the library is recompiled.

    So, the basic question is: how I can get the same results showed in the above code using the pinvoke platform? More specifically, how I can do something like this:

    [DllImport("dll.dll", CallingConvention = CallingConvention.Cdecl)]
    [return: MarshalAs(UnmanagedType.ReadIntPtrFromAddress)]
    internal extern static IntPtr TheConstantValue();
    
    or
    
    [DllImport("dll.dll", CallingConvention = CallingConvention.Cdecl)]
    [return: MarshalAs(UnmanagedType.IntPtrAddress)]
    internal extern static IntPtr GetConstantAddress();
    
    IntPtr ConstAddr = GetConstantAddress();
    IntPtr TheConstantValue = Marshal.ReadIntPtr(ConstAddr);

    Thanks to all in advance

    NEWS!

    I managed to improve a little the code. Instead of calling LoadLibrary(), I just call GetModuleHandle() which returns the handle of the module already loaded from the pinvoke platform. This saves me from loading/unloading the same module already loaded from pinvoke. 

    IntPtr Dll = GetModuleHandle(@"mydll.dll");
    IntPtr constAddr = GetProcAddress(Dll, "TheConstant");
    IntPtr theConstantPtr = Marshal.ReadIntPtr(constAddr); //The constant value I want!

    I still consider this as an unreliable hack. I'm sure that pinvoke can handle all of the process very easily. It's just my fault because I don't know the right keywords that pinvoke wants!

    Any help would be appreciated.

    • Editado mark_555 miércoles, 02 de mayo de 2012 22:51 Updated with new code
    miércoles, 02 de mayo de 2012 17:25

Respuestas

  • Hello mark_555,

    1. The technique of using LoadLibrary(), GetProcAddress(), conversion to managed type from an IntPtr followed eventually by FreeLibrary() is the correct way. This is not a hack. Exported data is just like an exported function. It is merely an exported symbol which can be retrieved by using GetProcAddress(). The "Proc" in GetProcAddress() is perhaps a misnomer in this sense.

    2. >> So, the basic question is: how I can get the same results showed in the above code using the pinvoke platform?
    There is no way to achieve what you want by using some mechanism involving the DllImportAttribute.

    3. Using GetModuleHandle() assumes that you have already loaded the DLL into your C# app. If it has been unloaded GetModuleHandle() will fail.

    4. Using the LoadLibrary()/FreeLibrary() pair is better in that if you have already loaded your DLL into your C# app, the calling LoadLibrary() will simply increment the reference count of the DLL. FreeLibrary() will decrement this ref count but not necessarily unload the DLL. The ref count must reach zero before this happens.

    5. >> I can always avoid that by storing the addresses I need in an enum, but this is a bad solution because my code will break as soon as the library is recompiled...
    Not only that, if, for some reason, the DLL is re-loaded into memory, it may be loaded into a different address and your pre-determined address will not be correct.

    6. >> Most of the times they're just instances of structs with already filled some internal data...
    In this situation, make sure you have declared a managed equivalent of the structure. It would be best to convert the structure into its managed version using Marshal.PtrToStructure().

    7. For more information, pls refer to :

    http://limbioliong.wordpress.com/2011/11/11/accessing-exported-data-from-a-dll-in-managed-code/

    - Bio.


    Please visit my blog : http://limbioliong.wordpress.com/


    • Editado Lim Bio Liong jueves, 03 de mayo de 2012 2:49 Added link to article.
    • Propuesto como respuesta Mike FengModerator jueves, 03 de mayo de 2012 11:40
    • Marcado como respuesta mark_555 jueves, 03 de mayo de 2012 16:51
    jueves, 03 de mayo de 2012 2:47

Todas las respuestas

  • Hello mark_555,

    1. The technique of using LoadLibrary(), GetProcAddress(), conversion to managed type from an IntPtr followed eventually by FreeLibrary() is the correct way. This is not a hack. Exported data is just like an exported function. It is merely an exported symbol which can be retrieved by using GetProcAddress(). The "Proc" in GetProcAddress() is perhaps a misnomer in this sense.

    2. >> So, the basic question is: how I can get the same results showed in the above code using the pinvoke platform?
    There is no way to achieve what you want by using some mechanism involving the DllImportAttribute.

    3. Using GetModuleHandle() assumes that you have already loaded the DLL into your C# app. If it has been unloaded GetModuleHandle() will fail.

    4. Using the LoadLibrary()/FreeLibrary() pair is better in that if you have already loaded your DLL into your C# app, the calling LoadLibrary() will simply increment the reference count of the DLL. FreeLibrary() will decrement this ref count but not necessarily unload the DLL. The ref count must reach zero before this happens.

    5. >> I can always avoid that by storing the addresses I need in an enum, but this is a bad solution because my code will break as soon as the library is recompiled...
    Not only that, if, for some reason, the DLL is re-loaded into memory, it may be loaded into a different address and your pre-determined address will not be correct.

    6. >> Most of the times they're just instances of structs with already filled some internal data...
    In this situation, make sure you have declared a managed equivalent of the structure. It would be best to convert the structure into its managed version using Marshal.PtrToStructure().

    7. For more information, pls refer to :

    http://limbioliong.wordpress.com/2011/11/11/accessing-exported-data-from-a-dll-in-managed-code/

    - Bio.


    Please visit my blog : http://limbioliong.wordpress.com/


    • Editado Lim Bio Liong jueves, 03 de mayo de 2012 2:49 Added link to article.
    • Propuesto como respuesta Mike FengModerator jueves, 03 de mayo de 2012 11:40
    • Marcado como respuesta mark_555 jueves, 03 de mayo de 2012 16:51
    jueves, 03 de mayo de 2012 2:47
  • Hi bio and thanks for your as always detailed and professional answers.
    1-2
    At this point, since pinvoke only handles functions and methods, I can stop any research.
    3-4
    In my case I should be able to call GetModuleHandle() safely because I can access the constant only after calling a few functions from the dll. If the normal behaviour is that once the dll is loaded in memory it will only be unloaded when the program terminates (unless I call manually FreeLibrary), I should stay fine.
    5
    Absolutely to avoid!
    6
    Yes, if I want to access and manipulate the data inside the struct I have to use Marshal.PtrToStructure() and store the result into an already declared managed equivalent of the structure. If I only need to pass a reference, Marshal.ReadIntPtr should be enough.
    7
    Thanks, I've discovered and read your tutorial before opening this thread. I really got usefull info. You really made a great job.

    8
    At this point I know well how to handle exported data, but there is one more question.
    Mostly of the time the exported data is declared as constant.
    As showed in my sample,  in c++ the constant is declared as "__declspec(dllexport) extern const TheInternalStruct TheConstant;".

    Whenever a particular function require this constant I just pass the constant name as argument ("int res = FunctionThatWantTheConst(TheConstant);".

    I would like to do something in a similar fashion also in C# but whenever I try to include the const keyword, VS refuse to compile:
    static const IntPtr TheConstant = Marshal.ReadIntPtr(GetProcAddress(GetModuleHandle("MyDll.dll"), "TheConstant"));

    It compiles only with "static readonly IntPtr TheConstant =". 
    I'm afraid there's no way to treat this as constant like does the unmanaged side.

    jueves, 03 de mayo de 2012 14:01
  • Hello mark_555,

    >> I would like to do something in a similar fashion also in C# but whenever I try to include the const keyword, VS refuse to compile:
    static const IntPtr TheConstant = Marshal.ReadIntPtr(GetProcAddress(GetModuleHandle("MyDll.dll"), "TheConstant"));...

    Yes, this is expected. Besides the fact that IntPtr cannot be declared as const, a const of any other type (e.g. an int) cannot be assigned a value that can only be determined at runtime (as in the return value of Marshal.ReadIntPtr() or any other function).

    The readonly keyword makes more sense because the actual value of the readonly entity can be determined at runtime, albeit only in a static constructor.

    - Bio.


    Please visit my blog : http://limbioliong.wordpress.com/

    jueves, 03 de mayo de 2012 14:36
  • Yes, unfortunately c# treats the constants in a different way than c++. The most near thing I can do is to use the readonly keyword. 
    At this point I think that I can mark this thread as solved. 

    Thanks again for your precious support.
    jueves, 03 de mayo de 2012 16:50