Calling Functions in EXE From Plugins in Windows

When writing an application that uses plugins sometimes it’s necessary to have the plugin call functions from the application. This is fairly easy to do on pretty much any OS but on Windows it requires the plugin to link to the application. Sometimes explicit linking is unreasonable. One situation where you don’t want the plugin linking to the application is when the plugin will be used by multiple applications.

Windows allows for runtime resolution of exported functions (GetProcAddress) and this is typically used to access functions in a loaded DLL. What MSDN’s documentation about GetProcAddress doesn’t say is this function is not exclusive to DLLs. GetProcAddress can also be called from a DLL on an EXE to get and execute functions from within an EXE. The trick (if you can call it that) is, have the functions in the EXE you want to access exported.

Following is two sample applications and a sample plugin (DLL). Each application provides the same function prototype with a different implementation. The plugin will call the applications function. The basic design is app1 and app2 calls mod1’s test_func wich calls the applications print_hi function. The implementation of print_hi is different between app1 and app2. mod1 can be used by both app1 and app2 and does not require static linking.

The directory structure for this example is app1/ app2/ mod1/. The sample applications look for the module is the relative directory ../mod1. In real world usage it would be better to assume that the module is in the same directory (or a in a modules subdirectory) as the application.

app1

Makefile.msc

TARGET = app1.exe

CC  = cl
AS  = ml
LD  = link
AR  = lib

OBJS = 
	app1_hi.obj 
	main.obj

LDFLAGS = /nologo /release /SUBSYSTEM:CONSOLE
DEFINES = /D_CRT_SECURE_NO_DEPRECATE
CFLAGS  = /nologo /TP /MD /Os /GF $(DEFINES)

all: $(TARGET)

.c.obj:
	$(CC) /c $(CFLAGS) /D "NDEBUG" $<

$(TARGET): $(OBJS)
	$(LD) $(LDFLAGS) /out:$@ $(OBJS) kernel32.lib /incremental:no /export:print_hi

clean:
	-del *.obj *.exe *.manifest

app1_hi.h

#ifndef APP1_HI_H
#define APP1_HI_H

__declspec(dllexport) void __stdcall print_hi(void);

#endif
[

app1_hi.c

#include <stdio.h>

__declspec(dllexport) void __stdcall print_hi(void)
{
	printf("Hin");
}

main.c

#include <stdio.h>
#include <windows.h>

typedef void (*test_func)(void);

int main(int argc, char **argv)
{
	HMODULE h = LoadLibrary("..\mod1\mod1.dll");
	if (!h) {
		printf("Could not load DLLn");
		return 1;
	}

	test_func tf;
	tf = (test_func)GetProcAddress(h, "test_func");
	if (!tf) {
		printf("Could not locate test_funcn");
		return 1;
	}
	tf();

	FreeLibrary(h);

	return 0;
}

app2

Makefile.msc

TARGET = app2.exe

CC  = cl
AS  = ml
LD  = link
AR  = lib

OBJS = 
	app2_hi.obj 
	main.obj

LDFLAGS = /nologo /release /SUBSYSTEM:CONSOLE
DEFINES = /D_CRT_SECURE_NO_DEPRECATE
CFLAGS  = /nologo /TP /MD /Os /GF $(DEFINES)

all: $(TARGET)

.c.obj:
	$(CC) /c $(CFLAGS) /D "NDEBUG" $<

$(TARGET): $(OBJS)
	$(LD) $(LDFLAGS) /out:$@ $(OBJS) kernel32.lib /incremental:no /export:print_hi

clean:
	-del *.obj *.exe *.manifest

app2_hi.h

#ifndef APP2_HI_H
#define APP2_HI_H

__declspec(dllexport) void __stdcall print_hi(void);

#endif

app2_hi.c

#include <stdio.h>

__declspec(dllexport) void __stdcall print_hi(void)
{
	int a = 12;
	int b = 99;

	printf("Hello World!n");
	printf("Also... n");
	printf("a = %in", a);
	printf("b = %in", b);
	printf("a + b = %i", a + b);
}

main.c


#include <stdio.h>
#include <windows.h>

typedef int (*test_func)();

int main(int argc, char **argv)
{
	HMODULE h = LoadLibrary("..\mod1\mod1.dll");
	if (!h) {
		printf("Could not load DLL: %in", GetLastError());
		return 1;
	}

	test_func tf;
	tf = (test_func)GetProcAddress(h, "test_func");
	if (!tf) {
		printf("Could not locate test_funcn");
		return 1;
	}
	tf();

	FreeLibrary(h);

	return 0;
}

mod1

Makefile.msc

TARGET = mod1.dll

CC  = cl
AS  = ml
LD  = link
AR  = lib

OBJS = main.obj
LDFLAGS = /nologo /release /SUBSYSTEM:CONSOLE
DEFINES = /D_CRT_SECURE_NO_DEPRECATE
CFLAGS  = /nologo /TP /MD /Os /GF $(DEFINES)

all: $(TARGET)

.c.obj:
	$(CC) /c $(CFLAGS) /D "NDEBUG" $<

$(TARGET): $(OBJS)
	$(LD) /DLL $(LDFLAGS) /out:$@ $(OBJS) kernel32.lib /incremental:no /def:exp.def

clean:
	-del *.obj *.dll *.manifest

exp.def

LIBRARY mod1.dll
EXPORTS
	test_func @1

main.h

#ifndef MAIN_H
#define MAIN_H

__declspec(dllexport) void __stdcall test_func(void);

#endif

main.c

#include <stdio.h>
#include <windows.h>

#include "main.h"

typedef void (*print_hi)(void);

__declspec(dllexport) void __stdcall test_func(void)
{
	/* There are two ways we can get the function address. */
#if 0
	/* Method 1 */
	char filename[MAX_PATH] = {0};
	GetModuleFileName(NULL, filename, MAX_PATH-1);
	HMODULE h = LoadLibrary(filename);
	if (!h) {
		printf("Could not load EXEn");
		return;;
	}

	print_hi ph;
	ph = (print_hi)GetProcAddress(h, "print_hi");
	if (!ph) {
		printf("Could not locate print_hin");
		return;
	}
	ph();

	FreeLibrary(h);
#endif

	/* Method 2 */
	print_hi ph = (print_hi)GetProcAddress(GetModuleHandle(NULL), "print_hi");
	if (!ph) {
		printf("Could not locate print_hin");
		return;
	}
	(*ph)();
}

If you look closely at mod1’s make file you’ll notice that it uses an exports.def file. This is an alternative way to declare exported functions than explicitly putting them in the make file as was done with app1 and app2.

mod1’s main.c also lists two ways to load the functions from the EXE. The first (commented out) method is more explicit but both methods ultimately use GetProcAddress to load the function. The first method gets the name of the running application (the EXE) wich then calls LoadLibrary and passes the returned HMODULE to GetProcAddress. The second method uses GetModuleHandle to get the HMODULE for the currently running process.