ocaml / flexdll

a dlopen-like API for Windows
Other
97 stars 30 forks source link

Generate import library for exported function of the main executable file #104

Open asmwarrior opened 1 year ago

asmwarrior commented 1 year ago

There is a statement in your project home page:

Windows DLL cannot refer to symbols defined in the main application or in previously loaded DLLs.

Some usual solutions exist, but they are not very flexible. A notable exception is the edll library (its homepage also describes the usual solutions), which follows a rather drastic approach; indeed, edll implements a new dynamic linker which can directly load object files (without creating a Windows DLL).

While, it looks like I can do this with the tool gendef and dlltool.

The method I was using is like:

For example, the exe_export.exe has some code like:

#include <Windows.h>
#include <stdio.h>

#define MYDLL_EXPORTS

#include "interface_exe.h"

void appli_printf7777(void)
{
   printf("7777");
   return;
}

void appli_printf8888(void)
{
   printf("8888");
   return;
}

int main(void)
{
   HMODULE hModule = LoadLibrary("dll.dll");
   FARPROC pFunc = GetProcAddress(hModule, "dllFunction");

   ((DLL_FUNC)pFunc)();

   return 0;
}

The content of the interface_exe.h is like below:

#ifndef INTERFACE_EXE_H_INCLUDED
#define INTERFACE_EXE_H_INCLUDED

 #ifdef  MYDLL_EXPORTS
    /*Enabled as "export" while compiling the dll project*/
    #define DLLEXPORT __declspec(dllexport)
 #else
    /*Enabled as "import" in the Client side for using already created dll file*/
    #define DLLEXPORT __declspec(dllimport)
 #endif

#ifdef __cplusplus
extern "C"
{
#endif

typedef void(*DLL_FUNC)(void);

DLLEXPORT void appli_printf7777(void);

DLLEXPORT void appli_printf8888(void);

#ifdef __cplusplus
}
#endif

#endif // INTERFACE_EXE_H_INCLUDED

And I have another dll.cpp file which is used to generate the dll.

#include <Windows.h>
#include <stdio.h>

#include "interface_exe.h"

#ifdef __cplusplus
extern "C"
{
#endif

__declspec (dllexport) void dllFunction(void);

#ifdef __cplusplus
}
#endif

typedef void(*APPLI_PRINTF7)(void);

__declspec(dllexport) void dllFunction(void)
{
   //HMODULE hModule = LoadLibrary("exe_export.exe");
   FARPROC pFunc = GetProcAddress(0, "appli_printf7777");

   if(pFunc)
      ((APPLI_PRINTF7)pFunc)();
   else
      printf("error = %d\n", GetLastError());

   appli_printf7777();
   appli_printf8888();

   return;
}

I first build the exe_export.exe file, and later I use the commands below to generate the import .a file:

gendef exe_export.exe
dlltool --dllname exe_export.exe --def exe_export.def --output-lib libexe_export.a

Then later, I link the libexe_export.a file to generate the dll.

Now, when I run the exe file, I have two methods to call the methods in the dll.

One is using the GetProcAddress method.

   //HMODULE hModule = LoadLibrary("exe_export.exe");
   FARPROC pFunc = GetProcAddress(0, "appli_printf7777");

   if(pFunc)
      ((APPLI_PRINTF7)pFunc)();
   else
      printf("error = %d\n", GetLastError());

The other is using the imported method, which I can directly call

   appli_printf7777();
   appli_printf8888();

All works fine.

Here are some reference: 1, the method I use is described as the .def and .a method, see this discussion: Enhanced DLL / Discussion / Help and FAQ: The .def and .a method mentioned in the home page

2, https://www.codeproject.com/Articles/17697/Plugin-System-an-alternative-to-GetProcAddress-and The method mentioned in this link is using MSVC, and I think it is similar with the gendef and dlltool method.

3, my demo code was changed from the sample code in this discussion: Exporting symbols in a .exe for external DLLs

Hope the above method can help others.

dra27 commented 1 year ago

Thanks for posting your experience with this! I have two questions with it, which I haven't looked into the detail. IIRC, generating .def files for the executable is brittle in that the executable can't be renamed - is that still the case? The other issue is data symbols (which we use for OCaml) - does this still only work for functions?

asmwarrior commented 1 year ago

Thanks for posting your experience with this! I have two questions with it, which I haven't looked into the detail. IIRC, generating .def files for the executable is brittle in that the executable can't be renamed - is that still the case?

You mean if a dll is linked to an exe, for example. b.dll is linked to a.exe (the b.dll will loaded by running a.exe), I can't rename the a.exe to x.exe?

My guess is this is correct, but I haven't tried it.

The other issue is data symbols (which we use for OCaml) - does this still only work for functions?

I haven't tried it, so I can't say whether some data symbol(for example, the global variable) can be shared, I will tried it. In my test, the C function and C++ function can be exported by exe, and later be called from the dll.

dra27 commented 1 year ago

The renaming part is the "killer" requirement (albeit subtly) for how FlexDLL is used in OCaml. In this case, we need DLLs which can be loaded by any executable (via LoadLibrary) and can access symbols from the main executable, regardless of its name. Before FlexDLL (if I remember correctly - it's a long time ago!) OCaml on Windows put the runtime into a DLL with a fixed name precisely so this could "work".

The Windows loader expects all the symbols in the DLL being loaded to be resolved by import libraries specified in the DLL only - FlexDLL extends that to allow the symbols to come directly from what's already running with the need to specify the import libraries (which makes the behaviour closer to dlopen). You can of course emulate that entirely in code with a registration API - flexlink makes it slightly more efficient by rewriting the code pages with the actual locations (so it removes the pointer indirections, just like the loader does).

asmwarrior commented 1 year ago

Thanks for posting your experience with this! I have two questions with it, which I haven't looked into the detail. IIRC, generating .def files for the executable is brittle in that the executable can't be renamed - is that still the case?

You mean if a dll is linked to an exe, for example. b.dll is linked to a.exe (the b.dll will loaded by running a.exe), I can't rename the a.exe to x.exe?

My guess is this is correct, but I haven't tried it.

I have just tested it, and I can change the exe file name, and when I run the exe file, the dll file can still be loaded correctly, and it can call the function inside the exe. This means the "rename" feature is OK in my method.

asmwarrior commented 1 year ago

The other issue is data symbols (which we use for OCaml) - does this still only work for functions?

I haven't tried it, so I can't say whether some data symbol(for example, the global variable) can be shared, I will tried it. In my test, the C function and C++ function can be exported by exe, and later be called from the dll.

I have just tested it. I can correctly export a global variable from the exe, and the dll can access the global variable.

So, I think the exported feature from exe file works correctly for both functions and variables.