Я хочу внедрить c-dll в процесс. В c-dll загрузится другая dll, написанная golang. Вот мой код C:
когда загружается loader.dll, он автоматически загружает worker.dll, написанный на golang.
// loader.c
#include <windows.h>
#include <stdio.h>
#include <memory.h>
typedef void (*StartWorker)();
HMODULE hWorker = NULL;
BOOL APIENTRY DllMain(HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
{
hWorker = LoadLibrary("D:\\code\\toys\\worker.dll");
if (hWorker == NULL)
{
exit(1);
}
StartWorker startWorker = (StartWorker)GetProcAddress(hWorker, "StartWorker");
MessageBox(NULL, "worker starting", TEXT("Warning:"), MB_OK);
if (startWorker == NULL)
{
MessageBox(NULL, "error", TEXT("Warning:"), MB_OK);
exit(1);
}
startWorker();
MessageBox(NULL, "worker started", TEXT("Warning:"), MB_OK);
FreeLibrary(hWorker);
break;
}
case DLL_PROCESS_DETACH:
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
default:
break;
}
return TRUE;
}
и код перехода:
// worker.go
package main
/*
#include <stdlib.h>
#include <windows.h>
*/
import "C"
//export StartWorker
func StartWorker {
C.MessageBox(nil, C.CString("Hello Worker"), C.CString("Warning:"), 0)
}
func main() {
}
Я собрал их в MinGW-w64
. Когда loader.dll пытается вызвать StartWorker()
в worker.dll, процесс не показывает MessageBox. После того, как я переписал worker.dll на c, все работает хорошо. И это также нормально, когда я вызываю StartWorker()
по следующему коду:
#include <windows.h>
#include <stdio.h>
#include <memory.h>
typedef void (*StartWorker)();
int main()
{
char *dllPath = "D:\\code\\toys\\loader.dll";
HMODULE hWorker = NULL;
hWorker = LoadLibrary(dllPath);
StartWorker startWorker = (StartWorker)GetProcAddress(hWorker, "StartWorker");
if (startWorker == NULL)
{
MessageBox(NULL, "dllPath", TEXT("Warning:"), MB_OK);
exit(1);
}
startWorker();
MessageBox(NULL, "target", TEXT("Warning:"), MB_OK);
FreeLibrary(hWorker);
return 0;
}
Интересно, есть ли конфликт с go-runtime?
Спасибо @raoyc, да, кажется, я могу получить указатель/адрес функции StartWorker
в worker.dll
, просто больше не отвечает, когда вызывается startWork()
. похоже функция заблокирована :(
Читать learn.microsoft.com/en-us/windows/win32/dlls/…
Зачем минусовать? Рассмотрим вопрос с C# вместо go, и вы, возможно, поймете, почему этот вопрос сложен. ОП явно считает, что это проблема времени выполнения.
@ make-that-4, Спасибо, парень, это действительно полезно. но я не знаю, как установить ваш комментарий в качестве ответа :(
Протестируйте нормально, возможно, вы вызвали Microsoft-Windows-Only
api. Попробуйте изменить MessageBox()
(используя win32 user32.dll
) на Sum()
, чтобы проверить c-dll
, который вызвал go-dll
.
Вот иди файл в ген go-dll
// gosum.go
package main
/*
#include <stdlib.h>
*/
import "C"
//export Sum
func Sum(a int32, b int32) int32 {
return a + b
}
func main() {
}
Соберите c-shared Windows
dll:
go build -ldflags "-s -w" -buildmode=c-shared -o gosum.dll
вы можете написать тестовый файл в c/cpp:
#include <stdio.h>
#include <malloc.h>
#include <string.h>
#include "gosum.h"
int main() {
printf("Sum(1,2)=%d\n", Sum(1,2));
return 1;
}
Скомпилировал в MinGW-w64.
g++ -c test_gosum.cpp -o test_gosum.o -g -std=c++11
g++ test_gosum.o gosum.dll -o test_gosum.exe -g -std=c++11
.\test_gosum.exe
Затем встроить loader.dll
в Visual Studio
, который вызвал gosum.dll
// скопируйте файл go-dll
CGO gen gosum.h
в проект, возможно, вам нужно удалить код ошибки
// файл gosum.h:
/* Code generated by cmd/cgo; DO NOT EDIT. */
/* package gosum */
#line 1 "cgo-builtin-export-prolog"
#include <stddef.h> /* for ptrdiff_t below */
#ifndef GO_CGO_EXPORT_PROLOGUE_H
#define GO_CGO_EXPORT_PROLOGUE_H
#ifndef GO_CGO_GOSTRING_TYPEDEF
typedef struct { const char* p; ptrdiff_t n; } _GoString_;
#endif
#endif
/* Start of preamble from import "C" comments. */
#line 3 "gosum.go"
#include <stdlib.h>
#line 1 "cgo-generated-wrapper"
/* End of preamble from import "C" comments. */
/* Start of boilerplate cgo prologue. */
#line 1 "cgo-gcc-export-header-prolog"
#ifndef GO_CGO_PROLOGUE_H
#define GO_CGO_PROLOGUE_H
typedef signed char GoInt8;
typedef unsigned char GoUint8;
typedef short GoInt16;
typedef unsigned short GoUint16;
typedef int GoInt32;
typedef unsigned int GoUint32;
typedef long long GoInt64;
typedef unsigned long long GoUint64;
typedef GoInt64 GoInt;
typedef GoUint64 GoUint;
typedef float GoFloat32;
typedef double GoFloat64;
/*
static assertion to make sure the file is being used on architecture
at least with matching size of GoInt.
*/
typedef char _check_for_64_bit_pointer_matching_GoInt[sizeof(void*) == 64 / 8 ? 1 : -1];
#ifndef GO_CGO_GOSTRING_TYPEDEF
typedef _GoString_ GoString;
#endif
typedef void* GoMap;
typedef void* GoChan;
typedef struct { void* t; void* v; } GoInterface;
typedef struct { void* data; GoInt len; GoInt cap; } GoSlice;
#endif
/* End of boilerplate cgo prologue. */
#ifdef __cplusplus
extern "C" {
#endif
extern GoInt32 Sum(GoInt32 a, GoInt32 b);
#ifdef __cplusplus
}
#endif
// файл pch.h(созданный vs, нужно что-то добавить):
#define PCH_H
#include "framework.h"
#endif
// ----- added
#ifdef IMPORT_DLL
#else
#define IMPORT_DLL extern "C" _declspec(dllimport)
#endif
IMPORT_DLL int get_sum_by_another_dll(int a, int b);
// ----- added
// файл dllmain.cpp (созданный vs):
#include "pch.h"
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;
}
// файл loader.cpp:
#include "pch.h"
#include <stdlib.h>
#include <windows.h>
#include "gosum.h" // also need add `gosum.dll` to VS project file
using namespace std;
typedef int (CALLBACK* GOSUMFUNC)(GoInt32 a, GoInt32 b);
int __stdcall get_sum_by_another_dll(int a, int b)
{
HMODULE h = LoadLibrary(TEXT("gosum.dll"));
if (NULL == h || INVALID_HANDLE_VALUE == h) {
return 0;
}
GOSUMFUNC go_dll_sum = (GOSUMFUNC)GetProcAddress(h, "Sum");
if (go_dll_sum) {
return go_dll_sum(a, b);
}
return 1;
}
затем скомпилируйте их в VS, вы получите loader.dll
.
там gosum.dll
— это go-dll
, loader.dll
— это c-dll
, скомпилированный VisualStudio, который вызвал gosum.dll
.
вы можете попробовать вызвать им в golang или clang.
// demo.go протестируйте loader.dll и gosum.dll с помощью golang, также необходимо скопировать gosum.dll
и loader.dll
в один и тот же каталог.
package main
import (
"fmt"
"log"
"syscall"
)
func main() {
h, e := syscall.LoadLibrary("loader.dll") //Make sure this DLL follows Golang machine bit architecture (64-bit in my case)
if e != nil {
log.Fatal(e)
}
defer syscall.FreeLibrary(h)
proc, e := syscall.GetProcAddress(h, "get_sum_by_another_dll") //One of the functions in the DLL
if e != nil {
log.Fatal(e)
}
var a int32 = 2
var b int32 = 5
ret, _, err := syscall.Syscall(proc, 2, uintptr(a), uintptr(b), 0) //Pay attention to the positioning of the parameter
fmt.Println(ret)
fmt.Println(err)
}
запустите его, вы получите результат: get_sum_by_another_dll(2,5) = 7
// callloader.cpp тестирует loader.dll и gosum.dll с c/cpp, также необходимо скопировать gosum.dll
и loader.dll
в один и тот же каталог.
#include <iostream>
#include <Windows.h>
int main()
{
HINSTANCE hDllInst;
hDllInst = LoadLibrary(L"loader.dll");
typedef int(*SUMFUNC)(int a, int b);
SUMFUNC dll_sum_fun = (SUMFUNC)GetProcAddress(hDllInst, "get_sum_by_another_dll");
std::cout << dll_sum_fun(1, 2);
}
затем скомпилируйте его в VS, запустите ./callloader.exe, вы получите результат: get_sum_by_another_dll(1,2) = 3
Большое спасибо за ваш подробный ответ. Как уже упоминал @Make that 4
, я думаю, что реальная проблема заключается в том, что I should not execute the go function startWorker() in DllMain() of loader.dll
, я думаю, это создаст новый поток или что-то, что вызовет взаимоблокировку. Еще раз спасибо :)
И вы дали мне отличные примеры golang и c :D
@ 12 высокий, может быть, но я проверил MessageBox
в функции dll, похоже, он тоже заблокирован. Может быть, вызвать какую-нибудь dll в Windows, а не просто вернуть адрес или значение. Например, вызов bass.dll
, вы можете использовать аудиокарту для воспроизведения звука в формате mp3, вызов gdip.dll
, вы можете обрабатывать графическую карту, чтобы рисовать некоторые элементы пользовательского интерфейса на экране.
Я нашел способ реализовать функцию. Я только что написал новый код следующим образом. Сейчас они работают нормально.
package main
import "C"
import (
"fmt"
"net/http"
)
func sayHello(w http.ResponseWriter, r *http.Request) {
_, _ = w.Write([]byte("Hello World!"))
}
//export StartHttp
func StartHttp() {
http.HandleFunc("/", sayHello)
err := http.ListenAndServe("127.0.0.1:9999", nil)
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}
fmt.Printf("Listening: http://127.0.0.1:9000")
}
func main() {
// StartHttp();
}
#include <windows.h>
#include <stdio.h>
// https://forum.pellesc.de/index.php?topic=4725.0
#ifdef __GNUC__
HANDLE k __attribute__((section(".shared"), shared)) = NULL;
#endif
#ifdef _MSC_VER
#pragma data_seg(".shared")
HANDLE k = NULL;
#pragma data_seg()
#pragma comment(linker, "/section:.shared,RWS")
#endif
typedef void (*StartHttp)();
HMODULE hHttp = NULL;
BOOL APIENTRY DllMain(HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
{
hHttp = LoadLibrary("http.dll");
StartHttp startWorker = (StartHttp)GetProcAddress(hHttp, "StartHttp");
k = startWorker;
// do not execute the function here!
break;
}
case DLL_PROCESS_DETACH:
{
FreeLibrary(hHttp);
break;
}
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
default:
break;
}
return TRUE;
}
HANDLE GetHttpStarter()
{
return k;
}
#include <windows.h>
#include <stdio.h>
#include <memory.h>
typedef void (*StartHttp)();
typedef HMODULE (*GetHttpStarter)();
int main()
{
HMODULE hLoader = NULL;
hLoader = LoadLibrary("loader.dll");
GetHttpStarter getHttpStarter = (GetHttpStarter)GetProcAddress(hLoader, "GetHttpStarter");
StartHttp startHttp = (StartHttp)getHttpStarter();
startHttp();
printf("^^\n");
FreeLibrary(hLoader);
return 0;
}
> go build -buildmode=c-shared -o http.dll .\http.go
> gcc .\loader.c -shared -o loader.dll
> gcc .\client.c -o client.exe
> .\client.exe
Самая важная вещь, которую я изменил, это загрузка http.dll(go)
только в loader.dll(c)
DllMain()
без выполнения функций http.dll(go)
и сохранение адреса этих функций в общей памяти. поэтому я могу получить адрес функции http.dll(go)
, вызвав функцию экспорта GetHttpStarter()
loader.dll(c)
. Выглядит сложно, но работает :)
Я также протестировал внедрение dll, в результате теперь я могу разместить http-сервис на notepad.exe
:D
Вы, кажется, не включаете их библиотеку. Включили ли вы статическую библиотеку worker или loader. может понравиться: #cgo linux CFLAGS: -I/usr/include -I. #cgo linux LDFLAGS: -L/usr/lib -lworker