Compare commits

...

3 commits

Author SHA1 Message Date
510d3547a2 port dll to clang-cl
really need to figure out why the stupid thing isn't working properly
2024-07-19 07:45:18 -04:00
cc59c0c6db Switch to clang-cl
It's less painful than mingw.
I'll next get the DLL going like this, though it'll be pretty easy.
2024-07-19 05:10:10 -04:00
d95d305734 more binding fuckery
i'm going to switch the build to clang-cl. To test, I've added EXE build support back to the speech2 C++ buildsystem and I'm going to replace the current mingw command lines for clang-cl in the next commit.

Because we only need XP SP3 compatibility, we won't need any compat library stuff, but I may still alter his libc++ fork if MSVC CRT ends up blowing too many chunks.
2024-07-19 03:59:19 -04:00
13 changed files with 166 additions and 64 deletions

View file

@ -1,18 +1,14 @@
build:
dotnet build -c Release
make -C speech2 -j$(nproc)
make -C speech2 CONFIG=Release -j$(nproc)
cp speech2/bin/x86/Release/speech2.dll SAPIServer/bin/Release/net40/windows-x86
cp /usr/i686-w64-mingw32/bin/libgcc_s_dw2-1.dll SAPIServer/bin/Release/net40/windows-x86/
cp /usr/i686-w64-mingw32/bin/libstdc++-6.dll SAPIServer/bin/Release/net40/windows-x86/
cp speech2/bin/x86/Release/speech2.pdb SAPIServer/bin/Release/net40/windows-x86
build-debug:
dotnet build -c Debug
make -C speech2 CONFIG=Release -j$(nproc)
cp speech2/bin/x86/Release/speech2.dll SAPIServer/bin/Debug/net40/windows-x86
cp /usr/i686-w64-mingw32/bin/libgcc_s_dw2-1.dll SAPIServer/bin/Debug/net40/windows-x86/
cp /usr/i686-w64-mingw32/bin/libstdc++-6.dll SAPIServer/bin/Debug/net40/windows-x86/
make -C speech2 CONFIG=Debug -j$(nproc)
cp speech2/bin/x86/Debug/speech2.dll SAPIServer/bin/Debug/net40/windows-x86
cp speech2/bin/x86/Debug/speech2.pdb SAPIServer/bin/Debug/net40/windows-x86
clean:
rm -rf SAPIServer/bin SAPIServer/obj

View file

@ -6,8 +6,11 @@ Simple HTTP frontend API for Microsoft Speech API
Requirements
- .NET SDK
- VS2022 lib pack (TODO: link)
- mingw-w64 toolchain built with `win32` thread model (`pthread` won't work)
You'll also need to chattr +F (or mount the whole thing with `ciopfs` and rename the headers to lowercase, if not on ext4 or you don't want to tune2fs) the windows sdk header directories so the build works.
`just` should do the trick.
## Running

View file

