Code:
#define WIN32_LEAN_AND_MEAN#include <windows.h>
#include <string>
#include <vector>
#include <fstream>
typedef void* (* _mono_image_open_from_data_with_name) (char* data, DWORD data_len, int need_copy, int* status, int refOnly, const char* name);
typedef void* (* _mono_assembly_load_from_full) (void* image, const char* fname, int* status, int refonly);
typedef void* (*_mono_assembly_get_image)(void* assembly);
typedef int (*_mono_image_get_table_rows)(void* image, int table);
typedef void* (*_mono_class_get)(void* image, int type_token);
typedef const char* (*_mono_class_get_name)(void* klass);
typedef const char* (*_mono_class_get_namespace)(void* klass);
typedef void* (*_mono_class_get_methods)(void* klass, void* itr);
typedef const char* (*_mono_method_get_name)(void* method);
typedef void* (*_mono_domain_get)();
typedef void* (*_mono_string_new)(void* domain, const char* text);
typedef void* (*_mono_runtime_invoke)(void* method, void* obj, void** params, void** exc);
#define ASSEMBLY_NAME "Unityject.dll"
_mono_image_open_from_data_with_name mono_image_open_from_data_with_name;
_mono_assembly_load_from_full mono_assembly_load_from_full;
_mono_assembly_get_image mono_assembly_get_image;
_mono_image_get_table_rows mono_image_get_table_rows;
_mono_class_get mono_class_get;
_mono_class_get_name mono_class_get_name;
_mono_class_get_namespace mono_class_get_namespace;
_mono_class_get_methods mono_class_get_methods;
_mono_method_get_name mono_method_get_name;
_mono_domain_get mono_domain_get;
_mono_string_new mono_string_new;
_mono_runtime_invoke mono_runtime_invoke;
void PopulateMethods()
{
HMODULE m = GetModuleHandle(L"mono.dll");
mono_image_open_from_data_with_name = (_mono_image_open_from_data_with_name)GetProcAddress(m, "mono_image_open_from_data_with_name");
mono_assembly_load_from_full = (_mono_assembly_load_from_full)GetProcAddress(m, "mono_assembly_load_from_full");
mono_assembly_get_image = (_mono_assembly_get_image)GetProcAddress(m, "mono_assembly_get_image");
mono_image_get_table_rows = (_mono_image_get_table_rows)GetProcAddress(m, "mono_image_get_table_rows");
mono_class_get = (_mono_class_get)GetProcAddress(m, "mono_class_get");
mono_class_get_name = (_mono_class_get_name)GetProcAddress(m, "mono_class_get_name");
mono_class_get_namespace = (_mono_class_get_name)GetProcAddress(m, "mono_class_get_namespace");
mono_class_get_methods = (_mono_class_get_methods)GetProcAddress(m, "mono_class_get_methods");
mono_method_get_name = (_mono_method_get_name)GetProcAddress(m, "mono_method_get_name");
mono_domain_get = (_mono_domain_get)GetProcAddress(m, "mono_domain_get");
mono_string_new = (_mono_string_new)GetProcAddress(m, "mono_string_new");
mono_runtime_invoke = (_mono_runtime_invoke)GetProcAddress(m, "mono_runtime_invoke");
CloseHandle(m);
}
void* FindClass(void* image, const char* className, const char* classNamespace)
{
// Now find the class in the assembly we just loaded.
// We do it this way, because the "generic" lookup stuff seems to be pitiful with Unity.
// Sometimes it works, but most of the time, it doesn't. This resolves that entirely.
int numTypes = mono_image_get_table_rows(image, 0x2); // MONO_TABLE_TYPEDEF
void* foundClass = 0;
// Note: start at 1, go 1 beyond the end
// This expects 0x02000001, 0x02000002, etc
// Passing 0x02000000 will cause an error (sometimes), or return nothing.
for (int i = 1; i < numTypes+1; i++)
{
void* klass = mono_class_get(image, i | 0x02000000); // MONO_TOKEN_TYPE_DEF
if(!klass)
continue;
std::string name = mono_class_get_name(klass);
std::string ns = mono_class_get_namespace(klass);
if(name == className && ns == classNamespace)
{
foundClass = klass;
break;
}
}
return foundClass;
}
void* FindMethod(void* classPtr, const char* methodName)
{
void* itr = 0;
void* method = 0;
while((method = mono_class_get_methods(classPtr, itr)))
{
std::string name = mono_method_get_name(method);
if(name == methodName)
return method;
}
return NULL;
}
extern "C" __declspec( dllexport )
void Run(char* assemblyData, int assemblyDataLength, const char* classNamespace, const char* className, const char* functionName, const char* functionArgs)
{
// Ensure we have methods mapped.
if(!mono_runtime_invoke)
PopulateMethods();
int status = 0;
void* image = mono_image_open_from_data_with_name(assemblyData, assemblyDataLength, 1, &status, 0, ASSEMBLY_NAME);
if(!image || status != 0)
{
MessageBoxA(0, "Could not open image!", "Error", 0);
return;
}
void* assembly = mono_assembly_load_from_full(image, ASSEMBLY_NAME, &status, false);
if(!assembly || status != 0)
{
MessageBoxA(0, "Could not open image!", "Error", 0);
return;
}
// Ensure we can get an image from the assembly.
if(!mono_assembly_get_image(assembly))
{
MessageBoxA(0, "Assembly image could not be retrieved! (Sanity Check)", "Error", 0);
return;
}
// Now find the class in the assembly we just loaded.
// We do it this way, because the "generic" lookup stuff seems to be pitiful with Unity.
// Sometimes it works, but most of the time, it doesn't. This resolves that entirely.
int numTypes = mono_image_get_table_rows(image, 0x2); // MONO_TABLE_TYPEDEF
void* foundClass = FindClass(image, className, classNamespace);
if(!foundClass)
{
MessageBoxA(0, "Class type could not be found!", "Error", 0);
return;
}
void* foundMethod = FindMethod(foundClass, functionName);
if(!foundMethod)
{
MessageBoxA(0, "Method could not be found!", "Error", 0);
return;
}
if(functionArgs)
{
void* str = mono_string_new(mono_domain_get(), functionArgs);
void* args[1];
args[0] = str;
// Invoke the method on a "NULL" object (static call), with the specified arguments.
mono_runtime_invoke(foundMethod, NULL, args, NULL);
}
else
{
void** args = NULL;
mono_runtime_invoke(foundMethod, NULL, args, NULL);
}
}
// Simple method wrapper for reading the bytes of a file.
// We don't want to load into the game with a full path. Just have Mono generate a name.
// However, this does break things like Assembly.GetExecutingAssembly() and whatnot. You'll probably need to use
// something like... System.Diagnostics.Process.GetCurrentProcess().MainModule for path-related things.
// Optionally, just pass the "bin path" for your lib to your entry point method.
static std::vector<char> ReadAllBytes(char const* filename)
{
std::ifstream ifs(filename, std::ios::binary|std::ios::ate);
std::ifstream::pos_type pos = ifs.tellg();
std::vector<char> result(pos);
ifs.seekg(0, std::ios::beg);
ifs.read(&result[0], pos);
return result;
}
void Example()
{
// NOTE: DON'T DO this here, "Run" is an export. Call it from the main mono thread. This is just an example!
std::vector<char> bytes = ReadAllBytes("C:\\SomeEntrypointDll.dll");
Run(&bytes[0], bytes.size(), "MyNamespace", "MyClassName", "MyEntryPointMethod", "this is just any arguments you decide to pass, in string form");
}
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
Re-use all you want. It resolves a few issues with Unity doing weird things. (Especially when loading assemblies without full paths!)