#include "catia_kesif.hpp" #include #include #include #include #include #include "sanal_masaustu.hpp" namespace fiko { namespace { std::string genisMetniUtf8Cevir(const wchar_t* w) { if (!w) return {}; int uz = WideCharToMultiByte(CP_UTF8, 0, w, -1, nullptr, 0, nullptr, nullptr); if (uz <= 1) return {}; std::string s(static_cast(uz) - 1, '\0'); WideCharToMultiByte(CP_UTF8, 0, w, -1, s.data(), uz, nullptr, nullptr); return s; } // ---- IDispatch late-binding yardimcilari (CATIA COM Automation buna dayanir; // projede once hic COM kullanimi yoktu, bu yuzden minimum-yeterli helper seti). ---- bool dispCagir(IDispatch* d, const wchar_t* isim, WORD bayrak, DISPPARAMS* parametreler, VARIANT* sonuc) { if (!d) return false; DISPID id = 0; LPOLESTR isimP = const_cast(isim); if (FAILED(d->GetIDsOfNames(IID_NULL, &isimP, 1, LOCALE_USER_DEFAULT, &id))) return false; EXCEPINFO ei{}; UINT hataArg = 0; return SUCCEEDED( d->Invoke(id, IID_NULL, LOCALE_USER_DEFAULT, bayrak, parametreler, sonuc, &ei, &hataArg)); } bool dispPropAl(IDispatch* d, const wchar_t* isim, VARIANT& sonuc) { VariantInit(&sonuc); DISPPARAMS bos{}; return dispCagir(d, isim, DISPATCH_PROPERTYGET, &bos, &sonuc); } // Koleksiyon.Item(idx) — CATIA/COM koleksiyonlari 1-tabanlidir. bool dispItemAl(IDispatch* koleksiyon, int idx, VARIANT& sonuc) { VariantInit(&sonuc); VARIANT arg; VariantInit(&arg); arg.vt = VT_I4; arg.lVal = idx; DISPPARAMS p{}; p.cArgs = 1; p.rgvarg = &arg; bool ok = dispCagir(koleksiyon, L"Item", DISPATCH_METHOD | DISPATCH_PROPERTYGET, &p, &sonuc); VariantClear(&arg); return ok; } std::string dispPropMetin(IDispatch* d, const wchar_t* isim) { VARIANT v; if (!dispPropAl(d, isim, v)) return {}; std::string sonuc; if (v.vt == VT_BSTR && v.bstrVal) sonuc = genisMetniUtf8Cevir(v.bstrVal); VariantClear(&v); return sonuc; } long dispPropTamsayi(IDispatch* d, const wchar_t* isim, long varsayilan) { VARIANT v; if (!dispPropAl(d, isim, v)) return varsayilan; long sonuc = varsayilan; if (v.vt == VT_I4) sonuc = v.lVal; else if (v.vt == VT_I2) sonuc = v.iVal; VariantClear(&v); return sonuc; } // ---- Aday pencere toplama (surec adi CNEXT.exe olan, gorunur, sahipsiz) ---- struct AdayToplayici { std::vector pencereler; }; std::wstring surecTamYolAl(DWORD pid) { HANDLE surec = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid); if (!surec) return {}; wchar_t yol[MAX_PATH]{}; DWORD boy = MAX_PATH; std::wstring sonuc; if (QueryFullProcessImageNameW(surec, 0, yol, &boy)) sonuc = yol; CloseHandle(surec); return sonuc; } bool cnextSureciMi(const std::wstring& tamYol) { size_t p = tamYol.find_last_of(L"\\/"); std::wstring ad = (p == std::wstring::npos) ? tamYol : tamYol.substr(p + 1); return _wcsicmp(ad.c_str(), L"CNEXT.exe") == 0; } BOOL CALLBACK adayEnumProc(HWND h, LPARAM lp) { auto* t = reinterpret_cast(lp); if (!IsWindowVisible(h)) return TRUE; if (GetWindow(h, GW_OWNER) != nullptr) return TRUE; // yalniz sahipsiz ust pencere (main frame) wchar_t baslik[512]; if (GetWindowTextW(h, baslik, 512) <= 0) return TRUE; DWORD pid = 0; GetWindowThreadProcessId(h, &pid); if (pid == 0) return TRUE; if (cnextSureciMi(surecTamYolAl(pid))) t->pencereler.push_back(h); return TRUE; } // ROT capraz kontrolu — DIKKAT: coklu CATIA instance'ta ROT genelde YALNIZ // SON kayitli "!CATIA.Application" kaydini tutar (bkz. dosya basi aciklamasi). // Bu yuzden sonuc yalniz bilgi/uyari amacli, kesif dogruluk KAYNAGI DEGILDIR. // TANI EKLEMESI (bu oturumda): AccessibleObjectFromWindow(OBJID_NATIVEOM) GERCEK // CATIA V5 makinesinde TUM pencerelerde hr=0x80004005 (E_FAIL) ile basarisiz // oldugu gozlemlendi (elevation eslesmesi denendi, sonuc degismedi) — yani CATIA // V5, Office gibi native-OM pencere mesajini desteklemiyor olabilir. Bu yuzden // ROT'taki HER "CATIA.Application" iceren moniker'in TAM display name'ini de // topluyoruz (adlarCikis) — hangi moniker sekliyle calistigini (tekil mi, // PID/dosya-yolu icerikli mi) gormeden dogru bir BindMoniker/GetObject // alternatifi tasarlanamaz. int rotCatiaKayitSayisiniSay(std::vector* adlarCikis = nullptr) { IRunningObjectTable* rot = nullptr; if (FAILED(GetRunningObjectTable(0, &rot)) || !rot) return -1; IEnumMoniker* enumMk = nullptr; int sayac = 0; if (SUCCEEDED(rot->EnumRunning(&enumMk)) && enumMk) { IMoniker* mk = nullptr; while (enumMk->Next(1, &mk, nullptr) == S_OK) { IBindCtx* bc = nullptr; if (SUCCEEDED(CreateBindCtx(0, &bc)) && bc) { LPOLESTR ad = nullptr; if (SUCCEEDED(mk->GetDisplayName(bc, nullptr, &ad)) && ad) { if (wcsstr(ad, L"CATIA.Application") != nullptr) { ++sayac; if (adlarCikis) adlarCikis->push_back(genisMetniUtf8Cevir(ad)); } CoTaskMemFree(ad); } bc->Release(); } mk->Release(); } enumMk->Release(); } rot->Release(); return sayac; } // DOGRU ROT kontrolu — GetObject(,"ProgID") ile AYNI mantik: ProgID->CLSID-> // class moniker->ROT. IMoniker::GetDisplayName() bu moniker turunde // "clsid:{GUID}:" doner, ProgID metnini ASLA icermez — bu yuzden yukaridaki // eski display-name substring taramasi ROT dolu olsa bile hep 0 dondururdu. // Basariliysa disaCikis'e Documents koleksiyonunu (IDispatch*) doldurur, // cagiran Release ETMEKLE YUKUMLUDUR. Bu sadece o an AKTIF/foreground olan // TEK CATIA orneginedir (ROT'un dogal sinirlamasi) — coklu instance kesfini // COZMEZ. bool rotAktifCatiaBagla(IDispatch** disaCikis, std::string* teshisCikis = nullptr) { if (disaCikis) *disaCikis = nullptr; auto hrYaz = [](HRESULT hr) { char b[16]; snprintf(b, sizeof(b), "0x%08lX", static_cast(hr)); return std::string(b); }; CLSID clsid; HRESULT hr1 = CLSIDFromProgID(L"CATIA.Application", &clsid); if (FAILED(hr1)) { if (teshisCikis) *teshisCikis = "CLSIDFromProgID basarisiz, hr=" + hrYaz(hr1) + " (\"CATIA.Application\" ProgID'i bu makinede/bu cagiran icin KAYITLI DEGIL)"; return false; } IMoniker* mk = nullptr; HRESULT hr2 = CreateClassMoniker(clsid, &mk); if (FAILED(hr2) || !mk) { if (teshisCikis) *teshisCikis = "CreateClassMoniker basarisiz, hr=" + hrYaz(hr2); return false; } IRunningObjectTable* rot = nullptr; HRESULT hr3 = GetRunningObjectTable(0, &rot); bool sonuc = false; if (SUCCEEDED(hr3) && rot) { IUnknown* punk = nullptr; HRESULT hr4 = rot->GetObject(mk, &punk); if (hr4 == S_OK && punk) { if (disaCikis) { sonuc = SUCCEEDED(punk->QueryInterface(IID_IDispatch, reinterpret_cast(disaCikis))); } else { sonuc = true; } punk->Release(); } else if (teshisCikis) { *teshisCikis = "CLSIDFromProgID=OK, IRunningObjectTable::GetObject basarisiz, hr=" + hrYaz(hr4) + (hr4 == MK_E_UNAVAILABLE ? " (MK_E_UNAVAILABLE — bu class moniker ROT'ta HIC KAYITLI DEGIL, yani " "CATIA.exe RegisterActiveObject() cagirmiyor/cagiramiyor bu ortamda)" : ""); } rot->Release(); } else if (teshisCikis) { *teshisCikis = "GetRunningObjectTable basarisiz, hr=" + hrYaz(hr3); } mk->Release(); return sonuc; } // ---- Faz 2: PID'e özel pencere taraması (yeni başlatılan tek bir süreç için) ---- struct PidPencereToplayici { DWORD pid; std::vector pencereler; }; BOOL CALLBACK pidPencereEnumProc(HWND h, LPARAM lp) { auto* t = reinterpret_cast(lp); DWORD pid = 0; GetWindowThreadProcessId(h, &pid); if (pid != t->pid) return TRUE; if (GetWindow(h, GW_OWNER) != nullptr) return TRUE; // sahipsiz ust pencere t->pencereler.push_back(h); return TRUE; } } // namespace CatiaKesifSonucu catiaKesifYap() { CatiaKesifSonucu sonuc; AdayToplayici toplayici; EnumWindows(adayEnumProc, reinterpret_cast(&toplayici)); sonuc.adayPencereSayisi = static_cast(toplayici.pencereler.size()); for (HWND h : toplayici.pencereler) { CatiaOturumBilgi oturum; oturum.hwnd = h; DWORD pid = 0; GetWindowThreadProcessId(h, &pid); oturum.pid = pid; wchar_t baslik[512]{}; GetWindowTextW(h, baslik, 512); oturum.pencereBasligi = genisMetniUtf8Cevir(baslik); oturum.surecYolu = genisMetniUtf8Cevir(surecTamYolAl(pid).c_str()); WINDOWPLACEMENT wp{}; wp.length = sizeof(wp); if (GetWindowPlacement(h, &wp)) { oturum.x = wp.rcNormalPosition.left; oturum.y = wp.rcNormalPosition.top; oturum.genislik = wp.rcNormalPosition.right - wp.rcNormalPosition.left; oturum.yukseklik = wp.rcNormalPosition.bottom - wp.rcNormalPosition.top; oturum.maksimize = (wp.showCmd == SW_SHOWMAXIMIZED); oturum.minimize = (wp.showCmd == SW_SHOWMINIMIZED); } std::string guid; MasaustuDurum md = pencereMasaustuGetir(h, guid); oturum.masaustuDurumu = masaustuDurumMetni(md); if (md == MasaustuDurum::Ok) { oturum.masaustuGuid = guid; } else { sonuc.uyarilar.push_back("Pencere \"" + oturum.pencereBasligi + "\" icin sanal masaustu tespiti basarisiz: " + oturum.masaustuDurumu); } IDispatch* uygulama = nullptr; HRESULT hr = AccessibleObjectFromWindow(h, OBJID_NATIVEOM, IID_IDispatch, reinterpret_cast(&uygulama)); if (SUCCEEDED(hr) && uygulama) { oturum.comEslesmeBasarili = true; ++sonuc.comEslesenSayisi; std::string aktifAd; VARIANT vAktif; if (dispPropAl(uygulama, L"ActiveDocument", vAktif)) { if (vAktif.vt == VT_DISPATCH && vAktif.pdispVal) aktifAd = dispPropMetin(vAktif.pdispVal, L"Name"); VariantClear(&vAktif); } VARIANT vBelgeler; if (dispPropAl(uygulama, L"Documents", vBelgeler)) { if (vBelgeler.vt == VT_DISPATCH && vBelgeler.pdispVal) { IDispatch* belgeler = vBelgeler.pdispVal; long adet = dispPropTamsayi(belgeler, L"Count", 0); for (long i = 1; i <= adet; ++i) { VARIANT vBelge; if (!dispItemAl(belgeler, static_cast(i), vBelge)) continue; if (vBelge.vt == VT_DISPATCH && vBelge.pdispVal) { CatiaBelgeBilgi b; b.ad = dispPropMetin(vBelge.pdispVal, L"Name"); b.tamYol = dispPropMetin(vBelge.pdispVal, L"FullName"); b.kayitliMi = !b.tamYol.empty() && b.tamYol != b.ad; b.aktifBelge = !aktifAd.empty() && aktifAd == b.ad; oturum.belgeler.push_back(std::move(b)); } VariantClear(&vBelge); } } VariantClear(&vBelgeler); } uygulama->Release(); } else { char hrMetin[32]; snprintf(hrMetin, sizeof(hrMetin), "0x%08lX", static_cast(hr)); sonuc.uyarilar.push_back("Pencere \"" + oturum.pencereBasligi + "\" (PID " + std::to_string(oturum.pid) + ") icin COM eslesmesi (AccessibleObjectFromWindow) basarisiz, hr=" + hrMetin); } sonuc.oturumlar.push_back(std::move(oturum)); } std::vector rotAdlari; sonuc.rotKayitSayisi = rotCatiaKayitSayisiniSay(&rotAdlari); if (sonuc.rotKayitSayisi >= 0 && sonuc.rotKayitSayisi < sonuc.comEslesenSayisi) { sonuc.uyarilar.push_back( "ROT yalniz " + std::to_string(sonuc.rotKayitSayisi) + " \"CATIA.Application\" kaydi gosteriyor ama " + std::to_string(sonuc.comEslesenSayisi) + " pencereyle COM eslesmesi kuruldu — bu BEKLENEN bir durumdur (coklu instance'ta ROT " "guvenilmez), kesif sonucunu ETKILEMEZ."); } // TANI: her ROT "CATIA.Application" moniker'inin TAM adini ayri satir olarak // logla — AccessibleObjectFromWindow calismadigi durumda alternatif baglanma // yolu (BindMoniker) tasarlamak icin gercek moniker seklini gormemiz lazim. for (size_t i = 0; i < rotAdlari.size(); ++i) { sonuc.uyarilar.push_back("ROT kaydi [" + std::to_string(i + 1) + "]: " + rotAdlari[i]); } // DOGRU ROT kontrolu (class moniker) — bu SADECE aktif/foreground instance'i // verir, ama "ROT gercekten bos mu" sorusuna yanlissiz cevap verir (bkz. // rotAktifCatiaBagla yorumlari ve catia_kesif.hpp dosya basi notu). IDispatch* uygulama = nullptr; std::string rotTeshis; sonuc.rotAktifCatiaBaglandi = rotAktifCatiaBagla(&uygulama, &rotTeshis); if (sonuc.rotAktifCatiaBaglandi) { VARIANT vBelgeler; if (dispPropAl(uygulama, L"Documents", vBelgeler)) { if (vBelgeler.vt == VT_DISPATCH && vBelgeler.pdispVal) { sonuc.rotAktifBelgeSayisi = static_cast(dispPropTamsayi(vBelgeler.pdispVal, L"Count", 0)); } VariantClear(&vBelgeler); } sonuc.uyarilar.push_back( "ROT (class-moniker, DOGRU yontem): AKTIF CATIA.Application'a baglanildi, " "Documents.Count=" + std::to_string(sonuc.rotAktifBelgeSayisi) + " — bu yalniz o an aktif/foreground olan TEK ornek icin gecerli, " "coklu instance kesfini COZMEZ."); uygulama->Release(); } else { sonuc.uyarilar.push_back("ROT (class-moniker, DOGRU yontem) basarisiz: " + rotTeshis); } return sonuc; } std::string catiaKesifRaporu(const CatiaKesifSonucu& s) { std::string r = "CATIA kesif: " + std::to_string(s.adayPencereSayisi) + " aday pencere, " + std::to_string(s.comEslesenSayisi) + " COM eslesmesi basarili"; if (s.rotKayitSayisi >= 0) r += ", ROT kaydi(eski/guvenilmez)=" + std::to_string(s.rotKayitSayisi); r += ", ROT-aktif-baglanti=" + std::string(s.rotAktifCatiaBaglandi ? "OK" : "BASARISIZ"); if (s.rotAktifCatiaBaglandi) r += "(Documents.Count=" + std::to_string(s.rotAktifBelgeSayisi) + ")"; r += ".\n"; int i = 1; for (const auto& o : s.oturumlar) { r += " [" + std::to_string(i++) + "] PID=" + std::to_string(o.pid) + " \"" + o.pencereBasligi + "\" COM=" + (o.comEslesmeBasarili ? "OK" : "BASARISIZ") + " masaustu=" + (o.masaustuGuid.empty() ? ("YOK(" + o.masaustuDurumu + ")") : o.masaustuGuid) + "\n"; for (const auto& b : o.belgeler) r += " - " + (b.kayitliMi ? b.tamYol : (b.ad + " [KAYITSIZ]")) + (b.aktifBelge ? " (aktif)" : "") + "\n"; } if (!s.uyarilar.empty()) { r += "Uyarilar:\n"; for (const auto& u : s.uyarilar) r += " - " + u + "\n"; } return r; } void* catiaUygulamasiPidIle(uint32_t pid) { PidPencereToplayici t{static_cast(pid), {}}; EnumWindows(pidPencereEnumProc, reinterpret_cast(&t)); for (HWND h : t.pencereler) { IDispatch* uygulama = nullptr; HRESULT hr = AccessibleObjectFromWindow(h, OBJID_NATIVEOM, IID_IDispatch, reinterpret_cast(&uygulama)); if (FAILED(hr) || !uygulama) continue; // KENDI KENDINI DOGRULA: AccessibleObjectFromWindow bazi CATIA-ic // yardimci/hayalet pencerelerde (orn. "CATIASessionWnd") de BASARILI // donebiliyor ama donen IDispatch gercek CATIA Application nesnesi // DEGIL — "Documents" property'si yok/basarisiz. Kabul etmeden once // gercekten calisiyor mu diye test et, calismiyorsa bu adayi birak, // bir sonraki pencereyi dene (korkor ilk-bulunani kabul ETME). VARIANT vBelgeler; VariantInit(&vBelgeler); const bool gecerli = dispPropAl(uygulama, L"Documents", vBelgeler) && vBelgeler.vt == VT_DISPATCH && vBelgeler.pdispVal != nullptr; VariantClear(&vBelgeler); if (gecerli) return uygulama; uygulama->Release(); } return nullptr; } void* catiaPencereBulPidIle(uint32_t pid) { PidPencereToplayici t{static_cast(pid), {}}; EnumWindows(pidPencereEnumProc, reinterpret_cast(&t)); if (t.pencereler.empty()) return nullptr; // Aday sahipsiz-ust-pencereler arasinda GORUNUR + BASLIKLI olani tercih et // (CATIA/CNEXT.exe genelde ayni PID altinda birden fazla sahipsiz ust // pencere acar — gizli/yardimci pencereler dahil, bunlarin cogu basliksiz // ve/veya gorunmez; EnumWindows Z-sirasina gore bunlardan biri gercek ana // cerceveden ONCE gelebilir, front() bu yuzden yanlis pencereyi secebilir). // "CATIASessionWnd" CATIA V5'in kendi ic/yardimci penceresi — normalde // gorunmez ama bazen (gozlemlendi) gorunur+basliksiz-degil hale gelebilen // BILINEN bir CATIA artefakti, gercek ana cerceve DEGIL — acikca disla. HWND ilkGorunurBaslikli = nullptr; for (HWND h : t.pencereler) { if (!IsWindowVisible(h) || GetWindowTextLengthW(h) <= 0) continue; wchar_t baslikW[256]{}; GetWindowTextW(h, baslikW, 256); if (wcscmp(baslikW, L"CATIASessionWnd") == 0) continue; // bilinen artefakt, atla if (!ilkGorunurBaslikli) ilkGorunurBaslikli = h; } if (ilkGorunurBaslikli) return static_cast(ilkGorunurBaslikli); // Hicbiri kriterlere uymuyorsa (beklenmedik durum) eski davranisa don. return static_cast(t.pencereler.front()); } bool catiaBelgeAc(void* uygulamaIDispatch, const std::string& tamYolUtf8, std::string& hataCikis) { auto* uygulama = reinterpret_cast(uygulamaIDispatch); if (!uygulama) { hataCikis = "Uygulama nesnesi bos (pid COM eslesmesi kurulmamis olabilir)"; return false; } VARIANT vBelgeler; if (!dispPropAl(uygulama, L"Documents", vBelgeler) || vBelgeler.vt != VT_DISPATCH || !vBelgeler.pdispVal) { hataCikis = "Documents koleksiyonu alinamadi"; VariantClear(&vBelgeler); return false; } IDispatch* belgeler = vBelgeler.pdispVal; const int wlen = MultiByteToWideChar(CP_UTF8, 0, tamYolUtf8.data(), static_cast(tamYolUtf8.size()), nullptr, 0); std::wstring wyol(static_cast(std::max(wlen, 0)), L'\0'); if (wlen > 0) MultiByteToWideChar(CP_UTF8, 0, tamYolUtf8.data(), static_cast(tamYolUtf8.size()), wyol.data(), wlen); VARIANT arg; VariantInit(&arg); arg.vt = VT_BSTR; arg.bstrVal = SysAllocString(wyol.c_str()); DISPPARAMS p{}; p.cArgs = 1; p.rgvarg = &arg; VARIANT sonuc; VariantInit(&sonuc); bool ok = false; DISPID id = 0; LPOLESTR isimP = const_cast(L"Open"); if (SUCCEEDED(belgeler->GetIDsOfNames(IID_NULL, &isimP, 1, LOCALE_USER_DEFAULT, &id))) { EXCEPINFO ei{}; UINT hataArg = 0; HRESULT hr = belgeler->Invoke(id, IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_METHOD, &p, &sonuc, &ei, &hataArg); if (SUCCEEDED(hr)) { ok = true; } else { char buf[64]; snprintf(buf, sizeof(buf), "Documents.Open basarisiz, hr=0x%08lX", static_cast(hr)); hataCikis = buf; if (hr == DISP_E_EXCEPTION && ei.bstrDescription) { hataCikis += " - " + genisMetniUtf8Cevir(ei.bstrDescription); SysFreeString(ei.bstrDescription); } } } else { hataCikis = "Documents.Open metodu bulunamadi (GetIDsOfNames basarisiz)"; } VariantClear(&sonuc); VariantClear(&arg); VariantClear(&vBelgeler); return ok; } } // namespace fiko