@ -36,16 +36,18 @@ namespace SAPIServer {
// Speech2 DLL API. Sync with c++ code.
internal class SpeechDLL {
[DllImport("speech2.dll")]
private const string Speech2DLL = "speech2.dll";
[DllImport(Speech2DLL, CallingConvention = CallingConvention.StdCall)]
public static extern IntPtr speech2_create_api(EngineType type);
[DllImport("speech2.dll")]
[DllImport(Speech2DLL, CallingConvention = CallingConvention.StdCall)]
public static extern void speech2_destroy_api(IntPtr pAPI);
[DllImport("speech2.dll")]
[DllImport(Speech2DLL, CallingConvention = CallingConvention.StdCall)]
public static extern int speech2_api_get_voiceinfo_count(IntPtr pAPI);
[DllImport("speech2.dll")]
[DllImport(Speech2DLL, CallingConvention = CallingConvention.StdCall)]
public static extern IntPtr speech2_api_get_voiceinfo_index(IntPtr pAPI, int index);
}
@ -61,6 +63,8 @@ namespace SAPIServer {
}
public List<VoiceDef> GetVoices() {
Console.WriteLine("SpeechAPI.GetVoices()");
var count = SpeechDLL.speech2_api_get_voiceinfo_count(handle);
Console.WriteLine($"count {count}");
var list = new List<VoiceDef>();

View file

@ -2,35 +2,29 @@ include build/arch.mk
include build/configs.mk
NAME = speech2
BINDIR = bin/$(ARCH)/$(CONFIG)
OBJDIR = obj/$(ARCH)/$(CONFIG)
TYPE = dll
# Any C++ file in src/ is automatically picked up.
CXXSRCS = $(wildcard src/*.cpp) $(wildcard src/*/*.cpp)
VPATH = $(dir $(CXXSRCS))
OBJS = $(addprefix $(OBJDIR)/,$(notdir $(CXXSRCS:.cpp=.o)))
.PHONY: all dumpinfo clean matrix
# Required libraries.
LIBS = kernel32.lib shell32.lib user32.lib uuid.lib ole32.lib
all: $(BINDIR)/$(NAME).dll
# dir rules
$(BINDIR)/:
echo -e "\e[95mMKDIR $@\e[0m"
mkdir -p $(BINDIR)
$(OBJDIR)/:
echo -e "\e[95mMKDIR $@\e[0m"
mkdir -p $(OBJDIR)
.PHONY: all clean matrix
include build/rules.mk
all: $(BUILD_PRODUCT)
clean:
echo -e "\e[91mCleaning... \e[0m"
rm -rf $(BINDIR)/ $(OBJS)
# A fun make trick. This allows for verbose compilation if desired.
# Set V=1 or anything, and it'll be verbose.
$V.SILENT:
# Include dependency files.
# Include dependency files generated by compilation.
# `make clean` keeps them so we can reuse, however they can
# still optionally be blown away.
-include $(OBJS:.o=.d)

30
speech2/Makefile.testprog Normal file
View file

@ -0,0 +1,30 @@
include build/arch.mk
include build/configs.mk
NAME = testprog
TYPE = exe
# Any C++ file in src/ is automatically picked up.
CXXSRCS = testprog.cpp
# Required libraries.
LIBS = kernel32.lib shell32.lib user32.lib uuid.lib ole32.lib
.PHONY: all clean matrix
include build/rules.mk
all: $(BUILD_PRODUCT)
clean:
echo -e "\e[91mCleaning... \e[0m"
rm -rf $(BINDIR)/ $(OBJS)
# A fun make trick. This allows for verbose compilation if desired.
# Set V=1 or anything, and it'll be verbose.
$V.SILENT:
# Include dependency files generated by compilation.
# `make clean` keeps them so we can reuse, however they can
# still optionally be blown away.
-include $(OBJS:.o=.d)

View file

@ -1,19 +1,26 @@
x86_Valid=yes
x86_TRIPLET=i686-w64-mingw32
#x64_Valid=yes
#x64_TRIPLET=x86_64-w64-mingw32
x86_TRIPLET=i686-pc-windows-msvc
# Only supported arch.
ifeq ($(ARCH),)
ARCH = x86
endif
ifeq ($(VCDIR),)
$(error Please set VCDIR in your environment to an appropiate path)
endif
ifneq ($($(ARCH)_Valid),yes)
$(error Please select a valid target)
endif
# if we really need C
CC = $($(ARCH)_TRIPLET)-gcc
CXX = $($(ARCH)_TRIPLET)-g++
WINDRES = $($(ARCH)_TRIPLET)-windres
CC = clang -target $($(ARCH)_TRIPLET)
CXX = clang -target $($(ARCH)_TRIPLET)
LD = lld-link
# This is $(WINDRES) thanks to the fact I
# depended on the (relatively low quality)
# MinGW toolchain. Thank God For LLVM.
WINDRES = llvm-rc

View file

@ -1,17 +1,33 @@
# Base compiler flags. Only change if you *explicitly* know what you're doing.
BASE_CCFLAGS = -MMD -fvisibility=hidden -std=gnu17 -fpermissive -fno-pic -fno-pie -fno-ident -msse -Iinclude -Isrc -D_UCRT -D_WIN32_WINNT=0x0501
BASE_CXXFLAGS = -MMD -fvisibility=hidden -std=c++20 -fpermissive -fno-pic -fno-pie -fno-ident -msse -Iinclude -Isrc -Ithird_party -D_UCRT -D_WIN32_WINNT=0x0501
BASE_LDFLAGS = -Wl,--subsystem=windows -fvisibility=hidden -shared -lkernel32 -lshell32 -luser32 -luuid -lole32
CXXRTDIR = $(VCDIR)/crt
UCRTDIR = $(VCDIR)/ucrt
PSDKDIR = $(VCDIR)/winsdk
Release_RTLIBS = libcmt.lib libucrt.lib libvcruntime.lib libcpmt.lib
Debug_RTLIBS = libcmtd.lib libucrtd.lib libvcruntimed.lib libcpmtd.lib
# I really should rename this x_x
CLANG_FLAGS = -fms-extensions -fms-compatibility-version=19 -isystem $(CXXRTDIR)/include -isystem $(UCRTDIR)/include -isystem $(PSDKDIR)/include/shared -isystem $(PSDKDIR)/include/um
BASE_FLAGS = -MMD -gcodeview -fvisibility=hidden $(CLANG_FLAGS) -march=pentium-mmx -Iinclude -Isrc -Ithird_party -mstack-alignment=4 -D_WIN32_WINNT=0x0501
BASE_CCFLAGS = $(BASE_FLAGS) -std=gnu17
BASE_CXXFLAGS = $(BASE_FLAGS) -std=c++20
BASE_LDFLAGS_SHARED = /dll
BASE_LDFLAGS = /nodefaultlib /version:5.1 /machine:i386 /subsystem:windows,5.1 /libpath:$(CXXRTDIR)/lib/x86 /libpath:$(UCRTDIR)/lib /libpath:$(PSDKDIR)/lib
# TODO: Figure out what optimizations are safe and don't break the stack
Release_Valid = yes
Release_CCFLAGS = -O3 -ffast-math -fomit-frame-pointer -DNDEBUG
Release_CXXFLAGS = -O3 -ffast-math -fomit-frame-pointer -DNDEBUG
Release_LDFLAGS = -s
Release_CCFLAGS = -O0 -DNDEBUG
Release_CXXFLAGS = -O0 -DNDEBUG
Release_LDFLAGS = /debug /pdb:$(BINDIR)/$(NAME).pdb
Debug_Valid = yes
Debug_CCFLAGS = -O0 -g -DDEBUG
Debug_CXXFLAGS = -O0 -g -DDEBUG
Debug_LDFLAGS =
Debug_CCFLAGS = -O0 -DDEBUG # -D_DEBUG
Debug_CXXFLAGS = -O0 -DDEBUG # -D_DEBUG -D_ITERATOR_DEBUG_LEVEL=0
Debug_LDFLAGS = /debug /pdb:$(BINDIR)/$(NAME).pdb
# select a default configuration or validate configuration
ifeq ($(CONFIG),)
@ -21,3 +37,7 @@ endif
ifneq ($($(CONFIG)_Valid),yes)
$(error Please select a valid configuration)
endif
# define the directories used for output products here.
BINDIR = bin/$(ARCH)/$(CONFIG)
OBJDIR = obj/$(ARCH)/$(CONFIG)

View file

@ -1,6 +1,24 @@
# TODO: Handle C sources and deduplicate.
VPATH = $(dir $(CXXSRCS))
OBJS = $(addprefix $(OBJDIR)/,$(notdir $(CXXSRCS:.cpp=.o)))
# Build types
ifeq ($(TYPE),dll)
BUILD_PRODUCT = $(BINDIR)/$(NAME).dll
$(BINDIR)/$(NAME).dll: $(BINDIR)/ $(OBJDIR)/ $(OBJS)
echo -e "\e[92mLinking binary $@\e[0m"
$(CXX) $(OBJS) $(BASE_LDFLAGS) $($(CONFIG)_LDFLAGS) -o $@
echo -e "\e[92mLinking DLL $@\e[0m"
$(LD) $(BASE_LDFLAGS_SHARED) $(BASE_LDFLAGS) $($(CONFIG)_LDFLAGS) $($(CONFIG)_RTLIBS) $(LIBS) $(OBJS) /out:$@
else
ifeq ($(TYPE),exe)
BUILD_PRODUCT = $(BINDIR)/$(NAME).exe
$(BINDIR)/$(NAME).exe: $(BINDIR)/ $(OBJDIR)/ $(OBJS)
echo -e "\e[92mLinking EXE $@\e[0m"
$(LD) $(BASE_LDFLAGS) $($(CONFIG)_LDFLAGS) $($(CONFIG)_RTLIBS) $(LIBS) $(OBJS) /out:$@
endif
endif
$(OBJDIR)/%.o: %.c
echo -e "\e[94mCompiling C source file $< ($@)\e[0m"
@ -17,3 +35,12 @@ $(OBJDIR)/%.o: %.S
$(OBJDIR)/%.o: %.rc
echo -e "\e[94mCompiling Windows resource script $<\e[0m"
$(WINDRES) -Iinclude $< -o $@
# dir rules
$(BINDIR)/:
echo -e "\e[95mMKDIR $@\e[0m"
mkdir -p $(BINDIR)
$(OBJDIR)/:
echo -e "\e[95mMKDIR $@\e[0m"
mkdir -p $(OBJDIR)

View file

@ -1,15 +1,18 @@
#include <windows.h>
#include "speechapi.hpp"
#define SP2_EXPORT __declspec(dllexport)
#define SPEECH2_API __declspec(dllexport) WINAPI
// Engine type. Sync with C#
enum class EngineType : int { ET_SAPI4, ET_SAPI5, ET_DECTALK };
extern "C" {
SP2_EXPORT void* speech2_create_api(EngineType type) {
SPEECH2_API void* speech2_create_api(EngineType type) {
ISpeechAPI* api = nullptr;
//printf("speech2_create_api(%d)\n", type);
switch(type) {
case EngineType::ET_SAPI4:
api = ISpeechAPI::CreateSapi4();
@ -17,6 +20,8 @@ SP2_EXPORT void* speech2_create_api(EngineType type) {
default: return nullptr;
}
printf("api is %p\n", api);
if(auto hr = api->Initialize(); FAILED(hr)) {
delete api;
return nullptr;
@ -25,14 +30,14 @@ SP2_EXPORT void* speech2_create_api(EngineType type) {
return static_cast<void*>(api);
}
SP2_EXPORT void speech2_destroy_api(void* engine) {
SPEECH2_API void speech2_destroy_api(void* engine) {
if(engine)
delete static_cast<ISpeechAPI*>(engine);
}
// API bindings TODO
SP2_EXPORT int speech2_api_get_voiceinfo_count(void* engine) {
SPEECH2_API int speech2_api_get_voiceinfo_count(void* engine) {
if(engine) {
auto* api = static_cast<ISpeechAPI*>(engine);
return api->GetVoices().size();
@ -40,7 +45,7 @@ SP2_EXPORT int speech2_api_get_voiceinfo_count(void* engine) {
return -1;
}
SP2_EXPORT const ISpeechAPI::VoiceInfo* speech2_api_get_voiceinfo_index(void* engine, int index) {
SPEECH2_API const ISpeechAPI::VoiceInfo* speech2_api_get_voiceinfo_index(void* engine, int index) {
if(engine) {
auto* api = static_cast<ISpeechAPI*>(engine);
return &api->GetVoices()[index];

View file

@ -7,7 +7,7 @@ BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) {
// have COM initalized by default, so we don't need to do so there.
switch(fdwReason) {
case DLL_PROCESS_ATTACH:
CoInitialize(nullptr);
//CoInitialize(nullptr);
break;
case DLL_THREAD_ATTACH: break;
@ -18,7 +18,7 @@ BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) {
if(lpvReserved != nullptr) {
break; // do not do cleanup if process termination scenario
}
CoUninitialize();
//CoUninitialize();
break;
}
return TRUE;

View file

@ -6,6 +6,9 @@
#include "sapi4/include/speech.h"
#include "speechapi.hpp"
// stupid hacky but Whatevers
struct SpeechAPI_SAPI4 : public ISpeechAPI {
virtual ~SpeechAPI_SAPI4() {
printf("~SpeechAPI_SAPI4\n");
@ -17,9 +20,9 @@ struct SpeechAPI_SAPI4 : public ISpeechAPI {
};
HRESULT Initialize() override {
HRESULT hRes;
HRESULT hRes{};
printf("speech2: SpeechAPI_Sapi4::Initalize() begin\n");
printf("speech2: SpeechAPI_Sapi4::Initalize() begin: %p\n", this);
hRes = pEnum.CreateInstance(CLSID_TTSEnumerator, CLSCTX_INPROC);
if(FAILED(hRes))
@ -30,24 +33,30 @@ struct SpeechAPI_SAPI4 : public ISpeechAPI {
printf("speech2: SpeechAPI_Sapi4::Initalize() created enum\n");
printf("speech2: SpeechAPI_Sapi4::Initalize() starting enumvoices\n");
// Fill out voices
EnumVoices();
printf("speech2: SpeechAPI_Sapi4::Initalize() end enumvoices\n");
printf("speech2: SpeechAPI_Sapi4::Initalize() filled out voices! Yay\n");
printf("speech2: SpeechAPI_Sapi4::Initalize() end\n");
return S_OK;
}
void EnumVoices() {
static TTSMODEINFO found {};
static TTSMODEINFO found{};
while(!pEnum->Next(1, &found, nullptr)) {
//auto ptr = strdup(found.szModeName);
printf("EnumVoices() voice %p\n", &found.szModeName);
//voices.push_back(VoiceInfo { .guid = found.gModeID, .voiceName = ptr });
auto ptr = _strdup(found.szModeName);
printf("EnumVoices() voice %s\n", found.szModeName);
voices.push_back({found.gModeID, ptr});
}
printf("EnumVoices() end\n");
pEnum->Reset();
}

View file

@ -13,6 +13,7 @@ struct ISpeechAPI {
//VoiceInfo(VoiceInfo&&) = delete;
~VoiceInfo() {
// Make this a lot less stupid at some point.
if(voiceName)
free(voiceName);
}

6
speech2/testprog.cpp Normal file
View file

@ -0,0 +1,6 @@
#include <stdio.h>
int main() {
printf("Hello, C++20 on Windows XP~\n");
return 0;
}