#include "com_sunucu.hpp" #include #include #include #include #include // _wtol #include #include "calisma_alani_plan.hpp" #include "catia_kesif.hpp" // catiaPencereBulPidIle — PID->HWND, COM'suz/guvenilir #include "fiko/core/gunluk.hpp" #include "sanal_masaustu.hpp" // ComOturum, pencereMasaustuGetir #include "windows_surum.hpp" // {8F3A2C10-6B7D-4E15-9A3C-1D9E4F7B2A55} — bu projeye ozel, uydurulmus (MS'e // kayitli DEGIL, gerek de yok — yalniz bu makinede/bu kullanicida HKCU altinda // benzersiz olmasi yeterli) CLSID. static const CLSID kClsidFikocadUygulama = { 0x8F3A2C10, 0x6B7D, 0x4E15, {0x9A, 0x3C, 0x1D, 0x9E, 0x4F, 0x7B, 0x2A, 0x55}}; namespace fiko { namespace { // ---- genis<->utf8 yardimcilari (catia_kesif.cpp'deki genisMetniUtf8Cevir ile // AYNI sozlesme, ama o anonim namespace'te oldugu icin disari acilamiyor — // burada kucuk/bagimsiz bir kopyasi). ---- std::string genisUtf8(const std::wstring& w) { if (w.empty()) return {}; int uz = WideCharToMultiByte(CP_UTF8, 0, w.c_str(), static_cast(w.size()), nullptr, 0, nullptr, nullptr); if (uz <= 0) return {}; std::string s(static_cast(uz), '\0'); WideCharToMultiByte(CP_UTF8, 0, w.c_str(), static_cast(w.size()), s.data(), uz, nullptr, nullptr); return s; } std::wstring utf8Genis(const std::string& s) { if (s.empty()) return {}; int uz = MultiByteToWideChar(CP_UTF8, 0, s.c_str(), static_cast(s.size()), nullptr, 0); if (uz <= 0) return {}; std::wstring w(static_cast(uz), L'\0'); MultiByteToWideChar(CP_UTF8, 0, s.c_str(), static_cast(s.size()), w.data(), uz); return w; } std::vector pipeIleBol(const std::wstring& s) { std::vector sonuc; size_t basla = 0; while (true) { size_t bul = s.find(L'|', basla); if (bul == std::wstring::npos) { sonuc.push_back(s.substr(basla)); break; } sonuc.push_back(s.substr(basla, bul - basla)); basla = bul + 1; } return sonuc; } long variantUzunTamsayi(const VARIANT& v) { // VBA, tur kutuphanesi (ITypeInfo) olmayan late-bound IDispatch cagrilarinda // DEGISKEN argumanlari varsayilan olarak BYREF gonderir (literal/ifade // sonuclari ise BYVAL gelir) — cunku ByVal/ByRef'i bilecegi tur bilgisi YOK. // Bu yuzden VT_BYREF bayragini da coz, yoksa deger sessizce 0 okunur. if (v.vt & VT_BYREF) { switch (v.vt & ~VT_BYREF) { case VT_I4: return v.plVal ? *v.plVal : 0; case VT_I2: return v.piVal ? *v.piVal : 0; case VT_UI1: return v.pbVal ? *v.pbVal : 0; case VT_R4: return v.pfltVal ? static_cast(*v.pfltVal) : 0; case VT_R8: return v.pdblVal ? static_cast(*v.pdblVal) : 0; case VT_BSTR: return (v.pbstrVal && *v.pbstrVal) ? _wtol(*v.pbstrVal) : 0; case VT_VARIANT: return v.pvarVal ? variantUzunTamsayi(*v.pvarVal) : 0; default: return 0; } } switch (v.vt) { case VT_I4: return v.lVal; case VT_I2: return v.iVal; case VT_UI1: return v.bVal; case VT_R4: return static_cast(v.fltVal); case VT_R8: return static_cast(v.dblVal); case VT_BSTR: return v.bstrVal ? _wtol(v.bstrVal) : 0; default: return 0; } } std::wstring variantMetin(const VARIANT& v) { // Ayni BYREF gerekcesi (yukaridaki variantUzunTamsayi yorumuna bkz.) — // String degiskenler (orn. VBA'daki "yollar") late-bound cagrida // VT_BYREF|VT_BSTR olarak gelir, duz VT_BSTR DEGIL. if (v.vt == VT_BSTR && v.bstrVal) return std::wstring(v.bstrVal); if (v.vt == (VT_BSTR | VT_BYREF) && v.pbstrVal && *v.pbstrVal) { return std::wstring(*v.pbstrVal); } if (v.vt == (VT_VARIANT | VT_BYREF) && v.pvarVal) return variantMetin(*v.pvarVal); return L""; } // ---- Sunucu omru sayaclari (COM disinda, oldukca standart local-server // kalibi): g_nesneSayisi = su an yasayan FikocadUygulamaNesnesi adedi; // g_kilitSayisi = LockServer(TRUE) ile TRUE/FALSE arasindaki fark. Ikisi de // sifira dusunce sunucu surecinin CIKMASI gerektigine karar verilir — GUI // ACMAYAN bu arka-plan sureç sonsuza dek asilmasin diye. std::atomic g_nesneSayisi{0}; std::atomic g_kilitSayisi{0}; DWORD g_anaThreadId = 0; void comSunucuKapanmayiDenetle() { if (g_nesneSayisi.load() == 0 && g_kilitSayisi.load() == 0 && g_anaThreadId != 0) { PostThreadMessageW(g_anaThreadId, WM_QUIT, 0, 0); } } // ---- CatiaKaydet'in gercek is mantigi (IDispatch::Invoke'tan cagrilir) ---- // DONUS: bos wstring = basarili; dolu = insan-okunur hata metni (VBA'ya BSTR // olarak donecek). Sessiz basarisizlik YOK — her erken-cikis DOLU bir hata // metni birakir. std::wstring catiaKaydetIsle(long pid, long aktifIndex, const std::wstring& yollarW) { void* h = fiko::catiaPencereBulPidIle(static_cast(pid)); if (!h) { fiko::gunluk::uyari("CatiaKaydet: PID {} icin gorunur/sahipsiz ust pencere bulunamadi", pid); return L"PID " + std::to_wstring(pid) + L" icin gorunur bir CATIA penceresi bulunamadi (surec henuz tam acilmamis " L"olabilir, birkac saniye sonra tekrar deneyin)."; } HWND hwnd = static_cast(h); wchar_t baslikW[512]{}; GetWindowTextW(hwnd, baslikW, 512); const std::string pencereBasligi = genisUtf8(baslikW); std::string surecYolu; if (HANDLE sp = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, static_cast(pid))) { wchar_t yol[MAX_PATH]{}; DWORD boy = MAX_PATH; if (QueryFullProcessImageNameW(sp, 0, yol, &boy)) surecYolu = genisUtf8(yol); CloseHandle(sp); } fiko::PlanOturum po; po.pencereBasligi = pencereBasligi; po.surecYolu = surecYolu; WINDOWPLACEMENT wp{}; wp.length = sizeof(wp); if (GetWindowPlacement(hwnd, &wp)) { po.x = wp.rcNormalPosition.left; po.y = wp.rcNormalPosition.top; po.genislik = wp.rcNormalPosition.right - wp.rcNormalPosition.left; po.yukseklik = wp.rcNormalPosition.bottom - wp.rcNormalPosition.top; po.maksimize = (wp.showCmd == SW_SHOWMAXIMIZED); po.minimize = (wp.showCmd == SW_SHOWMINIMIZED); } { fiko::ComOturum com; // VD tespiti icin — basarisizsa sessizce atla, DOGRU raporla logda. if (com.basariliMi()) { std::string guid; fiko::MasaustuDurum md = fiko::pencereMasaustuGetir(hwnd, guid); if (md == fiko::MasaustuDurum::Ok) { po.masaustuGuid = guid; } else { fiko::gunluk::uyari("CatiaKaydet: sanal masaustu tespiti basarisiz (PID {}): {}", pid, fiko::masaustuDurumMetni(md)); } } else { fiko::gunluk::uyari("CatiaKaydet: COM oturumu acilamadi (PID {}), sanal masaustu atlaniyor", pid); } } for (const std::wstring& parca : pipeIleBol(yollarW)) { if (parca.empty()) continue; fiko::PlanBelge pb; pb.tamYol = genisUtf8(parca); size_t p = pb.tamYol.find_last_of("\\/"); pb.ad = (p == std::string::npos) ? pb.tamYol : pb.tamYol.substr(p + 1); pb.kayitliMi = true; pb.aktif = (static_cast(po.belgeler.size()) == aktifIndex); po.belgeler.push_back(std::move(pb)); } if (po.belgeler.empty()) { fiko::gunluk::uyari( "CatiaKaydet: PID {} icin belge listesi BOS geldi (CATIA'da acik belge yok mu, yoksa " "VBA'daki yol listesi hatali mi?)", pid); } const std::string dosyaYolu = fiko::sonPlanDosyaYolu(); fiko::CalismaAlaniPlani plan; std::string okumaHata; if (!fiko::calismaAlaniPlaniOku(dosyaYolu, plan, okumaHata)) { // Dosya yok (ilk push) veya bozuk — DURUST: nedeni logla, ama push akisini // KESME (kullanici her CATIA'da tek tek basiyor, tek bir hata tum turu // durdurmamali) — yeni bos plandan devam. fiko::gunluk::bilgi("CatiaKaydet: mevcut plan okunamadi ({}) — yeni plan olusturuluyor", okumaHata); plan = fiko::CalismaAlaniPlani{}; plan.windowsSurumOzeti = fiko::windowsSurumTespitEt().ozet(); } plan.olusturmaZamani = fiko::zamanDamgaOlustur("iso"); // son guncelleme zamani fiko::planOturumEkleGuncelle(plan, po); std::string yazmaHata; if (!fiko::calismaAlaniPlaniYaz(plan, dosyaYolu, yazmaHata)) { fiko::gunluk::hata("CatiaKaydet: plan dosyasina yazilamadi: {}", yazmaHata); return utf8Genis(yazmaHata); } fiko::gunluk::bilgi("CatiaKaydet: PID {} (\"{}\") -> {} belge kaydedildi ({})", pid, pencereBasligi, po.belgeler.size(), dosyaYolu); return L""; } // ============================================================================ // IDispatch nesnesi — "Fikocad.Application" — tek gercek metot: CatiaKaydet. // Ayrica "Ping" — VBA tarafinin baglantiyi hizlica test edebilmesi icin. // Late-binding (GetIDsOfNames+Invoke) ile calisir, tur kutuphanesi (ITypeInfo) // SAGLANMAZ — CATIA VBA'dan "Dim app As Object" + CreateObject icin yeterli. // ============================================================================ class FikocadUygulamaNesnesi final : public IDispatch { public: FikocadUygulamaNesnesi() { g_nesneSayisi.fetch_add(1); } ~FikocadUygulamaNesnesi() { g_nesneSayisi.fetch_sub(1); comSunucuKapanmayiDenetle(); } // ---- IUnknown ---- HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void** ppv) override { if (!ppv) return E_POINTER; if (riid == IID_IUnknown || riid == IID_IDispatch) { *ppv = static_cast(this); AddRef(); return S_OK; } *ppv = nullptr; return E_NOINTERFACE; } ULONG STDMETHODCALLTYPE AddRef() override { return static_cast(InterlockedIncrement(&ref_)); } ULONG STDMETHODCALLTYPE Release() override { LONG n = InterlockedDecrement(&ref_); if (n == 0) delete this; return static_cast(n); } // ---- IDispatch (tur bilgisi YOK — yalniz GetIDsOfNames+Invoke gerekli) ---- HRESULT STDMETHODCALLTYPE GetTypeInfoCount(UINT* pctinfo) override { if (pctinfo) *pctinfo = 0; return S_OK; } HRESULT STDMETHODCALLTYPE GetTypeInfo(UINT, LCID, ITypeInfo**) override { return E_NOTIMPL; } HRESULT STDMETHODCALLTYPE GetIDsOfNames(REFIID, LPOLESTR* rgszNames, UINT cNames, LCID, DISPID* rgDispId) override { if (cNames == 0 || !rgszNames || !rgDispId) return E_INVALIDARG; if (_wcsicmp(rgszNames[0], L"CatiaKaydet") == 0) { rgDispId[0] = 1; return S_OK; } if (_wcsicmp(rgszNames[0], L"Ping") == 0) { rgDispId[0] = 2; return S_OK; } return DISP_E_UNKNOWNNAME; } HRESULT STDMETHODCALLTYPE Invoke(DISPID dispIdMember, REFIID, LCID, WORD wFlags, DISPPARAMS* pDispParams, VARIANT* pVarResult, EXCEPINFO*, UINT*) override { if (!(wFlags & DISPATCH_METHOD)) return DISP_E_MEMBERNOTFOUND; switch (dispIdMember) { case 1: { // CatiaKaydet(pid As Long, aktifIndex As Long, yollar As String) As String if (!pDispParams || pDispParams->cArgs < 3) return DISP_E_BADPARAMCOUNT; // DISPPARAMS argumanlari TERS sirada gelir (COM sozlesmesi): son // bildirilen parametre rgvarg[0]'da. const long pid = variantUzunTamsayi(pDispParams->rgvarg[2]); const long aktifIndex = variantUzunTamsayi(pDispParams->rgvarg[1]); const std::wstring yollar = variantMetin(pDispParams->rgvarg[0]); const std::wstring hata = catiaKaydetIsle(pid, aktifIndex, yollar); if (pVarResult) { VariantInit(pVarResult); pVarResult->vt = VT_BSTR; pVarResult->bstrVal = SysAllocString(hata.c_str()); } return S_OK; } case 2: { // Ping() As String — baglanti testi if (pVarResult) { VariantInit(pVarResult); pVarResult->vt = VT_BSTR; pVarResult->bstrVal = SysAllocString(L"fikocad COM sunucusu calisiyor"); } return S_OK; } default: return DISP_E_MEMBERNOTFOUND; } } private: LONG ref_ = 0; }; // ============================================================================ // IClassFactory — CoCreateInstance/CreateObject bunun uzerinden yeni // FikocadUygulamaNesnesi orneklerini yaratir. // ============================================================================ class FikocadSinifFabrikasi final : public IClassFactory { public: HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void** ppv) override { if (!ppv) return E_POINTER; if (riid == IID_IUnknown || riid == IID_IClassFactory) { *ppv = static_cast(this); AddRef(); return S_OK; } *ppv = nullptr; return E_NOINTERFACE; } ULONG STDMETHODCALLTYPE AddRef() override { return static_cast(InterlockedIncrement(&ref_)); } ULONG STDMETHODCALLTYPE Release() override { LONG n = InterlockedDecrement(&ref_); if (n == 0) delete this; return static_cast(n); } HRESULT STDMETHODCALLTYPE CreateInstance(IUnknown* pUnkOuter, REFIID riid, void** ppv) override { if (!ppv) return E_POINTER; *ppv = nullptr; if (pUnkOuter) return CLASS_E_NOAGGREGATION; // agregasyon desteklenmiyor auto* nesne = new FikocadUygulamaNesnesi(); HRESULT hr = nesne->QueryInterface(riid, ppv); // basariliysa AddRef eder (ref_ 0->1) nesne->Release(); // gecici olusturma referansimiz — basarisizsa nesne burada silinir return hr; } HRESULT STDMETHODCALLTYPE LockServer(BOOL fLock) override { if (fLock) { g_kilitSayisi.fetch_add(1); } else { g_kilitSayisi.fetch_sub(1); comSunucuKapanmayiDenetle(); } return S_OK; } private: LONG ref_ = 0; }; } // namespace void comSunucusuKaydet() { wchar_t exeYolW[MAX_PATH]{}; if (!GetModuleFileNameW(nullptr, exeYolW, MAX_PATH)) { fiko::gunluk::uyari("COM sunucu kaydi: GetModuleFileNameW basarisiz, HKCU kaydi atlaniyor"); return; } wchar_t clsidMetinW[64]{}; StringFromGUID2(kClsidFikocadUygulama, clsidMetinW, 64); auto degerYaz = [](const std::wstring& anahtarYolu, const std::wstring& deger) -> bool { HKEY k = nullptr; if (RegCreateKeyExW(HKEY_CURRENT_USER, anahtarYolu.c_str(), 0, nullptr, 0, KEY_WRITE, nullptr, &k, nullptr) != ERROR_SUCCESS) { return false; } LONG r = RegSetValueExW(k, nullptr, 0, REG_SZ, reinterpret_cast(deger.c_str()), static_cast((deger.size() + 1) * sizeof(wchar_t))); RegCloseKey(k); return r == ERROR_SUCCESS; }; const std::wstring progId = L"Fikocad.Application"; const std::wstring clsidYol = L"Software\\Classes\\CLSID\\" + std::wstring(clsidMetinW); const std::wstring localServer = L"\"" + std::wstring(exeYolW) + L"\" -Embedding"; bool tamam = true; tamam &= degerYaz(L"Software\\Classes\\" + progId, L"Fikocad Uygulama Otomasyon Nesnesi"); tamam &= degerYaz(L"Software\\Classes\\" + progId + L"\\CLSID", clsidMetinW); tamam &= degerYaz(clsidYol, L"Fikocad Uygulama Otomasyon Nesnesi"); tamam &= degerYaz(clsidYol + L"\\LocalServer32", localServer); tamam &= degerYaz(clsidYol + L"\\ProgID", progId); if (!tamam) { fiko::gunluk::uyari( "COM sunucu kaydi (HKCU\\...\\Fikocad.Application) TAM tamamlanamadi — VBA'dan " "CreateObject(\"Fikocad.Application\") calismayabilir"); } } bool comEmbeddingKomutuMu(const std::wstring& komutSatiri) { // COM standart aktivasyon anahtari: "-Embedding" (eski "/Embedding" // varyanti da bazi COM etkinlestirme yollarinda gorulur — ikisi de kabul // edilir). return komutSatiri.find(L"-Embedding") != std::wstring::npos || komutSatiri.find(L"/Embedding") != std::wstring::npos; } int comSunucusuCalistir() { g_anaThreadId = GetCurrentThreadId(); HRESULT hrInit = CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); if (FAILED(hrInit)) { fiko::gunluk::hata("COM sunucu: CoInitializeEx basarisiz, hr=0x{:08X}", static_cast(hrInit)); return 1; } auto* fabrika = new FikocadSinifFabrikasi(); fabrika->AddRef(); DWORD kayitAnahtari = 0; HRESULT hr = CoRegisterClassObject(kClsidFikocadUygulama, fabrika, CLSCTX_LOCAL_SERVER, REGCLS_MULTIPLEUSE | REGCLS_SUSPENDED, &kayitAnahtari); if (FAILED(hr)) { fiko::gunluk::hata("COM sunucu: CoRegisterClassObject basarisiz, hr=0x{:08X}", static_cast(hr)); fabrika->Release(); // yalniz KENDI referansimiz (CoRegisterClassObject BASARISIZ oldu, COM // kendi ic AddRef'ini HIC yapmadi — burada iki kez Release cift-serbest // birakma/UAF olurdu) CoUninitialize(); return 1; } CoResumeClassObjects(); fiko::gunluk::bilgi("COM sunucu hazir (Fikocad.Application) — istemci bekleniyor"); MSG msg; BOOL sonuc; while ((sonuc = GetMessageW(&msg, nullptr, 0, 0)) != 0) { if (sonuc == -1) break; // GetMessage hatasi — durustce cik TranslateMessage(&msg); DispatchMessageW(&msg); } CoRevokeClassObject(kayitAnahtari); // COM'un kendi ic AddRef'ini burada Release EDER fabrika->Release(); // yalniz KENDI referansimiz kaldi, tek Release yeterli CoUninitialize(); fiko::gunluk::bilgi("COM sunucu kapandi (tum istemciler ayrildi/kilit sifirlandi)"); return 0; } } // namespace fiko