Можно ли загрузить go-dll в c-dll в Windows?

Я хочу внедрить 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?

Вы, кажется, не включаете их библиотеку. Включили ли вы статическую библиотеку worker или loader. может понравиться: #cgo linux CFLAGS: -I/usr/include -I. #cgo linux LDFLAGS: -L/usr/lib -lworker

raoyc 15.12.2020 11:49

Спасибо @raoyc, да, кажется, я могу получить указатель/адрес функции StartWorker в worker.dll, просто больше не отвечает, когда вызывается startWork(). похоже функция заблокирована :(

12tall 15.12.2020 13:32

Читать learn.microsoft.com/en-us/windows/win32/dlls/…

Make that 4 16.12.2020 08:40

Зачем минусовать? Рассмотрим вопрос с C# вместо go, и вы, возможно, поймете, почему этот вопрос сложен. ОП явно считает, что это проблема времени выполнения.

Joshua 16.12.2020 18:19

@ make-that-4, Спасибо, парень, это действительно полезно. но я не знаю, как установить ваш комментарий в качестве ответа :(

12tall 17.12.2020 03:16
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
5
1 478
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Протестируйте нормально, возможно, вы вызвали 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, я думаю, это создаст новый поток или что-то, что вызовет взаимоблокировку. Еще раз спасибо :)

12tall 17.12.2020 02:36

И вы дали мне отличные примеры golang и c :D

12tall 17.12.2020 02:39

@ 12 высокий, может быть, но я проверил MessageBox в функции dll, похоже, он тоже заблокирован. Может быть, вызвать какую-нибудь dll в Windows, а не просто вернуть адрес или значение. Например, вызов bass.dll, вы можете использовать аудиокарту для воспроизведения звука в формате mp3, вызов gdip.dll, вы можете обрабатывать графическую карту, чтобы рисовать некоторые элементы пользовательского интерфейса на экране.

raoyc 17.12.2020 03:36
Ответ принят как подходящий

Я нашел способ реализовать функцию. Я только что написал новый код следующим образом. Сейчас они работают нормально.

http.go

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();
}

загрузчик.c

#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;
}

клиент.c

#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

Другие вопросы по теме